键盘乐器
上小学的时候,音乐课对于五音不全的我来说是痛苦的存在。好在老师并不好意思给一个勤奋听话的孩子不及格,所以我音乐课期末成绩通常都是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
但实际演奏效果怎么,最好有视频
工作的视频https://zhuanlan.zhihu.com/p/103840801 项目不错啊。{:6_214:} 楼主强大,多谢分享。 能脱离电脑播放么 gada888 发表于 2020-2-21 16:47
能脱离电脑播放么
可以啊,这个就是不用电脑直接放出来声音的 想问一下这个 可以接USB口的那种MIDI键盘演奏吗?还是要换别的芯片? 蟹蟹泳裤 发表于 2024-8-29 12:02
想问一下这个 可以接USB口的那种MIDI键盘演奏吗?还是要换别的芯片?
不可以的,这个没有做 USB MIDI 设备的解析
页:
[1]