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 track; // track vector of events being gathered // --- vector of tracks std::vector > tracks; // --- vector of track event offsets std::vector 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