Creating an MCI MIDI Class


This article was contributed by Elmue.

Environment: pure C++. Runs on Win 95/98/ME/NT/2000/XP.

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 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 that require DirectX to be installed on the user’s 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 DirectX. 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 Megabytes).

  2. The documentation of DirectX in the MSDN is poor, poor, poor! And, if you even want to write for an older DirectX version (such as 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 documentation!

  3. When your application initializes DirectX, a dozen DLLs are loaded; they consume about 4 megabytes of RAM. On a Pentium 100, this loading of DLLs takes three seconds.

  4. After playing a MIDI file, DirectX does NOT automatically free the used MIDI device. This means that other applications (such as WinAmp) canNOT 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 playing. It is very complicated to find out the length of a MIDI sound in advance 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 (such as 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, and so forth, to allow the user to choose a MIDI device, which sounds better. My cSound class does not need that because it uses the device that 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

The cSound.h file:

#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 whether 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 from being played if the song does not begin with a rest of at least a quarter note’s duration!! (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 that 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 that also supports 3D-sound, special effects, and so forth, download cMIDIMusic (C++) from http://www.codeproject.com. (It requires DirectX 8.)

Downloads


Download demo project – 22 Kb


Download source – 20 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read