/* * Impulse Tracker module file to MIDI converter * Chris Xiong 2017, 2020 * License: Expat (MIT) * * Files generated by this application are not meant to be played but to * be placed in a DAW and worked on later. * * General principle: * * One MIDI track for each inst in each channel. * * Process: * parse file -> read patterns -> first pass -> * playback simulation & convert -> * assembly -> post process -> save as MIDI * post process: turn off notes properly, tempo handling etc. * * Default effects mapping: * * Volume column (mostly unimplemented): * value -> MIDI note on velocity * panning -> MIDI CC 10 (pan) * (fine) vol up/down -> MIDI CC 11 (expression) * pitch slide -> MIDI pitch wheel * portamento to -> pitch wheel ~~ or MIDI CC 5 (portamento) (which?)~~ * vibrato -> MIDI CC 1 (modulation) * * Effect column: * A Set Speed -> handled by per-pattern conversion * B Jump to Ord -> handled by per-pattern conversion and assembler * C Break to Row -> handled by per-pattern conversion * D Sample Volume Slide -> MIDI CC 7 (volume) * E Portamento -> pitch wheel * F Portamento -> E * G Tone Portamento -> pitch wheel or MIDI CC 5 (portamento) (which?) * H Vibrato -> MIDI CC 1 (modulation) * I Tremor -> note on/off * J Arp -> per-pattern conversion * K Vol sld + vib -> D+H * L Vol sld + tp -> D+G * M Channel Vol -> MIDI CC 7 (volume) * N Channel Vol sld -> MIDI CC 7 (volume) * O Sample Offset -> - * P Pan sld -> MIDI CC 10 (pan) * Q Retrigger -> MIDI note on/off * R Tremolo -> - * S Special -> ad hoc * T Tempo -> MIDI tempo * U Fine Vibrato -> H * V Global Vol -> - * W Golbal Vol Slide -> - * X Set panning -> MIDI CC 10 (pan) * Y Panbrello -> - * Z MIDI Macro -> - * * Bugs: * (nothing listed here, but I'm sure there are more than two dozens of 'em) * * Future features?: * instrument max ticks from IT instrument * option for IT vol -> midi vel/midi expression * implement the reset of the vol/effect column effects */ #include #include #include #include #include #include #include #include #include #include const char* IMPHdr="IMPM"; uint16_t readSW(FILE* f) { uint16_t ret=0; for(int i=0;i<2;++i)ret|=(((uint16_t)fgetc(f))&0xFF)<<(i<<3); return ret; } uint32_t readDW(FILE* f) { uint32_t ret=0; for(int i=0;i<4;++i)ret|=(((uint32_t)fgetc(f))&0xFF)<<(i<<3); return ret; } class ITSample { public: int defvol; void readSampleStub(FILE *f) { fseek(f,0x13,SEEK_CUR); defvol=fgetc(f); } }; class ITInstrument { public: std::string name; double aenvmaxt=65536; int instrpbrange=24; int sampleref[120]; void readInstStub(FILE *f,FILE *mxtkf,int iid) { fseek(f,0x20,SEEK_CUR); char buf[27];buf[26]=0; fread(buf,1,26,f); name=std::string(buf); name+=" (instr #"+std::to_string(iid)+")"; printf("instr: %s\n",buf); fseek(f,0x20,SEEK_CUR);//sample references for(int i=0;i<120;++i) { fgetc(f); sampleref[i]=fgetc(f); } fseek(f,0x0F0,SEEK_CUR);//amp(vol) envelope int flg=fgetc(f); bool useenv=flg&1; bool decloop=flg&2; if(!useenv||decloop)aenvmaxt=65536; else { int n=fgetc(f); readDW(f); for(int i=0;i cells; private: ITRow(ITContainer *c,ITPattern *pat); public: ITCell& operator [](size_t s){if(s<64)return cells[s];} friend class ITPattern; }; class ITPattern { private: std::vector rows; uint16_t len,nrows; std::vector lastval; public: ITPattern(ITContainer *c,int p); ITRow& operator [](size_t s){if(s instr; std::vector sample; std::vector patterns; public: ITContainer(const char* path) { printf("loading file %s...\n",path); char buf[32]; f=fopen(path,"rb"); if(!f) throw std::runtime_error("cannot open file"); if(fread(buf,1,4,f)!=4) throw std::runtime_error("unexpected EOF"); if(strncmp(buf,IMPHdr,4)) throw std::runtime_error("wrong impulse header"); fread(buf,1,26,f); printf("song name: %s\n",buf); title=std::string(buf); fseek(f,2,SEEK_CUR);//PHiligt cord=readSW(f);cins=readSW(f);csmp=readSW(f);cpat=readSW(f); crv=readSW(f);cmv=readSW(f);flag=readSW(f);flagsp=readSW(f); printf("created with impulse tracker %x.%02x\n", crv>>8,crv&0xff); printf("format version: %x.%02x\n",cmv>>8,cmv&0xff); fseek(f,2,SEEK_CUR);//GV,MV initspeed=fgetc(f); inittempo=fgetc(f); printf("init speed/tempo: %d %d\n",initspeed,inittempo); fseek(f,2,SEEK_CUR);//Sep,PWD msgl=readSW(f);msgp=readDW(f); fseek(f,4,SEEK_CUR);//Reserved fread(chp,1,64,f); fread(chv,1,64,f); ord=new uint8_t[cord]; fread(ord,1,cord,f); pins=new uint32_t[cins]; fread(pins,4,cins,f); psmp=new uint32_t[csmp]; fread(psmp,4,csmp,f); ppat=new uint32_t[cpat]; fread(ppat,4,cpat,f); fseek(f,msgp,SEEK_SET); char* msg=new char[msgl+1]; fread(msg,1,msgl,f); msg[msgl]=0; for(uint16_t i=0;if);) { uint8_t ch=(chmarker-1)&0x3f; if(chmarker>>7) pat->lastval[ch].mask=cells[ch].mask=fgetc(c->f); else cells[ch].mask=pat->lastval[ch].mask; if(cells[ch].mask&0x01) pat->lastval[ch].note=cells[ch].note=fgetc(c->f); if(cells[ch].mask&0x02) pat->lastval[ch].inst=cells[ch].inst=fgetc(c->f); if(cells[ch].mask&0x04) pat->lastval[ch].vol=cells[ch].vol=fgetc(c->f); if(cells[ch].mask&0x08) { pat->lastval[ch].efx=cells[ch].efx=fgetc(c->f); pat->lastval[ch].fxp=cells[ch].fxp=fgetc(c->f); } if(cells[ch].mask&0x10) cells[ch].note=pat->lastval[ch].note; if(cells[ch].mask&0x20) cells[ch].inst=pat->lastval[ch].inst; if(cells[ch].mask&0x40) cells[ch].vol=pat->lastval[ch].vol; if(cells[ch].mask&0x80) { cells[ch].efx=pat->lastval[ch].efx; cells[ch].fxp=pat->lastval[ch].fxp; } } } ITPattern::ITPattern(ITContainer *c,int p) { printf("loading pattern %d\n",p); fseek(c->f,c->ppat[p],SEEK_SET); len=readSW(c->f); nrows=readSW(c->f); readDW(c->f); long pos=ftell(c->f); lastval.resize(64); for(size_t i=0;if)-pos!=len) { printf("length mismatch: %u<->%u\n",ftell(c->f)-pos,len); throw std::runtime_error("length mismatch"); } } void ITPattern::dumpPattern() { for(size_t i=0;i=120) printf("~~ "); else printf("%2.2s%1d ",notes+(rows[i].cells[ch].note%12)*2,rows[i].cells[ch].note/12); } else printf("... "); if(rows[i].cells[ch].inst) printf("%02d ",rows[i].cells[ch].inst); else printf(".. "); if(rows[i].cells[ch].vol) printf("%03d ",rows[i].cells[ch].vol); else printf("... "); if(rows[i].cells[ch].efx) printf("%c%02x|",'A'-1+rows[i].cells[ch].efx,rows[i].cells[ch].fxp); else printf("...|"); } puts(""); } } class ITCellAction { public: virtual void action( uint8_t ch, uint8_t mask, uint8_t note, uint8_t inst, uint8_t vol, uint8_t efx, uint8_t fxp )=0; virtual ~ITCellAction(){} }; class ITPlayer { private: ITContainer& c; ITCellAction* a; uint8_t pmsk[64],pnt[64],pinst[64],pvol[64],pefx[64],pfxp[64]; int skpord,skprow; void processPattern(size_t pat,uint16_t _skprow=0) { printf("process pattern %u\n",pat); skprow=-1; for(uint16_t i=_skprow;iaction(ch,msk>>4|msk,note,inst,vol,efx,fxp); } a->action(0xFF,0,0,0,0,0,0); if(~skpord||~skprow)return; } } public: ITPlayer(ITContainer &_c,ITCellAction *_a=NULL):c(_c),a(_a) { skpord=skprow=-1; for(uint16_t i=0;i<64;++i)pmsk[i]=pnt[i]=pinst[i]=pvol[i]=pefx[i]=pfxp[i]=0; for(uint16_t i=0;i16383)val=16383; return MidiEvent(t,0xE0|ch,val&0x7F,val>>7); } static MidiEvent tempo(uint32_t t,double tmpo) { int us=60000000./tmpo; char c[4]; c[0]=us>>16&0xFF;c[1]=us>>8&0xFF;c[2]=us&0xFF;c[3]=0; return MidiEvent(t,0xFF,0x51,0x03,c); } static MidiEvent tsig(uint32_t t,uint32_t n,uint32_t pot_d) { char c[5]; c[0]=n;c[1]=pot_d;c[2]=24;c[3]=8;c[4]=0; return MidiEvent(t,0xFF,0x58,0x04,c); } static MidiEvent trkname(uint32_t t,const char* s) { return MidiEvent(t,0xFF,0x03,strlen(s),s); } }; struct MidiTrack { std::vector eventList; }; class MidiFile { private: FILE* f; void writeDWBE(uint32_t v,FILE* f) { for(int i=3;i>=0;--i) fputc(v>>(i<<3)&0xFF,f); } void writeSWBE(uint16_t v,FILE* f) { for(int i=1;i>=0;--i) fputc(v>>(i<<3)&0xFF,f); } void dumpVL(uint32_t v,std::vector &d) { if(v>0x0FFFFFFF)throw std::runtime_error("VL overflow"); uint32_t sh=4*7; while(sh&&!(v>>sh))sh-=7; for(;sh>0;sh-=7)d.push_back(((v>>sh)&0x7F)|0x80); d.push_back(v&0x7f); } void writeTrack(const MidiTrack &tr) { std::vector buf; fputs("MTrk",f); uint32_t curt=0,lastst=0; for(const MidiEvent &j:tr.eventList) { size_t ip=buf.size(); if(j.type<0xF0) { dumpVL(j.time-curt,buf); curt=j.time; if(lastst!=j.type) buf.push_back(lastst=j.type); buf.push_back(j.p1); if((j.type&0xF0)!=0xC0&&(j.type&0xF0)!=0xD0) buf.push_back(j.p2); } else { dumpVL(j.time-curt,buf); curt=j.time; buf.push_back(lastst=j.type); buf.push_back(j.p1); dumpVL(j.p2,buf); for(uint32_t i=0;i tracks; void dumpFile() { puts("Midi File dump"); for(MidiTrack &i:tracks) { puts("==============track=============="); for(MidiEvent &j:i.eventList) if(j.str.length()) printf("type %x @%x p1 %x p2 %x str %s\n",j.type, j.time,j.p1,j.p2,j.str.c_str()); else printf("type %x @%x p1 %x p2 %x\n",j.type, j.time,j.p1,j.p2); } } void writeFile(const char* path,std::vector tracksw={}) { for(MidiTrack &i:tracks) std::stable_sort(i.eventList.begin(),i.eventList.end(), [](const MidiEvent& a,const MidiEvent& b)->bool{ return a.time,int> minstch; MidiFile f; ITPlayer *p; uint8_t speed,tempo; uint8_t chnote[64],chinst[64],chvol[64],chefx[64],chfxp[64]; uint8_t chvelm[64],chvolm[64],chpanm[64]; uint8_t chefxmem[64][32]={0}; uint8_t chefxflg[64][32]={0}; int chportasrcnote[64]={0},chportadstnote[64]={0}; double chpitchm[64]={0}; double chage[64]; uint32_t currow,curmiditk; class ITCellActionPre:public ITCellAction { private: ITConverter* par; public: ITCellActionPre(ITConverter *_p):par(_p){} void action( uint8_t ch, uint8_t mask, uint8_t note, uint8_t inst, uint8_t vol, uint8_t efx, uint8_t fxp ) { if(mask>>1&1) { if(inst)par->chinst[ch]=inst; par->minstch[std::make_pair(ch,inst)]=0; } if(mask>>0&1)//note { if(!inst)inst=par->chinst[ch]; if(note<120)par->minstch[std::make_pair(ch,inst)]=0; } } }; class ITCellActionConv:public ITCellAction { private: ITConverter* par; public: ITCellActionConv(ITConverter *_p):par(_p){} void action( uint8_t ch, uint8_t mask, uint8_t note, uint8_t inst, uint8_t vol, uint8_t efx, uint8_t fxp ) { if(ch==0xFF) { ++par->currow; par->curmiditk+=40*par->speed; for(uint8_t i=0;i<64;++i) if(par->chnote[i]!=255) { par->chage[i]+=2.5/par->tempo*par->speed; if(par->c.instr[par->chinst[i]].aenvmaxtchage[i]) { par->f.tracks[par->minstch[std::make_pair(i,par->chinst[i])]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk,0,par->chnote[i])); par->chnote[i]=255; } } return; } uint8_t previnst=par->chinst[ch]; if(mask>>1&1)//inst { if(inst)par->chinst[ch]=inst; } if(mask>>2&1)//vol { if(vol<=64)par->chvelm[ch]=(vol==64?127:2*vol); else if(vol>=128&&vol<=192) { par->chpanm[ch]=(vol==192?127:(vol-128)*2); par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::cc(par->curmiditk,0,10,par->chpanm[ch])); } } else { //default volume uint32_t curnote=par->chnote[ch]; if(mask>>0&1)curnote=note; if(curnote<120) par->chvelm[ch]=par->c.sample[par->c.instr[par->chinst[ch]].sampleref[curnote]-1].defvol*2; if(par->chvelm[ch]>127) par->chvelm[ch]=127; } if((mask>>0&1)&¬e<120)//reset for pitch slides (E/F/G) { if(fabs(par->chpitchm[ch])>1e-6) { par->chpitchm[ch]=0; par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::pb(par->curmiditk,0,8192)); } } int notedelay=0; if(mask>>3&1)//efx { switch(efx) { case 1://speed if(fxp)par->speed=fxp; break; case 2://jump par->p->skipPattern(fxp); break; case 3://break par->p->skipPattern(-1,fxp); break; case 4://vol slide if(fxp) par->chefxmem[ch][efx]=fxp; else fxp=par->chefxmem[ch][efx]; //unimplemented break; case 5://pitch down if(fxp) par->chefxmem[ch][efx]=fxp; else fxp=par->chefxmem[ch][efx]; { double granularity=fxp/16.; if(fxp>=0xe0) granularity=(fxp&0x0f)/(fxp>=0xf0?16.:64.); int runfor=fxp>=0xe0?1:par->speed; for(int tk=0;tkchpitchm[ch]-=granularity; double pbv=par->chpitchm[ch]/par->c.instr[par->chinst[ch]].instrpbrange; if(pbv>1)pbv=1; if(pbv<-1)pbv=-1; uint32_t pbvi=int(pbv*8192)+8192; par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::pb(40*tk+par->curmiditk,0,pbvi)); } } break; case 6://pitch up { if(fxp) par->chefxmem[ch][efx]=fxp; else fxp=par->chefxmem[ch][efx]; { double granularity=fxp/16.; if(fxp>=0xe0) granularity=(fxp&0x0f)/(fxp>=0xf0?16.:64.); int runfor=fxp>=0xe0?1:par->speed; for(int tk=0;tkchpitchm[ch]+=granularity; double pbv=par->chpitchm[ch]/par->c.instr[par->chinst[ch]].instrpbrange; if(pbv>1)pbv=1; if(pbv<-1)pbv=-1; uint32_t pbvi=int(pbv*8192)+8192; par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::pb(40*tk+par->curmiditk,0,pbvi)); } } } break; case 7://porta if(fxp) { par->chefxmem[ch][efx]=fxp; //if(!(mask>>0&1)||note>=120) // puts("no note to slide to?"); par->chportasrcnote[ch]=par->chnote[ch]; par->chportadstnote[ch]=note; } else fxp=par->chefxmem[ch][efx]; for(int tk=0;tkspeed;++tk) { if(fabs(par->chpitchm[ch]+par->chportasrcnote[ch]-par->chportadstnote[ch])<1e-6) break; int portadir=(par->chpitchm[ch]+par->chportasrcnote[ch])>par->chportadstnote[ch]?-1:1; double nextpitchm=par->chpitchm[ch]+1.*portadir*fxp/16.; int nextportadir=(nextpitchm+par->chportasrcnote[ch])>par->chportadstnote[ch]?-1:1; if(nextportadir*portadir<0)//detect overshoot par->chpitchm[ch]=par->chportadstnote[ch]-par->chportasrcnote[ch]; else par->chpitchm[ch]+=1.*portadir*fxp/16.; double pbv=par->chpitchm[ch]/par->c.instr[par->chinst[ch]].instrpbrange; if(pbv>1)pbv=1; if(pbv<-1)pbv=-1; uint32_t pbvi=int(pbv*8192)+8192; par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::pb(40*tk+par->curmiditk,0,pbvi)); } break; case 8://vib par->chefxflg[ch][efx]=1; if(fxp) par->chefxmem[ch][efx]=fxp; else { //fxp=par->chefxmem[ch][efx]; //actually midi has us covered already, //no need to save the memory manually. break; } //vibrato rate is currently ignored, even it is supported on some synths as CC76 par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::cc(par->curmiditk,0,1,(fxp&0x0f)*8)); break; case 9://tremor if(fxp) par->chefxmem[ch][efx]=fxp; else fxp=par->chefxmem[ch][efx]; break; //unimplemented case 10://arp { if(fxp) par->chefxmem[ch][efx]=fxp; else fxp=par->chefxmem[ch][efx]; uint32_t curnote=par->chnote[ch]; if(mask>>0&1)curnote=note; if(curnote<120) { par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk+40*par->speed/3,0,curnote)); par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOn(par->curmiditk+40*par->speed/3,0,curnote+(fxp>>4),par->chvelm[ch])); par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk+80*par->speed/3,0,curnote+(fxp>>4))); par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOn(par->curmiditk+80*par->speed/3,0,curnote+(fxp&0x0F),par->chvelm[ch])); par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk+120*par->speed/3,0,curnote+(fxp&0x0F))); par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOn(par->curmiditk+120*par->speed/3,0,curnote,par->chvelm[ch])); } } break; case 19://Sxx controls switch(fxp&0xf0) { case 0xc0://note cut { uint8_t param=fxp&0x0f; if(!param)param=1; uint32_t curnote=par->chnote[ch]; if(mask>>0&1)curnote=note; if(curnote<120) par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk+40*param,0,curnote)); } break; case 0xd0://note delay { uint8_t param=fxp&0x0f; if(!param)param=1; notedelay=40*param; } break; } break; case 20://tempo { static uint8_t tempop=0; if(fxp>0x20) { par->tempo=fxp; par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk,fxp)); } else { if(fxp==0x00)fxp=tempop; if(fxp>0x10) { tempop=fxp; for(int i=1;ispeed;++i) par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk+i*40,par->tempo+=fxp&0x0F)); } else if(fxp>0x00) { tempop=fxp; for(int i=1;ispeed;++i) par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk+i*40,par->tempo-=fxp&0x0F)); } } } break; case 24://set pan { uint64_t param=fxp/2; if(param>127)param=127; if(!par->chinst[ch]||par->minstch.find(std::make_pair(ch,par->chinst[ch]))==par->minstch.end()) { //set panning for all midi channels associated with this IT channel } else par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::cc(par->curmiditk,0,10,param)); } break; } } //stop non-active effects for(int i=0;i<20;++i) { if((!(mask>>3&1)||((mask>>3&1)&&efx!=i))&&par->chefxflg[ch][i]) { par->chefxflg[ch][i]=0; switch(i) { case 8: par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::cc(par->curmiditk,0,1,0)); break; } } } if(mask>>0&1)//note { if(!inst)inst=par->chinst[ch]; if(note<120&&par->minstch.find(std::make_pair(ch,inst))==par->minstch.end()) { printf("instr: %d @ ch %d note %d row %d\n",inst,ch,note,par->currow); throw std::runtime_error("wtf instrument???"); } if(par->chnote[ch]!=255&¬e<120&&efx!=7) par->f.tracks[par->minstch[std::make_pair(ch,previnst)]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk,0,par->chnote[ch])); if(note>=120) { if(par->chnote[ch]<128) par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk,0,par->chnote[ch])); par->chnote[ch]=255; } else { if(efx!=7) { par->chnote[ch]=note;par->chage[ch]=0; par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. eventList.push_back(MidiEvent::noteOn(par->curmiditk+notedelay,0,note,par->chvelm[ch])); } } } } }; public: ITConverter(ITContainer &_c,const char* path,bool single_instr_mode=false):c(_c) { ITCellActionPre* cap=new ITCellActionPre(this); for(int i=0;i<64;++i)chinst[i]=1; p=new ITPlayer(c,cap); delete p;delete cap; int trcnt=1; //track 0 is reserved for song title, speed changes etc for(auto &i:minstch) { i.second=trcnt++; printf("%d, %d -> %d\n",i.first.first,i.first.second,i.second); } for(int i=0;i<64;++i)chvelm[i]=chvolm[i]=100,chpanm[i]=64,chnote[i]=255,chinst[i]=1; speed=c.initspeed,tempo=c.inittempo; currow=curmiditk=0; f.divs=960; f.tracks.push_back(MidiTrack()); for(auto i=minstch.begin();i!=minstch.end();++i) { f.tracks.push_back(MidiTrack()); char buf[48]; sprintf(buf,"%s @ ch%d",c.instr[i->first.second].name.c_str(),i->first.first); f.tracks.back().eventList.push_back(MidiEvent::trkname(0,buf)); f.tracks.back().eventList.push_back(MidiEvent::cc(0,0,0x65,0)); f.tracks.back().eventList.push_back(MidiEvent::cc(0,0,0x64,0)); f.tracks.back().eventList.push_back(MidiEvent::cc(0,0,0x06,c.instr[i->first.second].instrpbrange)); } f.tracks[0].eventList.push_back(MidiEvent::trkname(0,c.title.c_str())); f.tracks[0].eventList.push_back(MidiEvent::tsig(0,1,2)); f.tracks[0].eventList.push_back(MidiEvent::tempo(0,tempo)); ITCellActionConv* cac=new ITCellActionConv(this); p=new ITPlayer(c,cac); for(int i=0;i<64;++i)//turn off any remaining notes if(chnote[i]!=255) f.tracks[minstch[std::make_pair(i,chinst[i])]]. eventList.push_back(MidiEvent::noteOff(curmiditk,0,chnote[i])); f.dumpFile(); for(auto i=f.tracks.begin()+1;i!=f.tracks.end();) { bool df=false; if(!i->eventList.size())df=true; else { df=true; for(auto &j:i->eventList) if((j.type&0xF0)==0x90)df=false; } if(df){auto j=i+1;f.tracks.erase(i);i=j;} else ++i; } printf("final file has %lu tracks.\n",f.tracks.size()); if(single_instr_mode) { for(int instr=1;instr<=c.cins;++instr) { std::vector tr={0}; for(auto &pr:minstch) if(pr.first.second==instr) tr.push_back(pr.second); if(tr.size()==1) { printf("instr#%d seems unused, skipped\n",instr); continue; } f.writeFile((std::string(path)+"."+std::to_string(instr)+".mid").c_str(),tr); } } else f.writeFile(path); delete p;delete cac; } }; int main() { ITContainer it("modulefile.it"); ITConverter itc(it,"output.mid"); return 0; }