简体   繁体   中英

Why does MIDI sequencer fail to play second time on Windows 10

For years I've been using the high-level MIDI interface in Windows to play MIDI files as background music in my games. Now I've heard from several folks that have upgraded to Windows 10 that the background music will play the FIRST time (after starting the program), but once it finishes, it fails to start up again (or start the next MIDI file playing). I don't have a Windows 10 installation myself yet, but I've put together a couple of debug programs, and the second time the program tries to start a MIDI file playing, this call in PlayMusic():

mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);

returns a value of 343 (MCIERR_SEQ_NOMIDIPRESENT). Yet if the program is exited and started up again, then the background MIDI can be made to play again.

I have two functions: PlayMusic(char *fname) and StopMusic() that start and stop a MIDI file playing, along with a global variable MusicPlaying that keeps track of whether a music file is currently playing or not. Then, the main window handler handles the MM_MCINOTIFY message, and when MCI_NOTIFY_SUCCESSFUL is received, it queues a notification to my main code that the music finished, and that main code will then eventually make another call (if it wants to) to PlayMusic() to start a file playing again. Here's the code blocks (MusicPlaying is the global variable that knows whether a MIDI file is active or not):

//*********************************************************
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;
}

and in the main window message processor, when the MIDI file finishes playing:

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;

The normal sequence of events would be:

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

If something causes the main game code to stop the music prematurely, then it would call StopMusic() which would:

MCI_STOP
MCI_CLOSE
MusicPlaying = 0;

NOTE: (See the line "**** SEE 'NOTE'..." in MM_MCINOTIFY above) As part of debugging this, I inserted a MessageBox call at this point to display what notifications were being received. The user with Win 10 saw the exact same messages as I do with earlier versions of Windows: wParam=1 and lParam=1 when SUCCESSFUL happens, wParam=4 and lParam=1 when ABORTED happens. But here's the kicker: with that MessageBox happening at that point, then when the user clicked OK on the MessageBox, the MIDI file restarted just fine! My first thought was that it was the time delay of the MessageBox appearing and being clicked away that gave the MIDI system time to 'reset'. But a further test, inserting code to delay by 2 seconds the queue-ing of the "MUSIC_DONE" message to my main code had NO effect on the problem. So it seems that it's probably something to do with the context switch from my main window to the MessageBox window and back rather than any time delay.

I found one web page that talks about changes that happened to the MIDI system on Windows 8, and that might have SOMETHING to do with it, except my MIDI playing code seems to have no trouble playing the first file, it just refuses to play any subsequent ones. That page is at:

http://coolsoft.altervista.org/en/blog/2013/03/what-happened-midi-mapper-windows-8

I've also heard a report that another game program from another developer is having the same problem: it will play background music once, and then the MIDI system seems to burp.

So, the BIG QUESTION: Does anyone have any idea what needs to be done to get Windows 10 to smoothly play sequential MIDI files?

First, I highly encourage you to get a Win 10 system to debug, even if it is in a VM. Since Win 10 is a free upgrade, this should not be a problem.

When you call mciSendCommand(..., MCI_CLOSE, ...) please specify MCI_WAIT in the third parameter. 0 is not valid here. Perhaps on Win 10 this causes your code not to wait until the Close is finished.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM