Current MS Students/Steven Emory/CS599 Page
From CSWiki
[edit] Thursday, April 10, 2008
Almost finished up with my MIDI file reader. I really dislike Big Endian binary chunk file formats with variable length quantities (too much bit hacking, having to reverse the byte order all the time), but it's almost done.
Now that this is done I'm going to work on a single-note version of the transcription algorithm (violin solos, etc.) to get things set up. The fun should start right about now :-).
Here's some sample output. It detects pretty much everything I'll need, although many MIDI files don't store the INSTRUMENT NAME for some reason (it rather appears in the SEQUENCE TRACK NAME instead, forcing me to pull it out from there). The SEQUENCE TRACK NAME however, is not guaranteed to equal the INSTRUMENT NAME. This is why MIDI sucks for computation: it's not strict notation; it's a communication protocol (like HTTP and client-server, MIDI is a protocol that specifies how data is transferred between a MIDI controller and a computer through a MIDI port).
256 ticks per beat.
TRACK
META EVENT
TIME SIGNATURE
3/8
36-8
META EVENT
KEY SIGNATURE
Bb Minor
META EVENT
SET TEMPO
META EVENT
END OF TRACK
TRACK
CHANNEL EVENT
CONTROL CHANGE EVENT
CHANNEL EVENT
CONTROL CHANGE EVENT
CHANNEL EVENT
PROGRAM CHANGE EVENT
META EVENT
SEQUENCE/TRACK NAME
Acoustic Guitar
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
CHANNEL EVENT
NOTE ON EVENT
META EVENT
END OF TRACK
Reading MIDI file done.
Press any key to continue . . .
stdafx.h (pre-compiled header file)
#ifndef __STDAFX_H
#define __STDAFX_H
#include<windows.h>
#include<iostream>
#include<iomanip>
#include<fstream>
#include<algorithm>
#include<string>
#include<cmath>
using namespace std;
typedef __int8 int08;
typedef __int16 int16;
typedef __int32 int32;
typedef __int64 int64;
typedef unsigned __int8 uint08;
typedef unsigned __int16 uint16;
typedef unsigned __int32 uint32;
typedef unsigned __int64 uint64;
template<class T>
inline T reverse_byte_order(const T& value)
{
T retval = value;
unsigned char* data = reinterpret_cast<unsigned char*>(&retval);
std::reverse(data, data + sizeof(T));
return retval;
}
template<class T>
inline void reverse_byte_order(T* data, size_t elem)
{
unsigned char* temp = reinterpret_cast<unsigned char*>(data);
std::reverse(temp, temp + elem*sizeof(T));
}
#endif
stdafx.cpp
#include "stdafx.h"
main.cpp
#include "stdafx.h"
class MIDIFileReader {
private :
ifstream file;
bool end_of_track;
private :
bool read_error(void);
bool read_error(const char* error);
uint32 read_vlq(void);
private :
bool ReadMetaEvent(void);
bool ReadChannelEvent(uint08 param);
private :
bool SequenceNumber(void);
bool Text(void);
bool CopyrightNotice(void);
bool SequenceTrackName(void);
bool InstrumentName(void);
bool Lyrics(void);
bool Marker(void);
bool CuePoint(void);
bool ChannelPrefix(void);
bool EndOfTrack(void);
bool SetTempo(void);
bool SMPTEOffset(void);
bool TimeSignature(void);
bool KeySignature(void);
bool SequencerSpecific(void);
private :
bool NoteOff(uint08 channel);
bool NoteOn(uint08 channel);
bool KeyAfterTouch(uint08 channel);
bool ControlChange(uint08 channel);
bool ProgramChange(uint08 channel);
bool ChannelAfterTouch(uint08 channel);
bool PitchWheelChange(uint08 channel);
public :
bool read(const char* filename);
public :
MIDIFileReader();
~MIDIFileReader();
};
int main()
{
MIDIFileReader reader;
reader.read("sample2.mid");
return 0;
}
MIDIFileReader::MIDIFileReader()
{
end_of_track = false;
}
MIDIFileReader::~MIDIFileReader()
{
}
bool MIDIFileReader::read_error(void)
{
if(file.is_open()) file.close();
return false;
}
bool MIDIFileReader::read_error(const char* error)
{
if(file.is_open()) file.close();
cout << "Error: " << error << endl;
return false;
}
uint32 MIDIFileReader::read_vlq(void)
{
if(!file.is_open()) return 0;
uint32 vl = 0;
uint08 c1, c2;
uint08 c3, c4;
file.read((char*)&c1, 1);
if(c1 < 128) vl = (unsigned int)c1;
else {
c1 = c1 & 0x7F;
file.read((char*)&c2, 1);
if(c2 < 128) vl = ((uint32)c1 << 7) | (uint32)c2;
else {
c2 = c2 & 0x7F;
file.read((char*)&c3, 1);
if(c3 < 128) vl = (((uint32)c1 << 14) | ((uint32)c2 << 7)) | (uint32)c3;
else {
c3 = c3 & 0x7F;
file.read((char*)&c4, 1);
if(c4 < 128) vl = ((((uint32)c1 << 21) | ((uint32)c2 << 14)) | ((uint32)c3 << 7)) | (uint32)c4;
}
}
}
return vl;
}
bool MIDIFileReader::ReadMetaEvent(void)
{
cout << " META EVENT" << endl;
// read command
unsigned char META_command = 0;
file.read((char*)&META_command, 1);
if(file.gcount() != 1) return false;
// process command
switch(META_command) {
case(0x00) : return SequenceNumber();
case(0x01) : return Text();
case(0x02) : return CopyrightNotice();
case(0x03) : return SequenceTrackName();
case(0x04) : return InstrumentName();
case(0x05) : return Lyrics();
case(0x06) : return Marker();
case(0x07) : return CuePoint();
case(0x20) : return ChannelPrefix();
case(0x2F) : return EndOfTrack();
case(0x51) : return SetTempo();
case(0x54) : return SMPTEOffset();
case(0x58) : return TimeSignature();
case(0x59) : return KeySignature();
case(0x7F) : return SequencerSpecific();
default : return false;
}
return true;
}
bool MIDIFileReader::ReadChannelEvent(uint08 param)
{
cout << " CHANNEL EVENT" << endl;
unsigned char command = (param >> 4);
unsigned char channel = (param & 0x0F);
switch(command) {
case(0x8) : return NoteOff(channel);
case(0x9) : return NoteOn(channel);
case(0xA) : return KeyAfterTouch(channel);
case(0xB) : return ControlChange(channel);
case(0xC) : return ProgramChange(channel);
case(0xD) : return ChannelAfterTouch(channel);
case(0xE) : return PitchWheelChange(channel);
default : return false;
}
return true;
}
//
// META EVENTS
//
bool MIDIFileReader::SequenceNumber(void)
{
cout << " SEQUENCE NUMBER" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::Text(void)
{
cout << " TEXT" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::CopyrightNotice(void)
{
cout << " COPYRIGHT NOTICE" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::SequenceTrackName(void)
{
cout << " SEQUENCE/TRACK NAME" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
char* str = reinterpret_cast<char*>(META_data);
str[META_length] = '\0';
cout << " " << str << endl;
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::InstrumentName(void)
{
cout << " INSTRUMENT NAME" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
char* str = reinterpret_cast<char*>(META_data);
str[META_length] = '\0';
cout << " " << str << endl;
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::Lyrics(void)
{
cout << " LYRICS" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::Marker(void)
{
cout << " MARKER" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::CuePoint(void)
{
cout << " CUE POINT" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::ChannelPrefix(void)
{
cout << " MIDI CHANNEL PREFIX" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::EndOfTrack(void)
{
cout << " END OF TRACK" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
end_of_track = true;
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::SetTempo(void)
{
cout << " SET TEMPO" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::SMPTEOffset(void)
{
cout << " SMPTE OFFSET" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::TimeSignature(void)
{
cout << " TIME SIGNATURE" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
cout << " " << (uint32)META_data[0] << "/" << (uint32)pow(2.0, (double)META_data[1]) << endl;
cout << " " << (uint32)META_data[2] << "-" << (uint32)META_data[3] << endl;
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::KeySignature(void)
{
cout << " KEY SIGNATURE" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
if(META_length != 2) return false;
int count = (int)((char)META_data[0]);
int type = (int)META_data[1];
if(count == 0 && type == 0) cout << " C Major" << endl;
if(count == 1 && type == 0) cout << " G Major" << endl;
if(count == 2 && type == 0) cout << " D Major" << endl;
if(count == 3 && type == 0) cout << " A Major" << endl;
if(count == 4 && type == 0) cout << " E Major" << endl;
if(count == 5 && type == 0) cout << " B Major" << endl;
if(count == 6 && type == 0) cout << " F# Major" << endl;
if(count == 7 && type == 0) cout << " C# Major" << endl;
if(count == 0 && type == 1) cout << " A Minor" << endl;
if(count == 1 && type == 1) cout << " E Minor" << endl;
if(count == 2 && type == 1) cout << " B Minor" << endl;
if(count == 3 && type == 1) cout << " F# Minor" << endl;
if(count == 4 && type == 1) cout << " C# Minor" << endl;
if(count == 5 && type == 1) cout << " G# Minor" << endl;
if(count == 6 && type == 1) cout << " D# Minor" << endl;
if(count == 7 && type == 1) cout << " A# Minor" << endl;
if(count == -1 && type == 0) cout << " F Major" << endl;
if(count == -2 && type == 0) cout << " Bb Major" << endl;
if(count == -3 && type == 0) cout << " Eb Major" << endl;
if(count == -4 && type == 0) cout << " Ab Major" << endl;
if(count == -5 && type == 0) cout << " Db Major" << endl;
if(count == -6 && type == 0) cout << " Gb Major" << endl;
if(count == -7 && type == 0) cout << " Cb Major" << endl;
if(count == -1 && type == 1) cout << " D Minor" << endl;
if(count == -2 && type == 1) cout << " G Minor" << endl;
if(count == -3 && type == 1) cout << " C Minor" << endl;
if(count == -4 && type == 1) cout << " F Minor" << endl;
if(count == -5 && type == 1) cout << " Bb Minor" << endl;
if(count == -6 && type == 1) cout << " Eb Minor" << endl;
if(count == -7 && type == 1) cout << " Ab Minor" << endl;
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
bool MIDIFileReader::SequencerSpecific(void)
{
cout << " SEQUENCER SPECIFIC" << endl;
// read data
uint32 META_length = read_vlq();
uint08* META_data = (META_length ? new uint08[META_length + 1] : 0);
if(META_length) file.read((char*)META_data, META_length);
// set data
// cleanup
delete[] META_data;
META_data = 0;
return true;
}
//
// CHANNEL EVENTS
//
bool MIDIFileReader::NoteOff(uint08 channel)
{
cout << " NOTE OFF EVENT" << endl;
// read parameters
unsigned char param1 = 0;
unsigned char param2 = 0;
file.read((char*)¶m1, 1);
file.read((char*)¶m2, 1);
return true;
}
bool MIDIFileReader::NoteOn(uint08 channel)
{
cout << " NOTE ON EVENT" << endl;
// read parameters
unsigned char param1 = 0;
unsigned char param2 = 0;
file.read((char*)¶m1, 1);
file.read((char*)¶m2, 1);
return true;
}
bool MIDIFileReader::KeyAfterTouch(uint08 channel)
{
cout << " KEY AFTER-TOUCH EVENT" << endl;
// read parameters
unsigned char param1 = 0;
unsigned char param2 = 0;
file.read((char*)¶m1, 1);
file.read((char*)¶m2, 1);
return true;
}
bool MIDIFileReader::ControlChange(uint08 channel)
{
cout << " CONTROL CHANGE EVENT" << endl;
// read parameters
unsigned char param1 = 0;
unsigned char param2 = 0;
file.read((char*)¶m1, 1);
file.read((char*)¶m2, 1);
return true;
}
bool MIDIFileReader::ProgramChange(uint08 channel)
{
cout << " PROGRAM CHANGE EVENT" << endl;
// read parameter
unsigned char param1 = 0;
file.read((char*)¶m1, 1);
return true;
}
bool MIDIFileReader::ChannelAfterTouch(uint08 channel)
{
cout << " CHANNEL AFTER-TOUCH EVENT" << endl;
// read parameter
unsigned char param1 = 0;
file.read((char*)¶m1, 1);
return true;
}
bool MIDIFileReader::PitchWheelChange(uint08 channel)
{
cout << " PITCH WHEEL CHANGE EVENT" << endl;
// read parameters
unsigned char param1 = 0;
unsigned char param2 = 0;
file.read((char*)¶m1, 1);
file.read((char*)¶m2, 1);
return true;
}
//
//
//
bool MIDIFileReader::read(const char* filename)
{
// open file
if(file.is_open()) return false;
if(!filename || !strlen(filename)) return false;
file.open(filename);
if(!file) return false;
// variables
uint32 MIDI_chunkID;
uint32 MIDI_chunksize;
uint16 MIDI_type;
uint16 MIDI_tracks;
uint16 MIDI_timedivision;
// read MIDI variables
file.read((char*)&MIDI_chunkID, sizeof(uint32));
file.read((char*)&MIDI_chunksize, sizeof(uint32));
file.read((char*)&MIDI_type, sizeof(uint16));
file.read((char*)&MIDI_tracks, sizeof(uint16));
file.read((char*)&MIDI_timedivision, sizeof(uint16));
// check file header
reverse_byte_order(&MIDI_chunkID, 1);
if(MIDI_chunkID != 0x4D546864) {
cout << "Invalid chunk ID." << endl;
return read_error();
}
reverse_byte_order(&MIDI_chunksize, 1);
if(MIDI_chunksize != 6) {
cout << "Invalid chunk size." << endl;
return read_error();
}
reverse_byte_order(&MIDI_type, 1);
if(MIDI_type > 1) {
cout << "Invalid MIDI file type (only type-0 and type-1 are supported)." << endl;
return read_error();
}
reverse_byte_order(&MIDI_tracks, 1);
if(MIDI_tracks == 0) {
cout << "Invalid numer of tracks." << endl;
return read_error();
}
reverse_byte_order(&MIDI_timedivision, 1);
int16 division_flag = MIDI_timedivision & 0x8000;
int16 division_data = MIDI_timedivision & 0x7FFF;
if(division_flag) cout << division_data << " frames per second." << endl;
else cout << division_data << " ticks per beat." << endl;
// loop tracks
for(;;)
{
// read track chunk ID
uint32 chunkID;
file.read((char*)&chunkID, sizeof(chunkID));
if(file.gcount() != sizeof(chunkID)) {
if(file.eof()) break;
else return read_error();
}
reverse_byte_order(&chunkID, 1);
if(chunkID != 0x4D54726B) return read_error("Invalid track chunk ID.");
// read track chunk size
uint32 chunksize;
file.read((char*)&chunksize, sizeof(chunksize));
if(file.gcount() != sizeof(chunksize)) return read_error();
reverse_byte_order(&chunksize, 1);
// valid track
cout << "TRACK" << endl;
end_of_track = false;
// read MIDI events
while(!end_of_track)
{
// read delta time
uint32 delta_time = read_vlq();
if(file.eof()) return true;
// read MIDI event command
unsigned char command = 0;
file.read((char*)&command, 1);
// process META event
if(command == 0xFF) {
if(!ReadMetaEvent()) return read_error();
}
// process CHANNEL event
else if(command < 0xF0) {
if(!ReadChannelEvent(command)) return read_error();
}
// process SYSTEM EXCLUSIVE event
else if(command == 0xF0 || command == 0xF7) {
return read_error("System exclusive events are not supported.");
}
// hmmm
else
cout << "Some other event." << endl;
}
}
cout << "Reading MIDI file done." << endl;
return true;
}

