Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MCI Midi class

0.00/5 (No votes)
14 Jul 2003 1  
A tiny and very easy to use C++ class to play *.WAV, *.MID and *.RMI files

Introduction

cSound is a really tiny and very easy to use C++ class to play *.WAV, *.MID and *.RMI files. You can add it to your project without changes. To play a Wave, Midi or Riff-Midi file you need only one function call! This class uses MCI, it does NOT require DirectX. It will run under Windows 95 / 98 / ME / NT / 2000 / XP.

It will play Midi via the Midi Mapper.  Which Midi device is used by the mapper depends on the settings in your Control Panel --> MultiMedia. Try the different devices, they sound extremely different !!! I added some specific Midi files to the download to check. The Main Demo project is written in MFC but the cSound class is completely free of MFC.

Midi without DirectX

If you search the internet for source code to play Midi you will find a lot which require DirectX to be installed on the users computer.

Advantages of using DirectX to play Midi

  1. If you select the DirectX Microsoft Synthesizer (default device) it will be possible that multiple applications can play Midi at once.
  2. If you have a very CHEAP sound card, the Microsoft Synthesizer will sound better than the driver of your soundcard.

Disadvantages of using DirectX to play Midi

  1. Every user of your software needs to have DirectX installed. On Windows 95 and NT he will always have to update Direct X. Users of Windows 98 and 2000 will also need to update DirectX if your application requires DirectX 7 or 8. The download of DirectX is unacceptable for modem users (> 12 Megabyte)
  2. The documentation of DirectX in the MSDN is poor, poor, poor ! And if you even want to write for an older DirectX version (like 5 or 6) to allow users of Windows 98 and 2000 to use your application without updating DirectX you will find NOTHING about that in the actual MSDN ! Microsoft completely removed the older documentations !
  3. When your application initializes DirectX a dozen of DLLs is loaded which consume about 4 Megabytes of RAM. On a Pentium 100 this loading of DLLs takes 3 seconds.
  4. After playing a Midi file DirectX does NOT automatically free the used Midi device. This means that other applications (e.g. WinAmp) can NOT access it. Your software has to take care to remove the port after playing the file. The problem is that the sound is played asynchronously and you don't know when DirectX will finish to play. To find out in advance the length of a Midi sound is very complicated because the tempo can change multiple times in a Midi file. So you could check with a timer in certain intervals if the sound is still playing (IDirectMusicPerformance::IsPlaying()) and than remove the port - but this is awkward !
  5. If you have a middle-class or a good soundcard (like Soundblaster live) you will find that the Microsoft Synthesizer (default device) sounds awful ! You have to insert extra code (IDirectMusic::EnumPort()), a combo box, code to store the user settings etc. to allow the user to choose a Midi device, which sounds better. My cSound class does not need that because it uses the device, which the user has selected in the Control Panel.

Using the code

Calling cSound cannot be easier. It's only one function call:

cSound::PlaySoundFile(Path);

The cMidiDemoDlg.h file

private:
    cSound i_Sound;

The cMidiDemoDlg.cpp file

void CMidiDemoDlg::PlayMidiOrWav(char *p_s8PathToSoundFile) 
{
    char s8_Buf[300];
    DWORD u32_Err;
    if (u32_Err=i_Sound.PlaySoundFile(p_s8PathToSoundFile))
    {
             // errors defined in cSound.h

             if (u32_Err == ERR_INVALID_FILETYPE)     
               strcpy(s8_Buf, "This filetype is not supported !");
        else if (u32_Err == ERR_PLAY_WAV)             
               strcpy(s8_Buf, "Windows could not play the Wav file !");
        else if (u32_Err == MCIERR_SEQ_NOMIDIPRESENT) 
              strcpy(s8_Buf, "There is no Midi device installed or" 
                  " it is used by another application!");
        else
        {
            // translate errors from WinError.h

            if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, 
               u32_Err, 0, s8_Buf, sizeof(s8_Buf), 0))
            {
                // translate errors from MMsystem.h

                if (!mciGetErrorString(u32_Err, s8_Buf, sizeof(s8_Buf)))
                {
                    sprintf(s8_Buf, "Error %d", u32_Err);
                }
            }
        }

        MessageBox(s8_Buf, "Error", MB_ICONSTOP);
    }
}

