From d8322e561c25b24504fc5787b10cfcdf525f1442 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Mon, 8 Jun 2020 23:29:56 +0800 Subject: Public release of IT2MIDI. Please do not expect this to work for you, because "it works for me" (TM). --- music/it2midi.cpp | 1011 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1011 insertions(+) create mode 100644 music/it2midi.cpp diff --git a/music/it2midi.cpp b/music/it2midi.cpp new file mode 100644 index 0000000..2fe3b26 --- /dev/null +++ b/music/it2midi.cpp @@ -0,0 +1,1011 @@ +/* + * 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; +} -- cgit v1.2.3