Current MS Students/Steven Emory/CS599 Page

From CSWiki

Jump to: navigation, search

[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*)&param1, 1);
 file.read((char*)&param2, 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*)&param1, 1);
 file.read((char*)&param2, 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*)&param1, 1);
 file.read((char*)&param2, 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*)&param1, 1);
 file.read((char*)&param2, 1);

 return true;
}

bool MIDIFileReader::ProgramChange(uint08 channel)
{
 cout << "    PROGRAM CHANGE EVENT" << endl;

 // read parameter
 unsigned char param1 = 0;
 file.read((char*)&param1, 1);

 return true;
}

bool MIDIFileReader::ChannelAfterTouch(uint08 channel)
{
 cout << "    CHANNEL AFTER-TOUCH EVENT" << endl;

 // read parameter
 unsigned char param1 = 0;
 file.read((char*)&param1, 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*)&param1, 1);
 file.read((char*)&param2, 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;
}
Personal tools