多年来,我一直使用Windows的高级MIDI界面在游戏中播放MIDI文件作为背景音乐。现在我从几个升级到Windows 10的人那里听说,背景音乐第一次会播放(在启动程序后),但一旦它完成,它就无法再次启动(或开始播放下一个MIDI文件)。我自己还没有安装Windows 10,但我已经组装了几个调试程序,当程序第二次尝试启动MIDI文件播放时,在PlayMusic()中调用:
mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);
返回值343 (MCIERR_SEQ_NOMIDIPRESENT)。但是,如果程序退出并再次启动,则可以使后台MIDI再次播放。
我有两个函数:PlayMusic(char *fname)和StopMusic(),它们启动和停止MIDI文件播放,以及一个全局变量MusicPlaying,它跟踪音乐文件当前是否正在播放。然后,主窗口处理程序处理MM_MCINOTIFY消息,当接收到mci_notify_success时,它向我的主代码排队通知音乐完成,然后主代码最终将对PlayMusic()进行另一个调用(如果它想的话)以再次启动文件播放。下面是代码块(MusicPlaying是全局变量,它知道MIDI文件是否处于活动状态):
//*********************************************************
void StopMusic() {
if( MusicPlaying ) {
mciSendCommand(MCIwDeviceID, MCI_STOP, MCI_WAIT, 0);
mciSendCommand(MCIwDeviceID, MCI_CLOSE, MCI_WAIT, 0);
MusicPlaying = 0;
}
}
//**********************************************************************
void PlayMusic(char *pMem) {
MCIERROR dwReturn;
StopMusic(); // stop any previously playing music
// BuildPath() just adds the appropriate folder info for the file
BuildPath(pMem, DIR_MUSIC, FALSE);
// Open the device by specifying the device name and device element.
// MCI will attempt to choose the MIDI Mapper as the output port.
mciOpenParms.dwCallback = 0;
mciOpenParms.wDeviceID = 0;
mciOpenParms.lpstrDeviceType = "sequencer";//NULL;
mciOpenParms.lpstrElementName = (TCHAR *)TmpPath;
mciOpenParms.lpstrAlias = NULL;
dwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_WAIT | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &mciOpenParms);
if( dwReturn ) return; // Failed to open device, bail out
// Begin playback. The window procedure function for the parent window
// will be notified with an MM_MCINOTIFY message when playback is
// complete. At that time, the window procedure closes the device.
MCIwDeviceID = mciOpenParms.wDeviceID;
mciPlayParms.dwCallback = (DWORD)(hWndMain);
dwReturn = mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);
if( dwReturn ) { // if error
mciSendCommand(MCIwDeviceID, MCI_CLOSE, 0, 0); // close MCI device
return; // and bail out
}
MusicPlaying = 1;
}
和在主窗口消息处理器中,当MIDI文件播放完成时:
case MM_MCINOTIFY:
//***** SEE "NOTE" BELOW FOR DEBUG CODE INSERTED HERE *****
// various MIDI messages, we only care about termination
switch( wParam ) {
case MCI_NOTIFY_ABORTED: // value of 4
case MCI_NOTIFY_FAILURE:
case MCI_NOTIFY_SUPERSEDED:
break;
case MCI_NOTIFY_SUCCESSFUL: // value of 1
mciSendCommand(MCIwDeviceID, MCI_CLOSE, 0, 0);
MusicPlaying = 0;
AddMsg(KHDR_MUSIC_DONE, 0, 0, 0); // Queue msg that music finished
break;
default:
break;
}
return 0;
事件的正常顺序是:
Main game code calls PlayMusic() to start a MIDI playing
(Nothing in StopMusic() since nothing's playing first time)
MCI_OPEN
MCI_PLAY
MusicPlaying = 1;
MainWnd MM_MCINOTIFY MCI_NOTIFY_SUCCESSFUL when MIDI finished
MCI_CLOSE
MusicPlaying = 0;
queue KHDR_MUSIC_DONE msg to main game code
Main game code eventually sends another PlayMusic() command
如果某些内容导致主游戏代码过早地停止音乐,那么它将调用StopMusic(),这将:
MCI_STOP
MCI_CLOSE
MusicPlaying = 0;
注意:(参见上面MM_MCINOTIFY中的"**** See 'NOTE'…"行)作为调试的一部分,我在此时插入了一个MessageBox调用来显示正在接收的通知。win10的用户看到的消息与我在早期版本的Windows上看到的完全相同:当成功发生时,wParam=1和lParam=1,当ABORTED发生时,wParam=4和lParam=1。但这里有一个问题:此时出现了MessageBox,然后当用户单击MessageBox上的OK时,MIDI文件就会重新启动!我的第一个想法是,这是MessageBox出现和被点击的时间延迟,给了MIDI系统时间来"重置"。但是在进一步的测试中,插入代码将"MUSIC_DONE"消息排队到主代码的时间延迟2秒,对问题没有任何影响。因此,这似乎可能与上下文切换有关,从我的主窗口切换到MessageBox窗口并返回,而不是任何时间延迟。
我发现一个网页,谈论发生在Windows 8上的MIDI系统的变化,这可能与它有关,除了我的MIDI播放代码似乎没有问题播放第一个文件,它只是拒绝播放任何后续的。该页位于:
http://coolsoft.altervista.org/en/blog/2013/03/what-happened-midi-mapper-windows-8我也听到另一个开发者的游戏程序也有同样的问题:它会播放一次背景音乐,然后MIDI系统似乎打嗝。
那么,一个大问题:有没有人知道需要做些什么才能让Windows 10流畅地播放顺序MIDI文件?
首先,我强烈建议您使用win10系统进行调试,即使它是在VM中。因为win10是免费升级,所以这应该不是问题。
当你调用mciSendCommand(…, MCI_CLOSE,…),请在第三个参数中指定MCI_WAIT。0在这里是无效的。也许在win10上,这会导致你的代码没有等到Close完成。