// mid2tgmDoc.cpp : implementation of the CMid2tgmDoc class // // comments about the midi format labeled with a plus taken from the document //+ Standard MIDI File Format //by //+ Dustin Caldwell // found at http://www.wotsit.org/ // There is a CString called Text which is what the window displays //#define verboseout void CMid2tgmDoc::OnFileImportmidi() { Text.Empty(); if (infile.is_open()==0) { CString Msg,TdsPathName; // TODO: Add your command handler code here CFileDialog aDlg(true,"Mid","*.mid",OFN_OVERWRITEPROMPT, "Midi File(*.mid)|*.mid||"); if (IDOK==aDlg.DoModal()) { TdsPathName=aDlg.GetPathName(); //start parsing the elements out to the POV file infile.open(TdsPathName,ios::binary|ios::nocreate|ios::in); if (infile.fail()){ Text+="Unable to open file\r\n"; }else { //read the file ParseMidi(); infile.close(); }//endif-able to open infile }//endif- load box domodal }//opened infile already UpdateAllViews(NULL); } bool CMid2tgmDoc::floatequal(float float1, float float2, float eps) { if (fabs((double)(float1-float2))<(double)eps) return true; return false; } DWORD CMid2tgmDoc::ReadChunk() { DWORD RetVal; RetVal=((DWORD)infile.get())<<24; RetVal|=((DWORD)infile.get())<<16; RetVal|=((DWORD)infile.get())<<8; RetVal|=(DWORD)infile.get(); return RetVal; } WORD CMid2tgmDoc::ReadWord() { WORD RetVal; RetVal=((WORD)infile.get())<<8; RetVal|=(WORD)infile.get(); return RetVal; } void CMid2tgmDoc::ParseMidi() //+ A midi (.MID) file contains basically 2 things, Header chunks and Track //+chunks. A midi file contains ONE header chunk describing the file format, //+etc., and any number of track chunks. A track may be thought of in the same //+way as a track on a multi-track tape deck. You may assign one track to each //+voice, each staff, each instrument or whatever you want. { DWORD Chunk; CString Temp; TPQ=1; Trackcount=0; while (Text.GetLength()<60000 && infile.eof()==0) { Chunk=ReadChunk(); switch (Chunk) { case 0x4D546864: ParseHeader();//MThd break; case 0x4D54726B: ParseTrack();//MTrk break; default: // Temp.Format("unknmid:%08X",Chunk); // Text+=Temp; break; } } } void CMid2tgmDoc::ParseHeader() //+ The header chunk appears at the beginning of the file, and describes the //+ file in three ways. The header chunk always looks like: //+ 4D 54 68 64 00 00 00 06 ff ff nn nn dd dd //+ The ascii equivalent of the first 4 bytes is MThd. After MThd comes the 4-byte //+ size of the header. This will always be 00 00 00 06, because the actual header //+ information will always be 6 bytes. //+ ff ff is the file format. There are 3 formats: //+ 0 - single-track //+ 1 - multiple tracks, synchronous //+ 2 - multiple tracks, asynchronous //+ Single track is fairly self-explanatory - one track only. Synchronous multiple //+ tracks means that the tracks will all be vertically synchronous, or in other //+ words, they all start at the same time, and so can represent different parts //+ in one song. Asynchronous multiple tracks do not necessarily start at the same //+ time, and can be completely asynchronous. //+ nn nn is the number of tracks in the midi file. //+ dd dd is the number of delta-time ticks per quarter note. (More about this //+ later) //this program can't deal with asynchronous multitrack midis, synch is assumed { DWORD Size; CString Temp; WORD Format,Tracks; Size=ReadChunk(); if (Size!=6) { //size should be six, if larger, eat up the unknown bits for (DWORD x=0;x= 128). A list of most of these commands is in appendix A. Each //+ command has different parameters and lengths, but the data that follows the //+ command will have a msb of 0 (less than 128). The exception to this is a meta- //+ event, which may contain data with a msb of 1. However, meta-events require a //+ length parameter which alleviates confusion. //+ One subtlety which can cause confusion is running mode. This is where //+ the actual midi command is omitted, and the last midi command issued is //+ assumed. This means that the midi event will consist of a delta-time and the //+ parameters that would go to the command if it were included. { DWORD Size,Curr,LineLength,TrackLength; float Time; CString Temp; char Event,Data1,Data2,x; char Command,Length; WORD Volume,Pan,LastPan,LastVolume,LastVelocity; bool TrackHasNotes; Time=0; LastTime=-.25f; LastCommand=0; LastNote=0; LineLength=0; TrackLength=0; LastVolume=Volume=0xFF; LastPan=Pan=0x40; LastVelocity=0x7F; Size=ReadChunk(); TrackHasNotes=false;//some tracks don't have notes, since we don't want empty arrays, this bool is used Curr=0;//curr is used to make sure the entire track is read, should there be unknown parts while(Curr>4)&0xF) { case 0x9://+ 9x 1001xxxx nn vv Note on Curr+=2; Data1=infile.get(); Data2=infile.get(); if (Data2!=0) { if (TrackHasNotes==false) { Temp.Format("const char Track%i[]={\r\n ",Trackcount); Text+=Temp; TrackHasNotes=true; } //notes appear whenever they are pressed which means irregular intervals, //but we need to show them at regular intervals so the while loops output the currently playing note while (LastTime<(Time-.26f)){//hardcoded frequency is .25 VolR=VolL=15.9f*(((float)LastVelocity)/127.0f); if (Pan<0x40) { VolL*=(((float)Pan)/64.0f); } if (Pan>0x40) { VolR*=(((float)(0x80-Pan))/64.0f); } Temp.Format("0x%02X",LastNote, ((((WORD)VolL)&0xF)<<4)|(((WORD)VolR)&0xF) ); Text+=Temp; LastTime+=.25f; if (LineLength>=10) { Text+="\r\n "; LineLength=0; } Text+=','; LineLength++; TrackLength++; } if (LastTime<(Time-.24f)) {//hardcoded frequency is .25 VolR=VolL=15.9f*(((float)Data2)/127.0f); if (Pan<0x40) { VolL*=(((float)Pan)/64.0f); } if (Pan>0x40) { VolR*=(((float)(0x80-Pan))/64.0f); } Temp.Format("0x%02X",Data1,((((WORD)VolL)&0xF)<<4)|(((WORD)VolR)&0xF)); Text+=Temp; if (LineLength>=10) { Text+="\r\n "; LineLength=0; } Text+=','; LastNote=Data1; LastTime=Time; LastVelocity=Data2; LastVolume=Volume; LastPan=Pan; }else{ #ifdef verboseout Temp.Format("IgNt ");//Ignored Note can happen if notes are faster than the update frequency Text+=Temp; #endif LastNote=Data1; } LineLength++; TrackLength++; }else{ #ifdef verboseout Text+="NS "; #endif while (LastTime<(Time-.26f)){//hardcoded frequency is .25 VolR=VolL=15.9f*(((float)LastVelocity)/127.0f); if (Pan<0x40) { VolL*=(((float)Pan)/64.0f); } if (Pan>0x40) { VolR*=(((float)(0x80-Pan))/64.0f); } Temp.Format("0x%02X",LastNote,((((WORD)VolL)&0xF)<<4)|(((WORD)VolR)&0xF)); Text+=Temp; LastTime+=.25f; if (LineLength>=10) {//hardcoded frequency is .25 Text+="\r\n "; LineLength=0; } Text+=','; LineLength++; TrackLength++; } LastNote=0; LastVelocity=Data2; LastVolume=Volume; LastPan=Pan; } break; case 0x8: //+ 8x 1000xxxx nn vv Note off //sometimes sequencers use note on with velocity 0 instead of note off Curr+=2; Data1=infile.get(); Data2=infile.get(); while (LastTime<(Time-.26f)){ VolR=VolL=15.9f*(((float)LastVelocity)/127.0f); if (Pan<0x40) { VolL*=(((float)Pan)/64.0f); } if (Pan>0x40) { VolR*=(((float)(0x80-Pan))/64.0f); } Temp.Format("0x%02X",LastNote,((((WORD)VolL)&0xF)<<4)|(((WORD)VolR)&0xF)); Text+=Temp; if (LineLength>=10) { Text+="\r\n "; LineLength=0; } Text+=','; LastTime+=.25f; LineLength++; TrackLength++; } LastNote=0; LastVelocity=Data2; LastVolume=Volume; LastPan=Pan; #ifdef verboseout Temp.Format("NO "); Text+=Temp; #endif break; case 0xA://+ Ax 1010xxxx nn vv Key after-touch Curr+=2; Data1=infile.get(); Data2=infile.get(); #ifdef verboseout Temp.Format("A "); Text+=Temp; #endif break; case 0xB://+ Bx 1011xxxx cc vv Control Change Curr+=2; Data1=infile.get(); Data2=infile.get(); if (Data1==0x7) { Volume=Data2; } if (Data1==0xA) { Pan=Data2; } #ifdef verboseout Temp.Format("CC:%02x:%02x ",Data1,Data2); Text+=Temp; #endif break; case 0xC://+Cx 1100xxxx pp Program (patch) change Curr++; Data1=infile.get(); #ifdef verboseout Temp.Format("P "); Text+=Temp; #endif break; case 0xD://+ Dx 1101xxxx cc Channel after-touch Curr++; Data1=infile.get(); #ifdef verboseout Temp.Format("CA "); Text+=Temp; #endif break; case 0xE://+Ex 1110xxxx bb tt Pitch wheel change Curr+=2; Data1=infile.get(); Data2=infile.get(); #ifdef verboseout Temp.Format("Pitch "); Text+=Temp; #endif break; case 0xF: //meta event or system event switch(Event&0xF) {//0xFF is meta event //+ meta-events which have no midi channel number. They //+ are of the format: //+ FF xx nn dd //+ All meta-events start with FF followed by the command (xx), the length, or //+ number of bytes that will contain data (nn), and the actual data (dd). case 0x8: case 0xA: case 0xB: case 0xC: #ifdef verboseout Text+="SystemControlMsg "; #endif break; case 0xF: Curr+=2; Command=infile.get(); Length=infile.get(); switch (Command) { case 0x00://+ 00 00000000 nn ssss Sets the track's sequence number Curr+=2; infile.get();//2 byte Data1=infile.get(); Data2=infile.get(); #ifdef verboseout Temp.Format("Sequence:%02%02X ",Data1,Data2); Text+=Temp; #endif break; case 0x01://text case 0x02://copyright case 0x03://trackname case 0x04://instrument name case 0x05://lyric case 0x06://marker case 0x07://cue -all these are comments #ifdef verboseout Text+="comment:"; #endif Curr+=Length; for (x=0;x