2007年12月17日星期一

MIDI编程

本节先介绍使用MCI播放MIDI文件的方法,然后介绍MMAPI中的基本MIDI函数,最后通过一个虚拟电子琴的实例来演示MIDI的功能。
12.5.1 用MCI播放MIDI文件
MIDI所对应的MCI设备名为sequencer(音序器),似12.3节,也可以使用MCI的命令串方式:
mciSendString(命令串, NULL, 0, NULL);
来打开MIDI设备和播放MIDI文件。
打开MIDI设备的命令串常用格式为:
open MIDI文件路径 alias 别名 type sequencer
播放MIDI文件的命令串一般格式为:
play 别名 from 位置1 to 位置2
位置的单位为毫秒。若无“from 位置1”则从头播放,若无“to 位置2”则播放到文件尾。如“play 别名”是从头到尾播放。
例如:
CString str;
str += L"open \"" + ar.GetFile()->GetFilePath() + L"\" alias midi type sequencer";
wchar_t buf[256];
MCIERROR err = mciSendString(str, NULL, 0, NULL); // 打开MIDI文件
if (err) {
if (mciGetErrorString(err, buf, 256))
MessageBox(NULL, buf, L"Error", MB_OK); // 获得并显示错误串
return;
}
mciSendString(L"play midi", NULL, 0, NULL); // 播放MIDI文件
其他常用的命令还有:暂停播放(pause 别名)、恢复播放(resume 别名)、停止播放(stop 别名)、关闭设备(close 别名)等。
12.5.2 MMAPI中的基本MIDI函数
在12.4.2中已经列出了MMAPI中含有的41个MIDI函数的名称,下面将介绍其中常用的一些基本MIDI函数的功能、原型和参数。

 查询MIDI设备
