为什么 MIDI 音序器无法在 Windows 10 上第二次播放
Why does MIDI sequencer fail to play second time on Windows 10
多年来,我一直在使用 Windows 中的高级 MIDI 接口来播放 MIDI 文件作为游戏中的背景音乐。现在我从几个已经升级到 Windows 10 的人那里听说背景音乐会第一次播放(在启动程序后),但是一旦完成,它就无法再次启动(或启动下一个MIDI 文件播放)。我自己还没有 Windows 10 安装,但我已经将几个调试程序放在一起,程序第二次尝试开始播放 MIDI 文件时,在 PlayMusic() 中调用:
mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);
returns 值为 343 (MCIERR_SEQ_NOMIDIPRESENT)。但是如果程序退出再启动,就可以让背景MIDI重新播放了。
我有两个函数:启动和停止 MIDI 文件播放的 PlayMusic(char *fname) 和 StopMusic(),以及跟踪音乐文件当前是否正在播放的全局变量 MusicPlaying。然后,主 window 处理程序处理 MM_MCINOTIFY 消息,当收到 MCI_NOTIFY_SUCCESSFUL 时,它会向我的主代码排队通知音乐结束,然后主代码最终会另一个调用(如果需要)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;
}
并且在主 window 消息处理器中,当 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 调用以显示收到了哪些通知。使用 Win 10 的用户看到的消息与我使用早期版本 Windows 时看到的消息完全相同:当成功发生时 wParam=1 和 lParam=1,当 ABORTED 发生时 wParam=4 和 lParam=1。但问题在于:此时出现 MessageBox,然后当用户在 MessageBox 上单击 OK 时,MIDI 文件重新启动就好了!我的第一个想法是 MessageBox 出现和被点击的时间延迟给了 MIDI 系统时间 'reset'。但是进一步的测试,插入代码以延迟 "MUSIC_DONE" 消息到我的主代码的队列对问题没有影响。所以看起来这可能与从我的主 window 到 MessageBox window 的上下文切换有关,而不是任何时间延迟。
我发现了一个网页,它讨论了 Windows 8 上 MIDI 系统发生的变化,这可能与它有关,除了我的 MIDI 播放代码似乎没有问题播放第一个文件,它只是拒绝播放任何后续文件。该页面位于:
http://coolsoft.altervista.org/en/blog/2013/03/what-happened-midi-mapper-windows-8
我也听说另一个开发者的另一个游戏程序也有同样的问题:它会播放一次背景音乐,然后 MIDI 系统似乎打嗝。
那么,大问题:有谁知道需要做什么才能让 Windows 10 顺利播放顺序 MIDI 文件?
首先,我强烈建议您使用 Win 10 系统进行调试,即使它在 VM 中也是如此。由于Win 10是免费升级,所以这应该不是问题。
当您调用 mciSendCommand(..., MCI_CLOSE, ...) 时,请在第三个参数中指定 MCI_WAIT。 0 在这里无效。也许在 Win 10 上,这会导致您的代码不等到关闭完成。
多年来,我一直在使用 Windows 中的高级 MIDI 接口来播放 MIDI 文件作为游戏中的背景音乐。现在我从几个已经升级到 Windows 10 的人那里听说背景音乐会第一次播放(在启动程序后),但是一旦完成,它就无法再次启动(或启动下一个MIDI 文件播放)。我自己还没有 Windows 10 安装,但我已经将几个调试程序放在一起,程序第二次尝试开始播放 MIDI 文件时,在 PlayMusic() 中调用:
mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);
returns 值为 343 (MCIERR_SEQ_NOMIDIPRESENT)。但是如果程序退出再启动,就可以让背景MIDI重新播放了。
我有两个函数:启动和停止 MIDI 文件播放的 PlayMusic(char *fname) 和 StopMusic(),以及跟踪音乐文件当前是否正在播放的全局变量 MusicPlaying。然后,主 window 处理程序处理 MM_MCINOTIFY 消息,当收到 MCI_NOTIFY_SUCCESSFUL 时,它会向我的主代码排队通知音乐结束,然后主代码最终会另一个调用(如果需要)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;
}
并且在主 window 消息处理器中,当 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 调用以显示收到了哪些通知。使用 Win 10 的用户看到的消息与我使用早期版本 Windows 时看到的消息完全相同:当成功发生时 wParam=1 和 lParam=1,当 ABORTED 发生时 wParam=4 和 lParam=1。但问题在于:此时出现 MessageBox,然后当用户在 MessageBox 上单击 OK 时,MIDI 文件重新启动就好了!我的第一个想法是 MessageBox 出现和被点击的时间延迟给了 MIDI 系统时间 'reset'。但是进一步的测试,插入代码以延迟 "MUSIC_DONE" 消息到我的主代码的队列对问题没有影响。所以看起来这可能与从我的主 window 到 MessageBox window 的上下文切换有关,而不是任何时间延迟。
我发现了一个网页,它讨论了 Windows 8 上 MIDI 系统发生的变化,这可能与它有关,除了我的 MIDI 播放代码似乎没有问题播放第一个文件,它只是拒绝播放任何后续文件。该页面位于:
http://coolsoft.altervista.org/en/blog/2013/03/what-happened-midi-mapper-windows-8
我也听说另一个开发者的另一个游戏程序也有同样的问题:它会播放一次背景音乐,然后 MIDI 系统似乎打嗝。
那么,大问题:有谁知道需要做什么才能让 Windows 10 顺利播放顺序 MIDI 文件?
首先,我强烈建议您使用 Win 10 系统进行调试,即使它在 VM 中也是如此。由于Win 10是免费升级,所以这应该不是问题。
当您调用 mciSendCommand(..., MCI_CLOSE, ...) 时,请在第三个参数中指定 MCI_WAIT。 0 在这里无效。也许在 Win 10 上,这会导致您的代码不等到关闭完成。