MIDI音乐编程那些事儿


前言

新冠疫情期间没事儿,玩起了音乐和电脑。因为正在利用老旧电子琴学弹 “我爱你中国” (简谱的眼睛),手上较为完整的曲谱却是五线谱,无奈!把它翻译成简谱。本来想通过电脑帮我把输入的五线谱转换成指定大调的简谱(转成不同调的简谱,这do re me 就不一样了,手工转换很烧脑的),这不需要MIDI的。可是,后来想让电脑在每转换好一个音后立刻把音发出来,然后就有了下面的文章。

一、Windows系统的 win32 API MIDI函数

在Windows系统中要演奏MIDI音乐,可以通过API函数进行。我们要做的只是调用它们,并通过它们发送MIDI消息给Windows,其余的事都由Windows在后台完成。为了要调用API函数,首先要做的就是获取这些MIDI函数,并了解它们的参数要求及其返回值。基本的MIDI函数有3个:midiOutOpen 、MidiOutShortMsg、MidiOutClose,分别用来打开MIDI设备、操作MIDI设备和关闭MIDI设备。

1、win32 API MIDI函数的获取

Windows的MIDI函数在系统动态链接库winmm.dll中。在Borland C++ builder环境中代码如下:
注:下面的代码结合了多位作者的代码示例才启发了我,并且在时间上也持续了2-3天,特别是函数的转换上。所以这里无法给出具体作者,如有作者向我指出,我会加上其引用。

//在获取函数地址前,先说明其在相应开发环境中的函数原型
int __stdcall (*MidiOutOpen)(int &hMidiOut, int uDeviceID,int dwCallBack,int dwInstance,int dwFlags);
int __stdcall (*MidiOutClose)(int hMidiOut );
int __stdcall (*MidiOutShortMsg)(int hMidiOut, int dwMsg);


HINSTANCE h=NULL;
int MidiOut=0;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    
    
     h=::LoadLibrary("winmm.dll");	//载入动态链接库,LoadLibrary函数会自动定位其所在目录
     if(NULL != h) 
     {
    
    
          MidiOutOpen= (int __stdcall (*)(int &, int,int,int,int)) GetProcAddress(h,"midiOutOpen");
          if( MidiOutOpen==NULL) ShowMessage( "'midiOutOpen' not found in winmm.dll " );

          MidiOutClose = (int __stdcall (*)(int)) GetProcAddress(h,"midiOutClose")   ;
          if( MidiOutClose ==NULL  )  ShowMessage( "'midiOutClose' not found in winmm.dll " );

          MidiOutShortMsg=(int __stdcall (*)(int , int )) GetProcAddress(h,"midiOutShortMsg") ;
          if( MidiOutShortMsg ==NULL  )  ShowMessage( "'midiOutShortMsg' not found in winmm.dll " );

          MidiOutOpen(MidiOut,-1,0,0,0);     //打开MIDI设备
          //MidiOutShortMsg(MidiOut,0x7BB0);   //停止演奏
          //MidiOutShortMsg(MidiOut,0xC0);     //默认乐器为钢琴
     }
     else 
          ShowMessage( "winmm.dll not found" ) ;
}

以上代码解释如下:LoadLibrary函数是windows函数,用来载入winmm.dll动态链接库到内存,并返回其句柄到h(h非空表示载入成功);GetProcAddress函数是windows API函数,用来从句柄h中获取指定名称的函数在相应DLL中的地址,我们对该指定名称的函数的调用实际上就是“程序跳转”到该地址,也就是说,该地址就是该函数。同样,GetProcAddress返回空表该DLL中没有指定名称的函数(这里严格区分大小写)。
  除了函数在DLL中的入口地址还不够,所以还要强制转换为与相应函数原型具有相同参数类型、参数顺序和返回值的调用地址,以便让系统知道调用时如何处理实参和返回值。如: MidiOutShortMsg=(int __stdcall (*)(int , int )) GetProcAddress(h,“midiOutShortMsg”);GetProcAddress获取地址后由(int __stdcall (*)(int , int )) 进行转换,这个转换器实际上是通过去除函数原型中的所有标识符后得到的。

在 Delphi 环境中代码如下:

  Function MidiOutOpen(
    var lphMidiOut:integer; uDeviceID,dwCallback,dwInstance,  dwFlags:integer):integer;
    stdcall;far; External 'winmm.dll';
  Function MidiOutClose(hMidiOut:integer):integer; stdcall;far; External 'winmm.dll';
  Function MidiOutShortMsg(hMidiOut,dwMsg:integer):integer;stdcall;far; External 'winmm.dll';

2、MIDI函数的参数和返回值

参考上面的三个函数原型,顾名思义:MidiOutOpen是打开MIDI输出设备、MidiOutClose是关闭MIDI输出设备,分别在开始前和结束后一次性调用;MidiOutShortMsg是向MIDI输出设备发短消息。
三个函数只有一个地方是特别的,即MidiOutOpen的第一个参数 int &hMidiOut(Delphi中为 var lphMidiOut:integer),这个参数必须传地址,通过它带回MIDI输出设备的句柄,以后关闭时也关闭此句柄,发短消息也是针对此句柄,所以一般情况下,保存这个句柄的变量应该为全局变量。
三个函数执行成功则返回值0,否则返回错误代码。
三个函数的调用方式为:
int MidiOut=0;
MidiOutOpen(MidiOut,-1,0,0,0); //打开MIDI设备,成功则MidiOut为MIDI设备的句柄,
//否则不要使用MidiOut变量,它是没有意义的。
MidiOutShortMsg(MidiOut, msg); //发MIDI短消息到MIDI设备MidiOut
MidiOutClose(MidiOut); //关闭MIDI设备MidiOut


二、MIDI短消息函数的使用

发MIDI短消息函数MidiOutShortMsg(MidiOut, int msg)中MIDI消息msg,是一个4字节的整数,格式有点复杂。下面内容中的通道概念是受到了一位可能是台湾的朋友1的启发,经本人严格测试后对其进行了修正。

1、midiOutShortMsg函数的参数格式

midiOutShortMsg( HMIDIOUT hmo,DWORD dwMsg ) 短消息参数dwMsg的使用格式:

dwMsg是4字节无符号整数,分成4个部分,每个部分1个字节:

字节 作用
最高字节(第3字节) 基本不用
字节2 (第2字节) 音量0-0x7F或不用
字节1 (第1字节) 音阶0-0x7F或音色
最低字节(第0字节) 命令或状态、通道
第0字节16进制高位 命令或状态0x9/0xC、0xB
第0字节16进制低位 通道0-0xF

具体格式如下(16进制):

字节3 字节2 字节1 字节0 功能
00 - 音色值 CN 为通道N指定一个音色值,即选择一个乐器,乐器有128种(见下表)
00 音量值 音阶值 9N 在通道N上,使用指定音量,演奏一个即定乐器的指定音阶
00 - 0x7B BN 停止通道N上的演奏

注:经过测试“停止演奏”消息还可以是0x78BN、0x7EBN和0x7FBBN

作为补充说明举例如下:
设置消息(CN):
midiOutShortMsg(hMidi,16<<8 | 0xC0) //设置通道0的乐器为 风琴
midiOutShortMsg(hMidi, 0<<8 | 0xC1) //设置通道1的乐器为 大钢琴
midiOutShortMsg(hMidi,24<<8 | 0xC7) //设置通道7的乐器为 尼龙弦吉他
midiOutShortMsg(hMidi,40<<8 | 0xCA) //设置通道10的乐器为 小提琴
最后一行也可以:
midiOutShortMsg(hMidi,40 * 0x100 + 0xCA) //设置通道10的乐器为 小提琴

演奏消息(9N):
midiOutShortMsg(
hMidi,0x7F<<16 | 60<<8| 0x9A) //在通道10上以最高音量(0x7F)弹一个中央C(音高60,音名c1)
midiOutShortMsg(
hMidi,0x40<<16 | 72<<8| 0x93) //在通道3上以半高音量(0x40)弹一个高音do(音高72,音名c2)
midiOutShortMsg(
hMidi,0x40<<16 | 76<<8| 0x94) //在通道4上以半高音量(0x40)弹一个高音me(音高76,音名e2)
最后一行也可以:
midiOutShortMsg(hMidi,0x40 * 0x10000 + 76 * 0x100 + 0x94) //在通道4上以半高音量(0x40)弹一个高音me(音高76,音名e2)

停止[演奏]消息(BN):
midiOutShortMsg(hMidi, 0x7BB7) //停止通道7上的演奏
midiOutShortMsg(hMidi, 0x7BB3) //停止通道3上的演奏



