Intro

This is a prototype as I couldn't get things working/sounding right. This is due to two main reasons:
A) I couldn't work out how to select instruments.
B) For some reason when the WAVEFORMAT_MIDI_MESSAGE is dynamicly allocated, waveOutUnprepareHeader never sends the WOM_DONE message, therefor locking the program up (until waveOutUnprepareHeader() is called). To test this, copy the example from WAVEFORMAT_MIDI_MESSAGE in the SDK help, and change the line

    WAVEFORMAT_MIDI_MESSAGE MidiMessage[5];

to

    WAVEFORMAT_MIDI_MESSAGE *MidiMessage = new WAVEFORMAT_MIDI_MESSAGE[5];

Drop us a line if you figure A out, I would REALLY like to know, as it is I'm going to have to shelf this code as I'm out of ideas for the moment.
For B you could try creating two static buffers, and while one is playing full the other, and switch them over.

But I NEED MIDI now!

Unfortunatly FMOD doesn't support MIDI on Windows CE, but Arcsoft does, although it will cost 'ya.

Main function

The following is based on the example in the SDK help.

The function loads the midi file into memory, calls readmidi() to return the track data, then calls parseMidiData() to get the number of MIDI commands and track speed. If this is acceptable it sets up the waveOut device, and calls parseMidiData() again, which fills out the WAVEFORMAT_MIDI_MESSAGE array with MIDI commands.

Things to note: Firstly, I have hardcoded the size of WAVEFORMAT_MIDI_MESSAGE to 48, this is just big enough for the the MIDI files I was using to test the routine.
Secondly I have hard coded the speed of play back (USecPerQuarterNote and TicksPerQuarterNote) as I was finding it didn't always sound right.
Lastly the WaitForSingleObject() timeout is 10 seconds, as dynamicly allocating the WAVEFORMAT_MIDI_MESSAGE would lock the program up here.


bool CSoundsDialog::playMidi(CString name)
{
#ifdef PLAY_MIDI
    struct miditrack seq[MAXTRKS];
    CFile f;
    f.Open(name, CFile::modeRead | CFile::shareDenyWrite);
    unsigned char * p = new unsigned char[f.GetLength()+1];
    f.Read(p, f.GetLength());

    int tracks = readmidi(p, f.GetLength(), seq);

    WAVEFORMAT_MIDI wfm; // WAVEFORMATEX
    memset(&wfm,0,sizeof(wfm));
    wfm.USecPerQuarterNote=263157;
    wfm.TicksPerQuarterNote=(p[12]<<8) | p[13];//480

    int j = parseMidiData(0, &wfm.USecPerQuarterNote, &wfm.TicksPerQuarterNote, 0, seq);

    wfm.USecPerQuarterNote=263157;
    wfm.TicksPerQuarterNote=480;

    if (j < 48) {
        hEvent=CreateEvent( NULL,TRUE,FALSE,NULL);
        wfm.wfx.wFormatTag=WAVE_FORMAT_MIDI;
        wfm.wfx.nChannels=1;
        wfm.wfx.nSamplesPerSec=32;
        wfm.wfx.nAvgBytesPerSec=32;
        wfm.wfx.nBlockAlign=sizeof(WAVEFORMAT_MIDI_MESSAGE);
        wfm.wfx.wBitsPerSample=16;
        wfm.wfx.cbSize=WAVEFORMAT_MIDI_EXTRASIZE;

        MMRESULT Result;
        HWAVEOUT hWaveOut;
        HANDLE hEvent;

        Result = waveOutOpen(&hWaveOut, WAVE_MAPPER, (LPWAVEFORMATEX)&wfm, (DWORD)waveOutProc, hEvent, CALLBACK_FUNCTION);

        if (Result!=MMSYSERR_NOERROR) {
            return false;
        }

        waveOutSetVolume(hWaveOut, 0x7777 );

        WAVEFORMAT_MIDI_MESSAGE MidiMessage[48];
        /*
        WAVEFORMAT_MIDI_MESSAGE * MidiMessage;
        MidiMessage = new WAVEFORMAT_MIDI_MESSAGE[48];
        //*/

        parseMidiData(MidiMessage, 0, 0, 0, seq);

        MidiMessage[j].DeltaTicks=0;
        MidiMessage[j].MidiMsg=0;
        j++;

        f.Close();

        delete [] p;

        //s.Format(L"created %d midi messages", j);
        //MessageBox(s);

        WAVEHDR WaveHdr;
        WaveHdr.lpData = (LPSTR)&MidiMessage;
        WaveHdr.dwBufferLength = sizeof(WAVEFORMAT_MIDI_MESSAGE) * j;
        WaveHdr.dwFlags = 0;
        WaveHdr.dwLoops = 1;
        Result = waveOutPrepareHeader(hWaveOut,&WaveHdr,sizeof(WaveHdr));
        if (Result != MMSYSERR_NOERROR)
            MessageBox(L"waveOutPrepareHeader failed");
        else {
            Result = waveOutWrite(hWaveOut,&WaveHdr,sizeof(WaveHdr));
            if (Result != MMSYSERR_NOERROR)
                MessageBox(L"waveOutWrite failed");
            else {
                WaitForSingleObject(hEvent,10000);
            }
            Result = waveOutUnprepareHeader(hWaveOut,&WaveHdr,sizeof(WaveHdr));
        }
        waveOutReset(hWaveOut);
        Result = waveOutClose(hWaveOut);
        //delete [] MidiMessage;

        return true;
    } else {
        f.Close();
        delete [] p;
        return false;
    }
#else
    return false;
#endif
}

Read MIDI file

The MIDI header looks like this: 'MThd' (4bytes), length=6 (4bytes), format (4bytes), tracks (2bytes), division (2bytes)
Followed by 1-to-many tracks, which look like this: 'MTrk' (4bytes), length (4bytes), data
And below you will find my function to read them. Now WAVEFORMAT_MIDI_MESSAGE only supports one track, so I have commented out the code to do multitrack MIDI files. (note: the MIDI 'format' should be 0, which means its a single track MIDI file, but I don't check this)

int CSoundsDialog::readmidi(unsigned char*midifilebuf, int filelength, miditrack *seq)
{
    int ntrks;

    if (Read32(&midifilebuf) == 0x4d546864) { // MThd
        midifilebuf += 4;//tracklen
        midifilebuf += 2;//format
        ntrks = Read16(&midifilebuf);
        midifilebuf += 2;//division
    } else
        return 0;

    if (ntrks > MAXTRKS) {
        CString s;
        s.Format(L"%d tracks found, only %d is supported", ntrks, MAXTRKS);
        MessageBox(s, L"", MB_ICONWARNING);
        ntrks = MAXTRKS;
    }
     //for (int track = 0; track < ntrks; track++) {
        if (Read32(&midifilebuf) != 0x4d54726b) //MTrk
            return 0;
        seq[0].length = Read32(&midifilebuf);
        seq[0].data = midifilebuf;
        midifilebuf += seq[0].length;
    //}
    return ntrks;
}

Parse MIDI track

And here is the key to getting things working, its all by the MIDI book, its just that sometimes its hard to find :-)

Each data chunk looks like this: delta time (varible size), event.

For efficiency MIDI has varible-size numbers. The lower 7 bits is the number, and the MSB signifies another byte has been used to represent the number. For example: 0x7F -> 127, and 0x81 00 -> 128, have a look at the DeltaTicks calculation for how to implement this. (Note I have only implemented 2 bytes, in practice it could be more, thats a prototype for you)

Event is always greater than or equal to 0x80

Event 0xff is a meta event, and takes the form: 0xff, event (2bytes), size (varible), data.
The one I'm most interested in is the Set Tempo event, which takes this form: 0xff 51 03 tt tt tt, where tt is the tempo.
Note: if I was doing this properly I would dynamicly interpret the Set Tempo message into a MIDI_MESSAGE_UPDATETEMPO message.

Events 0xf0 and 0xf7 are Sysex events, which can be device specific messages

Any other messages (such as 0x9x AKA Note On messages) we can send onto the WaveOut functions to handle

Lastly, and messages < 0x80 are 'running status' messages, which means keep using the last command. These are oftern used when you want to play several notes in a row.

int CSoundsDialog::parseMidiData(WAVEFORMAT_MIDI_MESSAGE*MidiMessage,
                                 UINT32 * secs, UINT32 * tics, DWORD track, miditrack * seq)
{
    int j = 0; // j indexes into MidiMessage
    DWORD sz;

    for (unsigned int i = 0; i < seq[track].length; ++i) { // i indexes into the midi data track
	// the delta time
        if (seq[track].data[i] >= 0x80) {
            if (MidiMessage) MidiMessage[j].DeltaTicks = ((seq[track].data[i]&0x7f)<<7)|seq[track].data[i+1];
            ++i;
        } else
            if (MidiMessage) MidiMessage[j].DeltaTicks = seq[track].data[i];
        ++i;

        // the event
        if (seq[track].data[i] >= 0x80) {
            if (seq[track].data[i] == 0xff) { // Meta
                int type = seq[track].data[++i];
                sz = seq[track].data[++i];
                if (type == 0x51 && secs) // set tempo
                    *secs = (seq[track].data[i]<<16) | (seq[track].data[i+1]<<8) | (seq[track].data[i+2]);
                i+= sz;
            } else if (seq[track].data[i] == 0xf0 || seq[track].data[i] == 0xf7) { // systex
                sz = seq[track].data[++i];
                i+= sz;
            } else {
                ++i;
                if (MidiMessage)
                    MidiMessage[j].MidiMsg = (seq[track].data[i]<<8) | (seq[track].data[i+1]<<16) | seq[track].data[i-1];
                ++j;
                ++i;
            }
        } else {
            // running status, check the SDK help for more info
            if (MidiMessage)
                MidiMessage[j].MidiMsg = seq[track].data[i] | (seq[track].data[i+1]<<8);
            ++j;
            ++i;
        }
    }

    return j;
}

Here is an example midi file breakdown of AlarmXtra2.mid (sans the delta-time):

    Header
    ======
    SMF type 0
    Number of tracks = 1
    480 ticks per quarter note

    Track 1 (100 bytes)
    ===================
    Meta 20 Channel prefix   5
    Meta 03 Track name        Celesta*Demixed 06
    Meta 04 Instrument name  GM Device  6
    Meta 58 Time signature   4:0  24 8
    Meta 59 Key signature
    Meta 54 SMPTE offset
    Meta 51 Set tempo        151114
    Midi 95 Note on          57 5F
    Midi 95 Note on          53 50
    Midi 95 Note on          57 00
    Midi 95 Note on          53 00
    Midi 95 Note on          57 50
    Midi 95 Note on          57 00
    Meta 2F End of track

As you can see I've given you a VERY quick overview of the MIDI format, so if you want to know more have a read of this.

Utilities/Header

Here is the utility functions/header that have been referenced by the above code. Nothing special here, I just include it for completeness.

#include "wfmtmidi.h"
class CSoundsDialog : public CDialogImpl<CSoundsDialog>
{
private:
    struct miditrack {
        unsigned char *data;
        unsigned long length;
    };
    int parseMidiData(WAVEFORMAT_MIDI_MESSAGE*MidiMessage, UINT32 * secs, UINT32 * tics, DWORD track, miditrack * seq);
    bool playMidi(CString name);
    int readmidi(unsigned char*filebuf, int filelength, miditrack *seq);
};

#define MAXTRKS      1

void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    if (uMsg==WOM_DONE)
        SetEvent((HANDLE)dwInstance);

    return;
}
unsigned short Read16(unsigned char **midifilebuf)
{
    unsigned short x = (**(midifilebuf) << 8) | (*midifilebuf)[1];
    *midifilebuf += 2;
    return x;
}

unsigned long Read32(unsigned char **midifilebuf)
{
    unsigned long x = (**(midifilebuf) << 24) | ((*midifilebuf)[1] << 16) |
        ((*midifilebuf)[2] << 8) | (*midifilebuf)[3];
    *midifilebuf += 4;
    return x;
}

visits:
(c) Riki June 2004