C Programming Column
by Al Stevens

Listing One
// ----- midiplayer.h
#ifndef MIDIPLAYER_H
#define MIDIPLAYER_H
#include "stdafx.h"
#include "midiinfo.h"
// ---- realtime midi event data
struct MIDIEvent    {
    Long delta;
    Short eventno;
    Short channel;
    Short param1;
    Short param2;
};
// ---- midi event data ready for sequencing
struct MIDIData {
    Long delta;     // delta time from beginning of sequence
    DWORD data;     // midi event packet
   // --- these are to let the type be contained in a std::vector
    bool operator<(const MIDIData&) const
        { return true; }
    bool operator==(const MIDIData&) const
        { return true; }
};
// ------- class for sequencing a Standard MIDI Format file
class MIDIPlayer : public MIDIFile  {
    long division;          // delta time ticks per quarter note
    CWnd* owner;            // window to notify when sequence is done
    // ----- the ticking clock variables
    long clock, period, nticks, fticks, trtime;
    Long delta;             // running delta time accumulation
    std::vector<MIDIData> track;    // track vector of events being gathered
    // --- vector of tracks
    std::vector<std::vector<MIDIData> > tracks;
    // --- vector of track event offsets
    std::vector<int> trkndx;
    HMIDIOUT hMidiOut;
    UINT timer;
    TIMECAPS tc;
    long tempo;   // microseconds per quarter note
    bool ticking; // semaphore to wait for timing message function to complete
    int countoff; // number of measures to count off
    bool metronome;         // true for metronome during playback
    int divctr;             // counts for metronome ticks
    int beatspermeasure;    // number of beats per measure (3, 4, ...)
    // --- overridden MIDIFile class functions
    void Header(Short fmt,Short trks,Short div)
        { division = div; }
    void StartTrack(int trkno)
        { delta = 0; }
    void EndOfTrack(Long delta);
    void TimeSignature(Long delta,Short numer,
                                Short denom,Short clocks,Short qnotes);
    void NoteOn(Long delta,Short channel,Short note, Short velocity);
    void NoteOff(Long delta,Short channel,Short note, Short velocity);
    void Controller(Long delta,Short channel,Short controller, Short value);
    void ProgramChange(Long delta,Short channel,Short program);
    // ----- private member functions
    void StoreEvent(const MIDIEvent& mev);
    void KillTimer();
    void StopMIDI();
    // ----- timer mechanism
    friend void CALLBACK TimerCallback(UINT, UINT, DWORD, DWORD, DWORD);
    void TimingMessage();
    static MIDIPlayer* pplay;   
                       // = "this" so TimerCallback can call TimingMessage
public:
    explicit MIDIPlayer(std::ifstream& rFile);
    // ---- play SMF file with count measures of count-off, tempo of tmpo, and
    //      metronome click if metr (if tmpo == 0, use tempo from SMF file)
    void Play(long tmpo, int count, bool metr);
    void StopPlay();                // stop playing the sequence
    void Reset();                   // reset the midi system
    void RegisterWindow(CWnd* wnd)  // register a window to 
                                    //       notify when sequence is done
        { owner = wnd; }
    void Metronome(bool onoff)      // turn the metronome on or off
        { metronome = onoff; }
    void ChangeTempo(long t)
        { tempo = t; }
};
#endif


Listing Two
// ---- midiplayer.cpp
#include "stdafx.h"
#include "midiplayer.h"

MIDIPlayer::MIDIPlayer(std::ifstream& rFile) : MIDIFile (rFile) 
{
    hMidiOut = 0;
    timer = 0;
    division = 5;
    owner = 0;
    ticking = false;
    countoff = 0;
    metronome = false;
    beatspermeasure = 4;
}
void MIDIPlayer::EndOfTrack(Long)
{
    if (track.size())   {
        // a track of realtime events has been accumulated, save it
        tracks.push_back(track);
        track.clear();
    }
}
void MIDIPlayer::TimeSignature(Long delta,Short numer,
                                   Short denom,Short clocks,Short qnotes)
{
    beatspermeasure = numer;
}
inline void MIDIPlayer::StoreEvent(const MIDIEvent& mev)
{
    delta += mev.delta;
    DWORD dwEvent  = mev.eventno | mev.channel | 
                             (mev.param1 << 8) | (mev.param2 << 16);
    MIDIData data = { delta, dwEvent };
    track.push_back(data);
}
void MIDIPlayer::NoteOn(Long delta,Short channel,Short note, Short velocity)
{
    MIDIEvent mev = { delta, MIDI_NOTEON, channel, note, velocity };
    StoreEvent(mev);
}
void MIDIPlayer::NoteOff(Long delta,Short channel,Short note, Short velocity)
{
    MIDIEvent mev = { delta, MIDI_NOTEOFF, channel, note, velocity };
    StoreEvent(mev);
}
void MIDIPlayer::Controller(Long delta,Short channel,Short controller, Short value)
{
    MIDIEvent mev = { delta, MIDI_CONTROL, channel, controller, value };
   StoreEvent(mev);
}
void MIDIPlayer::ProgramChange(Long delta,Short channel,Short program)
{
    MIDIEvent mev = { delta, MIDI_PROGRAM, channel, program, 0 };
    StoreEvent(mev);
}
MIDIPlayer* MIDIPlayer::pplay;
void CALLBACK TimerCallback(UINT, UINT, DWORD, DWORD, DWORD)
{
    MIDIPlayer::pplay->TimingMessage();
}
void MIDIPlayer::Play(long tmpo, int count, bool metr)
{
    if (midiOutOpen(&hMidiOut, MIDIMAPPER, 0, 0L, 0L) == 0) {
        nticks = fticks = 0;    // integer representation of 
                                //    integral and fractional parts of clock
        period = 1;             // time slice in milliseconds
        clock = 0;              // accumulated time
        trtime = (period * 1000) * division;
        countoff = count * beatspermeasure + 1;
        metronome = metr;
        if (tmpo)
            ChangeTempo(tmpo);      // playing at a specified tempo
        divctr = 0;
        for (int i = 0; i < tracks.size(); i++)
            trkndx.push_back(0);
        pplay = this;
        ticking = false;
        timeGetDevCaps(&tc, sizeof tc);
        timeBeginPeriod(tc.wPeriodMin);
        timer = timeSetEvent(period, tc.wPeriodMin, 
                                     TimerCallback, 0, TIME_PERIODIC);
        DWORD mmsg  = 0xfa;     //.start message
        midiOutShortMsg(hMidiOut, mmsg);
    }
}
void MIDIPlayer::TimingMessage()
{
    if (hMidiOut)   {
        ticking = true;
        // --- integral part of tick
        nticks = (fticks + trtime) / tempo;
        // --- fractional part of tick
        fticks += trtime - (nticks * tempo);
        // ---- process the count-off and the metronome
        if (divctr <= 0)    {
            // --- at a quarter note beat
            if (countoff)   {
                // --- in the count-off
                if (--countoff) {
                    DWORD ev  = MIDI_NOTEON | 9 | (37 << 8) | (80 << 16);
                    midiOutShortMsg(hMidiOut, ev);
                }
            }
            if (countoff == 0 && metronome) {
                // ---- play metronome (except during count-off)
               DWORD ev  = MIDI_NOTEON | 9 | (37 << 8) | (80 << 16);
                midiOutShortMsg(hMidiOut, ev);
            }
            divctr = division;
        }
        divctr -= nticks;
        if (countoff == 0)  {
            // ---- sequencer code
            bool stillplaying = false;
            // --- scan the tracks for realtime midi events due for playing
            for (int i = 0; i < tracks.size(); i++) {
                // --- see if there are more events this track
                if (trkndx[i] < tracks[i].size())   {
                    stillplaying = true;
                    MIDIData& ev = tracks[i][trkndx[i]];
                    while (ev.delta <= clock)   {
                        // fire this event
                        midiOutShortMsg(hMidiOut, ev.data);
                        trkndx[i]++;
                        if (trkndx[i] == tracks[i].size())
                            break;
                        ev = tracks[i][trkndx[i]];
                    }
                }
            }
            if (!stillplaying)
                StopPlay();
            clock += nticks;
        }
        ticking = false;
    }
}
void MIDIPlayer::KillTimer()
{
    if (timer)  {
        timeKillEvent(timer);
        timer = 0;
        timeEndPeriod(tc.wPeriodMin);
    }
}
void MIDIPlayer::StopMIDI()
{
    if (hMidiOut)   {
        DWORD mmsg  = 0xfc;     // stop message
        midiOutShortMsg(hMidiOut, mmsg);
        midiOutClose(hMidiOut);
        hMidiOut = 0;
    }
}
void MIDIPlayer::StopPlay()
{
    KillTimer();
    StopMIDI();
    if (owner)
        owner->SendMessage(MM_MCINOTIFY, 0, 0);
}
void MIDIPlayer::Reset()
{
    KillTimer();
    while (ticking) // wait for TimingMessage to return
        ;
    if (hMidiOut)   {
        // --- all notes off, all channels
        DWORD ev;
        for (unsigned char channel = 0; channel < 16; channel++)    {
            ev = 0x7bb0 | channel;
            midiOutShortMsg(hMidiOut, ev);
        }
        ev = 0xff;  // system reset message
        midiOutShortMsg(hMidiOut, ev);
        StopMIDI();
    }
}






5