可以利用midiOutGetNumDevs和midiOutGetDevCaps函数来查询当前系统的MIDI设备和性能:
UINT midiOutGetNumDevs(VOID); // 返回MIDI输出设备的数目(非0表示支持MIDI)
MMRESULT midiOutGetDevCaps( // 检查MIDI输出设备的性能
UINT_PTR uDeviceID, // 设备ID(0 ~ 设备数-1)
LPMIDIOUTCAPS lpMidiOutCaps, // 指向MIDI输出设备的性能结构的指针
UINT cbMidiOutCaps // 性能结构的大小(字节数)
); // 成功时返回MMSYSERR_NOERROR(=0)
typedef struct { // MIDI输出设备的性能结构
WORD wMid; // 厂商ID
WORD wPid; // 产品ID
MMVERSION vDriverVersion; // 驱动程序版本号(高/低位字节分别为主/次版本号)
CHAR szPname[MAXPNAMELEN]; // 产品名称
WORD wTechnology; // 设备类型
WORD wVoices; // 同时音色数
WORD wNotes; // 复音数
WORD wChannelMask; // 通道数
DWORD dwSupport; // 支持的可选功能
} MIDIOUTCAPS;
其中,typedef UINT MMVERSION;,设备类型wTechnology的取值见表12-7:
表12-7 MIDI设备类型
==============================================
wTechnology 数值 设备类型
MOD_MIDIPORT 1 MIDI硬件端口
MOD_SYNTH 2 合成器
MOD_SQSYNTH 3 方波合成器
MOD_FMSYNTH 4 调频合成器
MOD_MAPPER 5 微软MIDI映射器
MOD_WAVETABLE 6 硬件波表合成器
MOD_SWSYNTH 7 软件合成器
=============================================
支持的可选功能dwSupport的取值可为表12-8中值的组合。
======================================================
dwSupport 数值 功能
MIDICAPS_VOLUME 1 支持音量控制
MIDICAPS_LRVOLUME 2 支持分离的左右音量控制
MIDICAPS_CACHE 4 支持音色(patch)缓冲
MIDICAPS_STREAM 8 提供对midiStreamOut函数的直接支持
========================================================
例如:(查询并输出MIDI设备的性能)
UINT dn = midiOutGetNumDevs();
if (dn == 0) MessageBox(L"No MIDI device!", L"Error");
else { // dn > 0
CString str;
MIDIOUTCAPS moc;
for (UINT i = 0; i < dn; i++) {
if (midiOutGetDevCaps(i, &moc, sizeof(moc)) == MMSYSERR_NOERROR) {
str.Format(L"DevNum = %d, M-ID = %d, P-ID = %d, Ver = %.4X, \
P-Name = %s, Tech = %d, Voices = %d, Notes = %d, Channels = %d, Support = %lX",
i, moc.wMid, moc.wPid, moc.vDriverVersion,moc.szPname,
moc.wTechnology, moc.wVoices, moc.wNotes, moc.wChannelMask,
moc.dwSupport);
pDC->TextOut(10, 10 + i*20, str);
}
}
输出结果如:


例如,其中设备号为1的MIDI设备:制造商ID为1(创新公司)、产品ID为102、版本号为5.10、产品名为SB Live! MIDI Synth、采用硬件波表合成器技术、可同时有32种音色和32个复音、有64K个通道、并支持对[主]音量和左右音量的控制。

 播放MIDI数据
为了播放MIDI数据,必须首先调用函数midiOutOpen打开指定序号的MIDI输出设备,利用它所返回的设备句柄指针,再来调用函数midiOutPrepareHeader来准备信息头缓冲区,然后才可以调用函数midiOutLongMsg来播放MIDI数据。
在完成播放后,还必须调用函数midiOutUnprepareHeader来清除信息头缓冲区,最后调用函数midiOutClose来关闭MIDI输出设备。
在播放MIDI数据的过程中,还可以调用函数midiOutReset来停止播放。
这里所用的MIDI数据,可以是利用MMIO函数获取的MIDI文件中的数据,也可以是用户自己动态创建的定制MIDI数据。

下面是这些函数的原型和参数含义:
MMRESULT midiOutOpen( // 打开MIDI输出设备,成功返回MMSYSERR_NOERROR
LPHMIDIOUT lphmo, // 设备句柄指针(用于返回值)
UINT_PTR uDeviceID, // 设备ID(一般取为MIDI_MAPPER[=1])
DWORD_PTR dwCallback, // 回调函数指针(一般取为NULL[0])
DWORD_PTR dwCallbackInstance, // 回调函数使用的数据(一般取为NULL[0])
DWORD dwFlags // 打开设备标志(用于回调函数参数的类型,(一般取为0)
);
MMRESULT midiOutPrepareHeader( // 准备输出MIDI音频用的信息头
HMIDIOUT hmo, // (由midiOutOpen获得的)设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
typedef struct { // MIDI信息头结构
LPSTR lpData; // 指向MIDI数据缓冲区的指针
DWORD dwBufferLength; // 缓冲区的大小
DWORD dwBytesRecorded; // 录音时缓冲区中数据的实际数量
// 必须≤dwBufferLength
DWORD_PTR dwUser; // 用户定制的数据
DWORD dwFlags; // 关于缓冲区信息的标志
struct midihdr_tag far * lpNext;
DWORD_PTR reserved;
DWORD dwOffset;
DWORD_PTR dwReserved[4];
} MIDIHDR;
MMRESULT midiOutLongMsg( // 发送系统专用的MIDI消息到指定的MIDI输出设备
HMIDIOUT hmo, // 设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
MMRESULT midiOutUnprepareHeader( // 清除输出MIDI音频用的信息头
HMIDIOUT hmo, // 设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
MMRESULT midiOutReset( // 停止播放
HMIDIOUT hmo // 设备句柄指针
);
MMRESULT midiOutClose( // 关闭MIDI输出设备
HMIDIOUT hmo // 设备句柄指针
);

 发送短消息
在打开MIDI输出设备后,可以调用函数midiOutShortMsg来发送短消息,从而能够动态控制MIDI输出设备和播放乐音。
该函数的原型为:
MMRESULT midiOutShortMsg( // 发送短MIDI消息到指定的MIDI输出设备
HMIDIOUT hmo, // 设备句柄指针
DWORD dwMsg // MIDI消息
);
其中,MIDI消息为4字节无符号整数,其高字的高位字节保留为0、低位字节为MIDI数据的第2个字节,低字的高位字节MIDI数据的第1个字节、低位字节为MIDI的状态。因为在Intel CPU中,多字节整数的低位字节在前,所以实际的MIDI消息的字节顺序见图12-4。
====================================================
字节序号: 0 1 2 3
字节内容: 状态 数据1 数据2 保留为0
==================================================
图12-4 MIDI消息的字节
例如:
union {
DWORD dwData;
BYTE bData[4];
} midi;
midi.bData[0] = status;
midi.bData[1] = data1;
midi.bData[2] = data2;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
至于MIDI消息的具体内容,会在下面的12.6.3小节中进行介绍。

 其他常用函数
MMRESULT midiOutSetVolume( // 设置MIDI输出设备的音量
HMIDIOUT hmo, // 设备句柄指针
DWORD dwVolume // 低/高字为左[单]/右声道的音量 = 0 ~ 0xFFFF
);
MMRESULT midiOutGetVolume( // 获取MIDI输出设备的当前音量
HMIDIOUT hmo, // 设备句柄指针
LPDWORD lpdwVolume // 指向音量双字变量的指针
);

上面只是简单的介绍了对MIDI输出设备进行操作的一些基本MMAPI函数,还有对MIDI输入设备和MIDI流设备进行操作的各种MMAPI函数。有兴趣的读者,可以参看Visual Studio的帮助文档。
2.5.3 MIDI消息
MIDI应用程序,通过调用MMAPI函数midiOutShortMsg和midiOutLongMsg,向MIDI设备发送各种消息,来使用和控制MIDI设备、播放MIDI文件和输出MIDI数据。参见图12-5



MIDI消息分为两大类:通道消息和系统消息(参见图12-6),前者由函数midiOutShortMsg发送,后者则由函数midiOutLongMsg来发送。最常用的是通道声音消息中的音符开/关和程序变换(选择音色/乐器种类)消息。



图12-6 MIDI消息的分类
MIDI消息由若干(2~3个)字节组成,第一个字节为状态字节,后跟1~2个数据字节。为了能区分状态字节和数据字节,MIDI规定状态字节的高位为1(即值≥128[0x80]),而各数据字节的高位为0(即值<128)。
对通道声音消息,状态字节的高4位用来表示种类(8~E)、而其低4位则用来表示通道序号(n = 0~F/15)。表12-9为MIDI通道声音消息的状态字节类型和所对应的数据字节的个数和含义。
========================================
功能描述 状态字节 数据字节
英文 中文 (n = 0~F) 数量 含义
Note Off 音符关 0x8n 2 1:音高、2:速率(音量)
Note On 音符开 0x9n 2 1:音高、2:速率(音量)
Key Pressure 按键力度 0xAn 2 1:音高、2:速率(音量)
Control Change 控制改变 0xBn 2 1:控制器编号、2:控制值
Program Change 程序改变 0xCn 1 1:程序编号(音色/乐器种类)
Channel Pressure 通道力度 0xDn 1 1:压力值
Pitch Bend 音高弯度 0xEn 2 1:粗调、2:微调
========================================
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
 0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
 通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
 音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
 音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
 0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
 0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
 0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
 通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
 音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
 音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
 0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
 0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
 0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
 通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
 音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
 音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
 0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
 0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
 0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
 通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
 音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
 音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
 0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
 0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
表12-10 GM旋律乐器音色编号
分组 序号 英文名 中文名
Piano
================钢琴===============
0 Acoustic Grand Piano [非电声]三角钢琴
1 Bright Acoustic Piano 明亮[非电声]钢琴
2 Electric Grand Piano 电三角钢琴
3 Honky-Tonk Piano 酒吧钢琴
4 Electric Piano 1 (Rhodes) 电钢琴1(罗得斯/柔和)
5 Electric Piano 2 (Chorused) 电钢琴2(合唱电钢琴)
6 Harpsichord 大键琴(拨弦古钢琴)
7 Clavichord 翼琴(击弦古钢琴)
Acoustic
Percussion
=============[非电]打击乐器 ============
8 Celesta 钢片琴
9 Glockenspiel 钟琴
10 Music Box 八音盒
11 Vibraphone 颤音琴
12 Marimba 马林巴琴
13 Xylophone 木琴
14 Tubular Bells 管钟
15 Dulcimer 洋琴
Organ
==================风琴=================
16 Drawbar(Hammond) Organ 拉杆式(哈蒙德)管风琴
17 Percussive Organ 敲击式管风琴
18 Rock Organ 摇滚管风琴
19 Church Organ 教堂管风琴
20 Reed Organ 簧风琴
21 Accordion 手风琴
22 Harmonica 口琴
23 Tango Accordion 探戈口琴
Guitar
=================吉他=================
24 Nylon String Guitar 尼龙弦吉他
25 Steel String Guitar 钢弦吉他
26 Electric Jazz Guitar 电爵士吉他
27 Electric Clean Guitar 电清音吉他
28 Electric Muted Guitar 电闷音吉他
29 Overdriven Guitar 过激吉他
30 Distortion Guitar 失真吉他
31 Guitar Harmonics 吉他和声
Bass
===================贝司============
32 Acoustic Bass [非电声]贝司
33 Electric Bass(finger) 电贝司(指弹)
34 Electric Bass(pick) 电贝司(拨片)
35 Fretless Bass 无品贝司
36 Slap Bass 1 击弦贝司1
37 Slap Bass 2 击弦贝司2
38 Synth Bass 1 合成贝司1
39 Synth Bass 2 合成贝司2
Solo Strings
===============独奏弦乐器============
40 Violin 小提琴
41 Viola 中提琴
42 Cello 大提琴
43 Contrabass 低音大提琴
44 Tremolo Strings 颤音弦乐
45 Pizzicato Strings 弹拨弦乐
46 Orchestral Harp 竖琴
47 Timpani 定音鼓
Ensemble
=================合奏/合唱==============
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 人声“[u:]”
54 Synth Voice 合成人声
55 Orchestra Hit 管弦乐齐奏
Brass
===================铜管乐器==========
56 Trumpet 小号
57 Trombone 长号
58 Tuba 大号
59 Muted Trumpet 弱音小号
60 French Horn 法国号(圆号)
61 Brass Section 铜管乐合奏
62 SynthBrass 1 合成铜管乐1
63 SynthBrass 2 合成铜管乐2
Reed
============簧片乐器==============
64 Soprano Sax 高音萨克斯
65 Alto Sax 中音萨克斯
66 Tenor Sax 次中音萨克斯
67 Baritone Sax 低音萨克斯
68 Oboe 双簧管
69 English Horn 英国号
70 Bassoon 巴松(大管)
71 Clarinet 单簧管(黑管)
Pipe
============木管乐器==============
72 Piccolo 短笛
73 Flute 长笛
74 Recorder 竖笛
75 Pan Flute 排箫
76 Blown Bottle 吹瓶
77 Skakuhachi 日本笛
78 Whistle 口哨
79 Ocarina 洋埙
Synth Lead
===============合成导音================
80 Lead 1 (square) 导音1(方波)
81 Lead 2 (sawtooth) 导音2(锯齿波)
82 Lead 3 (calliope) 导音3(汽笛风琴)
83 Lead 4 (chiff) 导音4(夜莺)
84 Lead 5 (charang) 导音5(精灵)
85 Lead 6 (voice) 导音6(人声)
86 Lead 7 (fifths) 导音7(平行五度)
87 Lead 8 (bass+lead) 导音8(低音+导音)
Synth Pad
===================合成垫音=================
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(风声)
Synth Effects
================合成效果=================
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(科幻)
Ethnic
===================民族乐器==============
104 Sitar 西班达(印度)
105 Banjo 班卓琴(美洲)
106 Shamisen 三昧线(日本)
107 Koto 十三弦筝(日本)
108 Kalimba 卡林巴(非洲)
109 Bagpipe 风笛(苏格兰)
110 Fiddle 提琴
111 Shanai 山奈
Percussive
==================打击乐器================
112 Tinkle Bell 叮当铃
113 Agogo 摇摆舞铃
114 Steel Drums 钢鼓
115 Woodblock 木鱼
116 Taiko Drum 太科鼓
117 Melodic Tom 通通鼓
118 Synth Drum 合成鼓
119 Reverse Cymbal 铜钹
一Sound Eff2141265ects
===============声效=================
120 Guitar Fret Noise 吉他换把
121 Breath Noise 呼吸
122 Seashore 海浪
123 Bird Tweet 鸟鸣
124 Telephone Ring 电话铃
125 Helicopter 直升飞机
126 Applause 掌声
127 Gunshot 枪声

例如:
midi.bData[0] = 0xC0; // 设置通道0的音色/乐器种类
midi.bData[1] = 40; // 小提琴(缺省为0:三角钢琴)
midi.bData[2] = 0; // 未使用字节
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 设置通道0中的乐器为小提琴
12.5.4 虚拟电子琴实例
为了帮助大家理解MIDI消息的编程,本小节给出一个利用MIDI接口来模拟电子琴的简单VC实例的核心代码段。

 创建项目、修改配置
创建一个名为Midi的VC05的MFC单文档项目,在项目属性中添加对多媒体库winmm.lib链接。具体做法为:
在VS05中,选“项目/Midi属性”菜单项,打开项目的属性页窗口,先选“所有配置”,再选“配置属性/链接器/输入”项,在右边上部的“附加依赖项”栏的右边,键入winmm.lib(参见图12-7)后按“应用”钮,最后按“确定”钮关闭对话框。

图12-7 添加对多媒体库winmm.lib的链接
 包含多媒体头文件、添加类变量
在视图类CMidiView的头文件中,包含多媒体头文件,并添加若干类变量:
#include
#pragma once
class CMidiView : public CView {
……
protected:
UINT dn; // MIDI输出设备的数目
HMIDIOUT hmo; // MIDI输出设备的句柄
union { // MIDI消息联合
DWORD dwData;
BYTE bData[4];
} midi;
BYTE chann, patch, pitch, intens; // 通道、音色、音高、音强
DWORD vol; // [左右]音量
……
}

 初始化和清除
在视图类的构造函数中设置若干初值,如:
CMidiView::CMidiView() {
dn = 0;
chann = 0; // 通道0
patch = 40; // 小提琴
pitch = 60; // 中音1
intens = 127; // 最大音强
vol = 0xFFFFFFFFl; // 最大音量
}
在视图类的初始化函数OnInitialUpdate中,判断系统是否支持MIDI,并打开标准的MIDI输出设备(MIDI_MAPPER[=1]):
void CMidiView::OnInitialUpdate() {
CView::OnInitialUpdate();
dn = midiOutGetNumDevs(); // 获取MIDI设备数目
if (dn > 0) midiOutOpen(&hmo, MIDI_MAPPER, NULL, NULL, 0);
else MessageBox(L"No MIDI device!", L"Error"); // dn == 0
}
在视图类的析构函数中关闭MIDI输出设备:
CMidiView::~CMidiView() {
if (dn > 0) midiOutClose(hmo);
}

 设计对话框
可以自己设计若干对话框,让用户来选择MIDI通道、设置各通道的乐器种类和[左右]音量的大小等。

 添加键盘消息响应、播放/停止音符
为视图类CMidiView添加对Windows消息WM_KEYDOWN和WM_KEYUP的消息响应函数OnKeyDown和OnKeyUp。
在其中添加对用户按1~7键(对应的虚拟键值为0x31~0x37)的响应,并发送MIDI输出的短消息来播放乐音。设用户
 只按1~7键——播放中音1~7;
 在按1~7键的同时按住Shift键——播放高音 ;
 在按1~7键的同时按住Ctrl键——播放低音 ;
 在按1~7键的同时按住Caps Lock键——播放超高音 ;
 在按1~7键的同时按住左窗口键——播放超低音 。
在OnKeyDown函数中添加语句:
if (dn > 0) PlayStopPitch(0x90, nChar, nFlags); // play note
在OnKeyUp函数中添加语句:
if (dn > 0) PlayStopPitch(0x80, nChar, nFlags); // stop note
其中,PlayStopPitch是自己为视图类添加的成员函数:
void CMidiView::PlayStopPitch(BYTE status, UINT nChar, UINT nFlags) {
BYTE pitch0 = 60; // 初始音高(c1 = 中音1)
BYTE pitchs[7] = {0, 2, 4, 5, 7, 9, 11}; // 8度音阶(12个半音)
if (nChar >= 0x31 && nChar <= 0x37) { // paly or stop note
if (GetKeyState(VK_CAPITAL) & 1<<15) pitch0 += 24; // 高16度
else if (GetKeyState(VK_SHIFT) & 1<<15) pitch0 += 12; // 高8度
else if(GetKeyState(VK_CONTROL) & 1<<15) pitch0 -= 12; // 低8度
else if(GetKeyState(VK_LWIN) & 1<<15) pitch0 -= 24; // 低16度
midi.bData[0] = status + chann; // 播放/停止音符
midi.bData[1] = pitch0 + pitchs[nChar - 0x31]; // 音高
midi.bData[2] = intens; // 音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 发送短消息
}
}

 播放连续音阶
除了响应用户按键来播放音符外,还可以设计各种自动播放音符的方法,例如播放连续的音阶(音高从0到127)。
为此,可以给视图类添加类变量(循环音高)n和(老音高)pitch0,在某个菜单或按键的消息响应中,设置计时器来启动连续音阶的播放。如:
n = 0;
SetTimer(1, 1000, NULL);
并为视图类添加计时器消息的响应函数:
void CMidiView::OnTimer(UINT_PTR nIDEvent) {
BYTE pitchs[7] = {0, 2, 4, 5, 7, 9, 11};
if (n > 0) { // stop note
midi.bData[0] = 0x80;
midi.bData[1] = pitch0;
midi.bData[2] = 127;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
}
UINT pitch = 12 * (n / 7) + pitchs[n % 7];
if (pitch > 127) {KillTimer(1); return;} // stop timer
midi.bData[0] = 0x90; // play note
midi.bData[1] = pitch;
midi.bData[2] = 127;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
n++;
pitch0 = pitch;
CView::OnTimer(nIDEvent);
}

没有评论: