[高级教程]键盘乐器

2912浏览
查看: 2912|回复: 6

[高级教程] 键盘乐器

[复制链接]
上小学的时候,音乐课对于五音不全的我来说是痛苦的存在。好在老师并不好意思给一个勤奋听话的孩子不及格,所以我音乐课期末成绩通常都是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送到芯片中的。
键盘乐器图1

对于第二个问题,很容易想到使用之前多次使用的USBHost Shield来完成。但是它存在占用 SPI 接口,代码复杂的缺点。经过比较最终确定使用的是南京沁恒微电子股份有限公司生产的USB键盘鼠标转串口通讯控制芯片 CH9350。南京沁恒是一家国产芯片公司,它设计了一些很有意思的USB 相关芯片,比如,市面上大量使用的廉价CH340 系列的USB转串口方案就是他们家的产品。这次使用的CH9350芯片是键盘鼠标远距离传输的方案。
键盘乐器图2
例如,右侧的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,直接插上即可,并没有特别线路连接):
键盘乐器图3
需要特别注意的是 CH9350模块上的 RSV 需要接地,这样会使得芯片工作在状态3,每次自动将解析后的键盘鼠标数据通过串口发出:
键盘乐器图4
软件方面,首先因为选用的是 Arduino Uno,留下硬串口以便Debug,使用软串口来和 CH9350通讯。之后将键盘按键和MIDI 发生映射起来,使用的映射方法和Everyone Piano 相同:

键盘乐器图5
代码:

  1. #include <SoftwareSerial.h>
  2. #include <SPI.h>
  3. #include <MusicPlayer.h>
  4. MusicPlayer player;
  5. //实例化软串口,连接到 Music Shield
  6. SoftwareSerial USBKB(2, 3); // RX, TX
  7. byte KeyToNote(byte key)
  8. {
  9.   byte v=0;
  10.   switch (key) {
  11.      case 0x1D: //Z
  12.        v=36;
  13.        break;
  14.      case 0x1B: //X
  15.        v=38;
  16.        break;
  17.      case 0x06: //C
  18.        v=40;
  19.        break;
  20.      case 0x19: //V
  21.        v=41;
  22.        break;
  23.      case 0x05: //B
  24.        v=43;
  25.        break;
  26.      case 0x11: //N
  27.        v=45;
  28.        break;
  29.      case 0x10: //M
  30.        v=47;
  31.        break;
  32.       case 0x36: //,
  33.        v=48;
  34.        break;
  35.       case 0x37: //.
  36.        v=50;
  37.        break;
  38.      case 0x38: // /
  39.        v=52;
  40.        break;
  41.      
  42.      case 0x04: //A
  43.        v=48;
  44.        break;
  45.      case 0x16: //S
  46.        v=50;
  47.        break;
  48.      case 0x07: //D
  49.        v=52;
  50.        break;
  51.      case 0x09: //F
  52.        v=53;
  53.        break;
  54.      case 0x0A: //G
  55.        v=55;
  56.        break;
  57.      case 0x0B: //H
  58.        v=57;
  59.        break;
  60.      case 0x0D: //J
  61.        v=59;
  62.        break;
  63.      case 0x0E: //K
  64.        v=60;
  65.        break;
  66.      case 0x0F: //L
  67.        v=62;
  68.        break;
  69.      case 0x33: //;
  70.        v=64;
  71.        break;
  72.      case 0x34: //'
  73.        v=65;
  74.        break;
  75.       
  76.      case 0x14:  //Q
  77.        v=60;
  78.        break;
  79.      case 0x1A: //W
  80.        v=62;
  81.        break;
  82.      case 0x08:  //E
  83.        v=64;
  84.        break;
  85.      case 0x15: //R
  86.        v=65;
  87.        break;
  88.      case 0x17: //T
  89.        v=67;
  90.        break;
  91.      case 0x1C: //Y
  92.        v=69;
  93.        break;
  94.      case 0x18: //U
  95.        v=71;
  96.        break;
  97.      case 0x0C: //I
  98.        v=72;
  99.        break;
  100.      case 0x12: //O
  101.        v=74;
  102.        break;
  103.      case 0x13: //P
  104.        v=76;
  105.        break;
  106.      case 0x2F: //[
  107.        v=77;
  108.        break;      
  109.      case 0x30: //]
  110.        v=79;
  111.        break;      
  112.      case 0x1E:  //1
  113.        v=72;
  114.        break;
  115.      case 0x1F: //2
  116.        v=74;
  117.        break;
  118.      case 0x20:  //3
  119.        v=76;
  120.        break;
  121.      case 0x21: //4
  122.        v=77;
  123.        break;
  124.      case 0x22: //5
  125.        v=79;
  126.        break;
  127.      case 0x23: //6
  128.        v=81;
  129.        break;
  130.      case 0x24: //7
  131.        v=83;
  132.        break;
  133.      case 0x25: //8
  134.        v=84;
  135.        break;
  136.      case 0x26: //9
  137.        v=86;
  138.        break;
  139.      case 0x27: //0
  140.        v=88;
  141.        break;
  142.      case 0x28: //-
  143.        v=89;
  144.        break;      
  145.      case 0x29: //=
  146.        v=91;
  147.        break;            
  148.     default:
  149.      break;
  150.     }   
  151.     return v;      
  152. }
  153. void setup()
  154. {
  155.   //用于Arduino Debug 串口
  156.   Serial.begin(115200);
  157.   
  158.   //用于和CH3995通讯
  159.   USBKB.begin(38400);
  160.   //初始化 Midi 功能
  161.   player.beginMidi();
  162.   
  163.   delay(2000);
  164.   //设置主音量为最大
  165.   player.midiWriteData(0xB0, 0x07, 127);
  166.   
  167.    //player.midiWriteData(0xB0, 0, 0x00);    //Default bank GM1
  168.    
  169.    player.midiWriteData(0xC0, 1, 0);   
  170. }
  171. //每笔 CH9350 键盘数据长度
  172. #define CH9350KBLENGTH  11
  173. //存放上一次 CH9350 发送过来的数据
  174. char Last[CH9350KBLENGTH]={0,0,0,0,0,0,0,0,0,0,0};
  175. //存放当前 CH9350 发送过来的数据
  176. char Current[CH9350KBLENGTH];
  177. byte i;
  178. boolean Found;
  179. void loop()
  180. {
  181.   byte Index=0;
  182.   //取得 CH9350 送过来的当前按键信息
  183.   while (Index<CH9350KBLENGTH-1) {//不保存首位的 0x57 帧头,所以总共只有
  184.     if ((USBKB.available())&&(0x57==USBKB.read())) {
  185.         while (Index<CH9350KBLENGTH-1) {
  186.             if (USBKB.available()) {
  187.                 Current[Index++]=USBKB.read();
  188.               }
  189.           }
  190.       }
  191.   }
  192.   
  193.   for (int p=0;p<CH9350KBLENGTH;p++) {Serial.print((byte)Current[p],HEX);Serial.print(" "); }
  194.   Serial.print("\n");
  195.   //检查 Current[Index],如果Last[]没有的,那么需要发送出去
  196.   for (Index=4;Index<CH9350KBLENGTH;Index++) {
  197.     Found=false;
  198.     if (Current[Index]!=0) {
  199.       for (i=4;i<CH9350KBLENGTH;i++) {
  200.          if (Current[Index]==Last<i>) {
  201.                  Found=true;
  202.          }
  203.       }
  204.     if (Found==false) {
  205.       //Set instrument number. 0xC0 is a 1 data byte command
  206.       player.midiNoteOn(0, KeyToNote(Current[Index]), 127);
  207.       Serial.print("On ");
  208.       Serial.println(Current[Index],HEX);
  209.       Serial.print(" >> ");
  210.       Serial.println(KeyToNote(Current[Index]));
  211.     }
  212.     }
  213.   }
  214.   //检查 Last[Index],如果Current[]里面没有,那么需要发送NoteOff命令
  215.   for (Index=4;Index<CH9350KBLENGTH;Index++) {
  216.     Found=false;
  217.     if (Last[Index]!=0) {
  218.       for (i=4;i<CH9350KBLENGTH;i++) {
  219.          if (Last[Index]==Current<i>) {
  220.                  Found=true;
  221.          }
  222.       }
  223.       if (Found==false) {
  224.         player.midiNoteOff(0, KeyToNote(Current[Index]), 127);
  225.         Serial.print("Off ");
  226.         Serial.println(KeyToNote(Last[Index]),HEX);
  227.       }
  228.      }
  229.     }
  230.    for (Index=0;Index<10;Index++) {Last[Index]=Current[Index];}
  231. }</i></i>
复制代码

VS1053B 用来播放MIDI是很好的选择,这次使用的是微雪出品的Music Shield 价格在90左右,淘宝最低价格有35的,我觉得应该同样能够使用。


工作的视频

完整的代码和用的库

下载附件MusicShield.zip

代码
下载附件MusicKB.zip

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

项目不错啊。
回复

使用道具 举报

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
能脱离电脑播放么

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail