zoologist 发表于 2020-1-23 14:04:15

键盘乐器

上小学的时候,音乐课对于五音不全的我来说是痛苦的存在。好在老师并不好意思给一个勤奋听话的孩子不及格,所以我音乐课期末成绩通常都是60分。三年级的一天,老师宣布要期末考试,内容很简单,随机抽取教科书上的一个简谱唱出来即可。经过认真的思考,我认为这是一个能够攫取满分的机会。于是在考试前,我找了一个会唱的小伙伴,付出了几个大大泡泡糖的代价后,他教了我考试范围中的一首歌曲。回去后,我又在那首歌曲的简谱上用“多来米发 索 拉西”将每个音符重新标记,随后背了下来。在另一个阳光灿烂的音乐课上,同学们排队到老师面前抽签,我盯住前面同学抽中的这首歌曲,然后等到我之后以迅雷不及掩耳之势抽出来那张,在老师狐疑的目光中假装在看谱,然后背诵了出来…….如果说有时候一种声音,可以把人带回真实的过去。那么对于我来说,这个声音就是《小星星》-----那首二十多年前死记硬背下来的歌曲。相比小号笛子之类的乐器,电子琴这样的键盘发生设备是更容易理解和操作的设备。如果将USB的键盘作为乐器按键,那么我们就能得到一个最简单的键盘乐器。为了达到这个目标,需要解决如下2个问题:1.   发声的问题2.   USB键盘数据的解析;首先需要一种能够发出乐器声音的模块。自然而然想到的是各种录音播放模块,但是很多时候会同时演奏出多个不同声音(可以理解为按下2个钢琴键实现同时发声)。对于这种情况下录音模块是无能为力的。接下来目光投向 目前已有的MIDI方案。MIDI是 MusicalInstrument Digital Interface(乐器数字接口)的简称。有时候中文会翻译成“迷笛”。这种技术是20世纪80 年代初为解决电声乐器之间的通信问题而提出的。简单的说这种方案规定了一种乐器通讯机制,比如当前按下了钢琴某个键,力度是多少。当音源设备收到这个消息后会根据内容播放实现准备好的声音。显而易见,这样的方案相比直接录音要节省大量空间,比如:十分钟左右的钢琴曲只有几十Kbytes的大小,WAV至少要十几Mbytes。经过比较,确定选用VS1053b 芯片的模块,这个芯片是单片OggVorbis/MP3/AAC/WMA/MIDI音频解码器,当它收到MIDI消息后会将乐器的声音直接发送出去。选择的是微雪的 Music Shield模块,是通过 SPI 接口和 Arduino 进行通讯。特别的我在阅读资料的时候特别看到,VS1053B芯片支持一种实时Midi模式,上电之后可以从RXPin 接受 MIDI 数据来发生。但是这种模式需要HW设置VS1053B 引脚。市面上的模块并没有支持和预留。因此,这次的 Music Shield仍然使用 SPI 接口,MIDI数据也是从SPI送到芯片中的。
对于第二个问题,很容易想到使用之前多次使用的USBHost Shield来完成。但是它存在占用 SPI 接口,代码复杂的缺点。经过比较最终确定使用的是南京沁恒微电子股份有限公司生产的USB键盘鼠标转串口通讯控制芯片 CH9350。南京沁恒是一家国产芯片公司,它设计了一些很有意思的USB 相关芯片,比如,市面上大量使用的廉价CH340 系列的USB转串口方案就是他们家的产品。这次使用的CH9350芯片是键盘鼠标远距离传输的方案。
例如,右侧的CH9350芯片解析操作者的USB键盘鼠标条码枪的数据,然后转换为TTL 串口信号(TXD,RXD)。这样可以用RS485或者网络发送到远端,再用另外的 CH3950芯片接收还原为一个USB设备从而实现远程操作的目的。同样的方法还可以实现一套键盘鼠标控制多台机器的目的。
硬件列表:1.      1.CH9350 Mini 模块2.      2.微雪MusicShield 模块3.      3Arduino Proto Shield (用于固定CH9350 Mini,无任何元件)4.      4.Arduino Proto Shield 扩展(用于插接Music Shield,无任何元件)硬件连接(实际上我使用Shield,直接插上即可,并没有特别线路连接):
需要特别注意的是 CH9350模块上的 RSV 需要接地,这样会使得芯片工作在状态3,每次自动将解析后的键盘鼠标数据通过串口发出:
软件方面,首先因为选用的是 Arduino Uno,留下硬串口以便Debug,使用软串口来和 CH9350通讯。之后将键盘按键和MIDI 发生映射起来,使用的映射方法和Everyone Piano 相同:

代码:
#include <SoftwareSerial.h>
#include <SPI.h>
#include <MusicPlayer.h>

MusicPlayer player;

//实例化软串口,连接到 Music Shield
SoftwareSerial USBKB(2, 3); // RX, TX

byte KeyToNote(byte key)
{
byte v=0;
switch (key) {
   case 0x1D: //Z
       v=36;
       break;
   case 0x1B: //X
       v=38;
       break;
   case 0x06: //C
       v=40;
       break;
   case 0x19: //V
       v=41;
       break;
   case 0x05: //B
       v=43;
       break;
   case 0x11: //N
       v=45;
       break;
   case 0x10: //M
       v=47;
       break;
      case 0x36: //,
       v=48;
       break;
      case 0x37: //.
       v=50;
       break;
   case 0x38: // /
       v=52;
       break;
   
   case 0x04: //A
       v=48;
       break;
   case 0x16: //S
       v=50;
       break;
   case 0x07: //D
       v=52;
       break;
   case 0x09: //F
       v=53;
       break;
   case 0x0A: //G
       v=55;
       break;
   case 0x0B: //H
       v=57;
       break;
   case 0x0D: //J
       v=59;
       break;
   case 0x0E: //K
       v=60;
       break;
   case 0x0F: //L
       v=62;
       break;
   case 0x33: //;
       v=64;
       break;
   case 0x34: //'
       v=65;
       break;
      
   case 0x14://Q
       v=60;
       break;
   case 0x1A: //W
       v=62;
       break;
   case 0x08://E
       v=64;
       break;
   case 0x15: //R
       v=65;
       break;
   case 0x17: //T
       v=67;
       break;
   case 0x1C: //Y
       v=69;
       break;
   case 0x18: //U
       v=71;
       break;
   case 0x0C: //I
       v=72;
       break;
   case 0x12: //O
       v=74;
       break;
   case 0x13: //P
       v=76;
       break;
   case 0x2F: //[
       v=77;
       break;      
   case 0x30: //]
       v=79;
       break;      

   case 0x1E://1
       v=72;
       break;
   case 0x1F: //2
       v=74;
       break;
   case 0x20://3
       v=76;
       break;
   case 0x21: //4
       v=77;
       break;
   case 0x22: //5
       v=79;
       break;
   case 0x23: //6
       v=81;
       break;
   case 0x24: //7
       v=83;
       break;
   case 0x25: //8
       v=84;
       break;
   case 0x26: //9
       v=86;
       break;
   case 0x27: //0
       v=88;
       break;
   case 0x28: //-
       v=89;
       break;      
   case 0x29: //=
       v=91;
       break;            
    default:
   break;
    }   
    return v;      
}
void setup()
{
//用于Arduino Debug 串口
Serial.begin(115200);

//用于和CH3995通讯
USBKB.begin(38400);

//初始化 Midi 功能
player.beginMidi();

delay(2000);

//设置主音量为最大
player.midiWriteData(0xB0, 0x07, 127);


   //player.midiWriteData(0xB0, 0, 0x00);    //Default bank GM1
   
   player.midiWriteData(0xC0, 1, 0);   
}

//每笔 CH9350 键盘数据长度
#define CH9350KBLENGTH11

//存放上一次 CH9350 发送过来的数据
char Last={0,0,0,0,0,0,0,0,0,0,0};
//存放当前 CH9350 发送过来的数据
char Current;

byte i;
boolean Found;
void loop()
{
byte Index=0;

//取得 CH9350 送过来的当前按键信息
while (Index<CH9350KBLENGTH-1) {//不保存首位的 0x57 帧头,所以总共只有
    if ((USBKB.available())&&(0x57==USBKB.read())) {
      while (Index<CH9350KBLENGTH-1) {
            if (USBKB.available()) {
                Current=USBKB.read();
            }
          }
      }
}

for (int p=0;p<CH9350KBLENGTH;p++) {Serial.print((byte)Current,HEX);Serial.print(" "); }
Serial.print("\n");

//检查 Current,如果Last[]没有的,那么需要发送出去
for (Index=4;Index<CH9350KBLENGTH;Index++) {
    Found=false;
    if (Current!=0) {
      for (i=4;i<CH9350KBLENGTH;i++) {
         if (Current==Last<i>) {
               Found=true;
         }
      }
    if (Found==false) {
      //Set instrument number. 0xC0 is a 1 data byte command
      player.midiNoteOn(0, KeyToNote(Current), 127);
      Serial.print("On ");
      Serial.println(Current,HEX);
      Serial.print(" >> ");
      Serial.println(KeyToNote(Current));
    }
    }
}

//检查 Last,如果Current[]里面没有,那么需要发送NoteOff命令
for (Index=4;Index<CH9350KBLENGTH;Index++) {
    Found=false;
    if (Last!=0) {
      for (i=4;i<CH9350KBLENGTH;i++) {
         if (Last==Current<i>) {
               Found=true;
         }
      }
      if (Found==false) {
      player.midiNoteOff(0, KeyToNote(Current), 127);
      Serial.print("Off ");
      Serial.println(KeyToNote(Last),HEX);
      }
   }
    }

   for (Index=0;Index<10;Index++) {Last=Current;}
}</i></i>
VS1053B 用来播放MIDI是很好的选择,这次使用的是微雪出品的Music Shield 价格在90左右,淘宝最低价格有35的,我觉得应该同样能够使用。

工作的视频https://v.qq.com/x/page/w3057ax6n3y.html
完整的代码和用的库



代码


gada888 发表于 2020-1-24 17:17:02

但实际演奏效果怎么,最好有视频

zoologist 发表于 2020-1-25 19:18:52

gada888 发表于 2020-1-24 17:17
但实际演奏效果怎么,最好有视频

工作的视频https://zhuanlan.zhihu.com/p/103840801

rzyzzxw 发表于 2020-1-26 16:49:20

项目不错啊。{:6_214:}

kylinpoet 发表于 2020-2-18 08:46:37

楼主强大,多谢分享。

gada888 发表于 2020-2-21 16:47:29

能脱离电脑播放么

zoologist 发表于 2020-2-24 16:04:05

gada888 发表于 2020-2-21 16:47
能脱离电脑播放么

可以啊,这个就是不用电脑直接放出来声音的

蟹蟹泳裤 发表于 2024-8-29 12:02:58

想问一下这个 可以接USB口的那种MIDI键盘演奏吗?还是要换别的芯片?

zoologist 发表于 2024-9-1 10:27:08

蟹蟹泳裤 发表于 2024-8-29 12:02
想问一下这个 可以接USB口的那种MIDI键盘演奏吗?还是要换别的芯片?

不可以的,这个没有做 USB MIDI 设备的解析
页: [1]
查看完整版本: 键盘乐器