The cSound class

#include "Mmsystem.h"


#define  ERR_INVALID_FILETYPE 50123
#define  ERR_PLAY_WAV         50124

class cSound
{
public:
    cSound();
    virtual   ~cSound();

    DWORD      PlaySoundFile(char *p_s8File);
    void       StopSoundFile();
};

The cSound.cpp file

/****************************************
       Plays Wav or (Riff) Midi file
****************************************/

DWORD cSound::PlaySoundFile(char *p_s8File)
{
    // It is important to check if the file exists !

    // On Windows NT PlaySound() returns TRUE 

    // even if the file does not exist !

    // Then PlaySound() makes the PC speaker beep !!!

    
    // mciSendString("open...") also gives an absolutely 

    // stupid error message

    // if the file does not exist !


    DWORD u32_Attr = ::GetFileAttributes(p_s8File);
    if (u32_Attr == 0xFFFFFFFF || (u32_Attr & 
            FILE_ATTRIBUTE_DIRECTORY)) 
        return ERROR_FILE_NOT_FOUND;


    // Get file extension

    char *p_s8Ext = strrchr(p_s8File, '.');
    if  (!p_s8Ext)
        return ERR_INVALID_FILETYPE;
    

    if (stricmp(p_s8Ext, ".wav") == 0)
    {
        StopSoundFile();
        
        // PlaySound() is very primitive: no Error Code available

        if (!PlaySound(p_s8File, 0, SND_FILENAME | SND_ASYNC))
            return ERR_PLAY_WAV;

        return 0;
    }
    

    DWORD u32_Err;
    if (!stricmp(p_s8Ext, ".mid") || !stricmp(p_s8Ext, ".midi")
           || !stricmp(p_s8Ext, ".rmi"))
    {
        StopSoundFile();

        static char s8_LastFile[MAX_PATH] = "";

        // the mciSendString("open...") command is slow 

        // (on Windows NT, 2000 and XP) 

        // so we call it only if necessary

        if (strcmp(s8_LastFile, p_s8File) != 0)
        {
            strcpy(s8_LastFile, p_s8File);

            mciSendString("close all", 0, 0, 0);

            char s8_Buf[300];
            sprintf(s8_Buf, 
             "open \"%s\" type sequencer alias MidiDemo", 
             p_s8File);

            if (u32_Err=mciSendString(s8_Buf, 0, 0, 0))
                return u32_Err;
        }

        if (u32_Err=mciSendString("play MidiDemo from 0", 0, 0, 0))
        {
            // replace stupid error messages

            if (u32_Err == 2) u32_Err = MCIERR_SEQ_NOMIDIPRESENT;
            return u32_Err;
        }

        return 0;
    }

    return ERR_INVALID_FILETYPE;
}



/**************************************************
       Stops the currently playing Wav and Midi
***************************************************/

void cSound::StopSoundFile()
{
    PlaySound(0, 0, SND_PURGE); // Stop Wav


    mciSendString("stop MidiDemo", 0, 0, 0); // Stop Midi

}

Known bugs in Windows NT, 2000 and XP

  1. On Windows NT, 2000 and XP the first note of a Midi song is omitted in playing if the song does not begin with a rest of at least a quarter note !! (The Midi sample-files added to the download all begin with a rest)
  2. On Windows NT, 2000 and XP the mciSendString("open...") command is extremely slow
  3. Only on Windows XP mciSendString("open...") gives a stupid error message if the Midi device is occupied by another application. (MCIERR_SEQ_NOMIDIPRESENT instead of MCIERR_SEQ_PORT_INUSE) mciGetErrorString() translates this to "No Midi hardware available or no drivers installed" !!

Another Midi Player using the MMSystem Interface

In the MSDN you find a sample code which also plays Midi files without DirectX. (search for "MidiPlyr") It uses the midiOutxxx() and midiStreamxxx() Interface of WinMM.Dll (This interface is also used by WinAmp). The bugs in Windows 2000 and XP described above do not affect MidiPlyr. It works perfectly on all platforms. But this sample code is EXTREMELY complex. (and written in pure C)

Midi class using DirectX 8

If you still want a DirectX Midi Player (which also supports 3D-Sound, special effects etc) download cMidiMusic (C++) from http://www.codeproject.com (It requires DirectX 8)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here