MIDI乐器编号(音色值): 2
0 Acoustic Grand Piano 大钢琴(声学钢琴)
1 Bright Acoustic Piano 明亮的钢琴
2 Electric Grand Piano 电钢琴
3 Honky-tonk Piano 酒吧钢琴
4 Rhodes Piano 柔和的电钢琴
5 Chorused Piano 加合唱效果的电钢琴
6 Harpsichord 羽管键琴(拨弦古钢琴)
7 Clavichord 科拉维科特琴(击弦古钢琴)

8 Celesta 钢片琴
9 Glockenspiel 钟琴
10 Music box 八音盒
11 Vibraphone 颤音琴
12 Marimba 马林巴
13 Xylophone 木琴
14 Tubular Bells 管钟
15 Dulcimer 大扬琴

16 Hammond Organ 击杆风琴
17 Percussive Organ 打击式风琴
18 Rock Organ 摇滚风琴
19 Church Organ 教堂风琴
20 Reed Organ 簧管风琴
21 Accordian 手风琴
22 Harmonica 口琴
23 Tango Accordian 探戈手风琴

24 Acoustic Guitar (nylon) 尼龙弦吉他
25 Acoustic Guitar (steel) 钢弦吉他
26 Electric Guitar (jazz) 爵士电吉他
27 Electric Guitar (clean) 清音电吉他
28 Electric Guitar (muted) 闷音电吉他
29 Overdriven Guitar 加驱动效果的电吉他
30 Distortion Guitar 加失真效果的电吉他
31 Guitar Harmonics 吉他和音

32 Acoustic Bass 大贝司(声学贝司)
33 Electric Bass(finger) 电贝司(指弹)
34 Electric Bass (pick) 电贝司(拨片)
35 Fretless Bass 无品贝司
36 Slap Bass 1 掌击Bass 1
37 Slap Bass 2 掌击Bass 2
38 Synth Bass 1 电子合成Bass 1
39 Synth Bass 2 电子合成Bass 2

40 Violin 小提琴
41 Viola 中提琴
42 Cello 大提琴
43 Contrabass 低音大提琴
44 Tremolo Strings 弦乐群颤音音色
45 Pizzicato Strings 弦乐群拨弦音色
46 Orchestral Harp 竖琴
47 Timpani 定音鼓

48 String Ensemble 1 弦乐合奏音色1
49 String Ensemble 2 弦乐合奏音色2
50 Synth Strings 1 合成弦乐合奏音色1
51 Synth Strings 2 合成弦乐合奏音色2
52 Choir Aahs 人声合唱“啊”
53 Voice Oohs 人声“嘟”
54 Synth Voice 合成人声
55 Orchestra Hit 管弦乐敲击齐奏

56 Trumpet 小号
57 Trombone 长号
58 Tuba 大号
59 Muted Trumpet 加弱音器小号
60 French Horn 法国号(圆号)
61 Brass Section 铜管组(铜管乐器合奏音色)
62 Synth Brass 1 合成铜管音色1
63 Synth Brass 2 合成铜管音色2

64 Soprano Sax 高音萨克斯风
65 Alto Sax 次中音萨克斯风
66 Tenor Sax 中音萨克斯风
67 Baritone Sax 低音萨克斯风
68 Oboe 双簧管
69 English Horn 英国管
70 Bassoon 巴松(大管)
71 Clarinet 单簧管(黑管)

72 Piccolo 短笛
73 Flute 长笛
74 Recorder 竖笛
75 Pan Flute 排箫
76 Bottle Blow [中文名称暂缺]
77 Shakuhachi 日本尺八
78 Whistle 口哨声
79 Ocarina 奥卡雷那

80 Lead 1 (square) 合成主音1(方波)
81 Lead 2 (sawtooth) 合成主音2(锯齿波)
82 Lead 3 (caliope lead) 合成主音3
83 Lead 4 (chiff lead) 合成主音4
84 Lead 5 (charang) 合成主音5
85 Lead 6 (voice) 合成主音6(人声)
86 Lead 7 (fifths) 合成主音7(平行五度)
87 Lead 8 (bass+lead)合成主音8(贝司加主音)

88 Pad 1 (new age) 合成音色1(新世纪)
89 Pad 2 (warm) 合成音色2 (温暖)
90 Pad 3 (polysynth) 合成音色3
91 Pad 4 (choir) 合成音色4 (合唱)
92 Pad 5 (bowed) 合成音色5
93 Pad 6 (metallic) 合成音色6 (金属声)
94 Pad 7 (halo) 合成音色7 (光环)
95 Pad 8 (sweep) 合成音色8

96 FX 1 (rain) 合成效果 1 雨声
97 FX 2 (soundtrack) 合成效果 2 音轨
98 FX 3 (crystal) 合成效果 3 水晶
99 FX 4 (atmosphere) 合成效果 4 大气
100 FX 5 (brightness) 合成效果 5 明亮
101 FX 6 (goblins) 合成效果 6 鬼怪
102 FX 7 (echoes) 合成效果 7 回声
103 FX 8 (sci-fi) 合成效果 8 科幻

104 Sitar 西塔尔(印度)
105 Banjo 班卓琴(美洲)
106 Shamisen 三昧线(日本)
107 Koto 十三弦筝(日本)
108 Kalimba 卡林巴
109 Bagpipe 风笛
110 Fiddle 民族提琴
111 Shanai 山奈

112 Tinkle Bell 叮当铃
113 Agogo [中文名称暂缺]
114 Steel Drums 钢鼓
115 Woodblock 木鱼
116 Taiko Drum 太鼓
117 Melodic Tom 通通鼓
118 Synth Drum 合成鼓
119 Reverse Cymbal 铜钹

120 Guitar Fret Noise 吉他换把杂音
121 Breath Noise 呼吸声
122 Seashore 海浪声
123 Bird Tweet 鸟鸣
124 Telephone Ring 电话铃
125 Helicopter 直升机
126 Applause 鼓掌声
127 Gunshot


音阶值: 3

序号 音阶值 音名 序号 音阶值 音名
1 21 A2
2 22 A2#
3 23 B2
4 24 C1 52 72 c2
5 25 C1# 53 73 c2#
6 26 D1 54 74 d2
7 27 D1# 55 75 d2#
8 28 E1 56 76 e2
9 29 F1 57 77 f2
10 30 F1# 58 78 f2#
11 31 G1 59 79 g2
12 32 G1# 60 80 g2#
13 33 A1 61 81 a2
14 34 A1# 62 82 a2#
15 35 B1 63 83 b2
16 36 C 64 84 c3
17 37 C# 65 85 c3#
18 38 D 66 86 d3
19 39 D# 67 87 d3#
20 40 E 68 88 e3
21 41 F 69 89 f3
22 42 F# 70 90 f3#
23 43 G 71 91 g3
24 44 G# 72 92 g3#
25 45 A 73 93 a3
26 46 A# 74 94 a3#
27 47 B 75 95 b3
28 48 c 76 96 c4
29 49 c# 77 97 c4#
30 50 d 78 98 d4
31 51 d# 79 99 d4#
32 52 e 80 100 e4
33 53 f 81 101 f4
34 54 f# 82 102 f4#
35 55 g 83 103 g4
36 56 g# 84 104 g4#
37 57 a 85 105 a4
38 58 a# 86 106 a4#
39 59 b 87 107 b4
40 60 c1标准键盘中央C 88 108 c5
41 61 c1#
42 62 d1
43 63 d1#
44 64 e1
45 65 f1
46 66 f1#
47 67 g1
48 68 g1#
49 69 a1
50 70 a1#
51 71 b1

2、midiOutShortMsg函数中的命令说明

关于通道的说明:
0-8,0xA,0xB共10个通道:正常发声
9:这个通道,各个音阶值对应的不是不同音高的音,而是各种打击乐声或哨声
0xC-0xF:这2个通道发出的声音持续时间较正常音短(键盘和打击类而言),在演奏钢琴时就像没有踏下踏板一样

关于音色的说明:
对于风管类(靠吹气吹风发声的)、弓弦类(靠弓拉弦发声的)音色,一旦奏响就不会停下来,必须发出0x7BBN短消息来停止它;对于打击类、键盘类和弹拨类音色,在奏响后音量会渐渐变小直到停下来(也可能是也没有停下来,只是音量极小听不到),所以一般情况下演奏和停止消息要成对发出,就像括号一样,左括号出现后必须出现右括号。但在有些情况下可以不使用或不能使用停止消息,如在通道0上用钢琴弹奏一个和音时,做法就是连续发出3到4个演奏消息,让这些音在1个通道上混响,需要停止该和音时,只要在该通道上发出单个停止消息即可。这个例子也说明:在同一个通道上是可以发出N多个音阶的,就像相同乐器的多重奏。



  1. 一位可能是台湾朋友问何起的文章 link ↩︎

  2. 音色(乐器)表参考自10年前的网络文章,暂无法溯源 ↩︎

  3. 音阶表参考自本平台韩曙亮的文章 link ↩︎

猜你喜欢

转载自blog.csdn.net/zhongzg/article/details/124256818