diff options
-rw-r--r-- | music/it2midi.cpp | 171 |
1 files changed, 140 insertions, 31 deletions
diff --git a/music/it2midi.cpp b/music/it2midi.cpp index f31c8b2..5bebc3d 100644 --- a/music/it2midi.cpp +++ b/music/it2midi.cpp @@ -25,6 +25,9 @@ * portamento to -> pitch wheel ~~ or MIDI CC 5 (portamento) (which?)~~ * vibrato -> MIDI CC 1 (modulation) * + * tempo stuff: + * 1 it tick <==> 40 midi ticks, converted file is always 960t/div + * * Effect column: * A Set Speed -> handled by per-pattern conversion * B Jump to Ord -> handled by per-pattern conversion and assembler @@ -60,6 +63,8 @@ * instrument max ticks from IT instrument * option for IT vol -> midi vel/midi expression * implement the reset of the vol/effect column effects + * custom controller mapping for each instrument + * */ #include <cstdio> #include <cstring> @@ -67,10 +72,15 @@ #include <cmath> #include <stdexcept> #include <algorithm> +#include <functional> #include <map> #include <string> #include <utility> #include <vector> +const int debuggxx=0; +const int disablegxx=0; +const int pretendspeed=6;//set to 3 to force standarized speed, -1 to disable +const int pretendswing=-1;//set to non -1 value to force a swing speed value in timing calculations (e.g. set to 5 for modules with A02/A03 swing). we don't support changing it midways in this version const char* IMPHdr="IMPM"; uint16_t readSW(FILE* f) { @@ -146,6 +156,7 @@ class ITRow { std::vector<ITCell> cells; private: + ITRow(){} ITRow(ITContainer *c,ITPattern *pat); public: ITCell& operator [](size_t s){if(s<64)return cells[s];} @@ -251,7 +262,7 @@ class ITContainer for(uint16_t i=0;i<cpat;++i) { patterns.push_back(ITPattern(this,i)); - //patterns.back().dumpPattern(); +// patterns.back().dumpPattern(); } } ~ITContainer() @@ -307,6 +318,15 @@ ITPattern::ITPattern(ITContainer *c,int p) readDW(c->f); long pos=ftell(c->f); lastval.resize(64); + if(!c->ppat[p]) + { + for(size_t i=0;i<nrows;++i) + { + rows.push_back(ITRow()); + rows.back().cells.resize(64); + } + return; + } for(size_t i=0;i<nrows;++i) rows.push_back(ITRow(c,this)); if(ftell(c->f)-pos!=len) @@ -424,6 +444,11 @@ struct MidiEvent time=_t;type=_tp;p1=_p1;p2=_p2; if(s)str=std::string(s);else str=""; } + MidiEvent(uint32_t _t,uint32_t _tp,uint32_t _p1,uint32_t _p2,std::string s) + { + time=_t;type=_tp;p1=_p1;p2=_p2; + str=s; + } static MidiEvent noteOff(uint32_t t,uint32_t ch,uint32_t note,uint32_t vel=0x40) { if(vel==0x40)return MidiEvent(t,0x90|ch,note,0); @@ -451,13 +476,13 @@ struct MidiEvent 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); + return MidiEvent(t,0xFF,0x51,0x03,std::string(c, 4)); } 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); + return MidiEvent(t,0xFF,0x58,0x04,std::string(c, 5)); } static MidiEvent trkname(uint32_t t,const char* s) { @@ -525,11 +550,14 @@ class MidiFile public: uint16_t divs; std::vector<MidiTrack> tracks; - void dumpFile() + void dumpFile(std::vector<size_t> tracksw={}) { puts("Midi File dump"); - for(MidiTrack &i:tracks) + if(tracksw.empty()) + for(size_t i=0;i<tracks.size();++i)tracksw.push_back(i); + for(size_t& ii:tracksw) { + MidiTrack &i=tracks[ii]; puts("==============track=============="); for(MidiEvent &j:i.eventList) if(j.str.length()) @@ -629,7 +657,7 @@ class ITConverter if(ch==0xFF) { ++par->currow; - par->curmiditk+=40*par->speed; + par->curmiditk+=~pretendspeed?pretendspeed*(~pretendswing?40.*par->speed/pretendswing:40):40*par->speed; for(uint8_t i=0;i<64;++i) if(par->chnote[i]!=255) { @@ -665,7 +693,11 @@ class ITConverter uint32_t curnote=par->chnote[ch]; if(mask>>0&1)curnote=note; if(curnote<120 && curnote>=12) - par->chvelm[ch]=par->c.sample[par->c.instr[par->chinst[ch]].sampleref[curnote-12]-1].defvol*2; + { + if (par->c.instr[par->chinst[ch]].sampleref[curnote-12]-1 < par->c.sample.size()) + par->chvelm[ch]=par->c.sample[par->c.instr[par->chinst[ch]].sampleref[curnote-12]-1].defvol*2; + else par->chvelm[ch]=127; + } if(par->chvelm[ch]>127) par->chvelm[ch]=127; if(par->chvelm[ch]==0) @@ -722,7 +754,7 @@ class ITConverter 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)); + eventList.push_back(MidiEvent::pb((~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed*tk:40*tk)+par->curmiditk,0,pbvi)); } } break; @@ -745,34 +777,43 @@ class ITConverter 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)); + eventList.push_back(MidiEvent::pb((~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed*tk:40*tk)+par->curmiditk,0,pbvi)); } } } break; case 7://porta + { + if(disablegxx)break; if(fxp) { par->chefxmem[ch][efx]=fxp; //if(!(mask>>0&1)||note>=120) // puts("no note to slide to?"); - if(par->chnote[ch]>=120)//Gxx without a prior note doesn't seem to have any effect? + if(par->chnote[ch]>=120 || previnst != par->chinst[ch])//Gxx without a prior note doesn't seem to have any effect? Also doesn't carry through inst changes? { efx=par->chefxmem[ch][efx]=0; break; } par->chportasrcnote[ch]=par->chnote[ch]; - par->chportadstnote[ch]=note; + if(mask&1) + par->chportadstnote[ch]=note; } else + { fxp=par->chefxmem[ch][efx]; + if(mask&1) + par->chportadstnote[ch]=note; + } + if(debuggxx)printf("src %d dst %d\n", par->chportasrcnote[ch], par->chportadstnote[ch]); for(int tk=0;tk<par->speed;++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; + if(debuggxx)printf("pitch %f\n",par->chpitchm[ch]); + 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; + 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 @@ -782,8 +823,9 @@ class ITConverter 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)); + eventList.push_back(MidiEvent::pb((~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed*tk:40*tk)+par->curmiditk,0,pbvi)); } + } break; case 8://vib par->chefxflg[ch][efx]=1; @@ -817,19 +859,63 @@ class ITConverter if(mask>>0&1)curnote=note; if(curnote<120) { + uint32_t incr = ~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/3:40*par->speed/3; 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)); + eventList.push_back(MidiEvent::noteOff(par->curmiditk+incr,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])); + eventList.push_back(MidiEvent::noteOn(par->curmiditk+incr,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))); + eventList.push_back(MidiEvent::noteOff(par->curmiditk+incr*2,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])); + eventList.push_back(MidiEvent::noteOn(par->curmiditk+incr*2,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))); + eventList.push_back(MidiEvent::noteOff(par->curmiditk+incr*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])); + eventList.push_back(MidiEvent::noteOn(par->curmiditk+incr*3,0,curnote,par->chvelm[ch])); + } + } + break; + case 17://Retrigger + { + if(fxp) + par->chefxmem[ch][efx]=fxp; + else + fxp=par->chefxmem[ch][efx]; + std::vector<std::function<int(int)>> retrigmod = { + [](int a){return a;}, + [](int a){return a-1>0?a-1:0;}, + [](int a){return a-2>0?a-2:0;}, + [](int a){return a-4>0?a-4:0;}, + [](int a){return a-8>0?a-8:0;}, + [](int a){return a-16>0?a-16:0;}, + [](int a){return a*2/3;}, + [](int a){return a/2;}, + [](int a){return a;}, + [](int a){return a+1>63?63:a+1;}, + [](int a){return a+2>63?63:a+2;}, + [](int a){return a+4>63?63:a+4;}, + [](int a){return a+8>63?63:a+8;}, + [](int a){return a+16>63?63:a+16;}, + [](int a){return a*3/2>63?63:a*3/2;}, + [](int a){return a*2>63?63:a*2;}, + }; + int tspan = fxp & 0xF; + int vmod = fxp >> 4; + int ctk = 0; + int vel = par->chvelm[ch]; + uint32_t curnote=par->chnote[ch]; + if (tspan==0) break; //can't handle that! + if (curnote==255) curnote=note; + auto &eventList = par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]].eventList; + + while (ctk + tspan < par->speed) + { + eventList.push_back(MidiEvent::noteOff(par->curmiditk+(~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed:40)*ctk,0,curnote)); + vel = retrigmod[vmod](vel); + eventList.push_back(MidiEvent::noteOn(par->curmiditk+(~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed:40)*ctk,0,curnote, vel)); + ctk += tspan; } + eventList.push_back(MidiEvent::noteOff(par->curmiditk+(~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed:40)*ctk,0,curnote)); } break; case 19://Sxx controls @@ -861,7 +947,7 @@ class ITConverter if(fxp>0x20) { par->tempo=fxp; - par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk,fxp)); + par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk,~pretendspeed?1.*fxp*pretendspeed/(~pretendswing?pretendswing:par->speed):fxp)); } else { @@ -870,17 +956,39 @@ class ITConverter { tempop=fxp; for(int i=1;i<par->speed;++i) - par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk+i*40,par->tempo+=fxp&0x0F)); + { + par->tempo+=fxp&0x0F; + par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk+(~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed*i:40*i),~pretendspeed?1.*par->tempo*pretendspeed/(~pretendswing?pretendswing:par->speed):par->tempo)); + } } else if(fxp>0x00) { tempop=fxp; for(int i=1;i<par->speed;++i) - par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk+i*40,par->tempo-=fxp&0x0F)); + { + par->tempo-=fxp&0x0F; + par->f.tracks[0].eventList.push_back(MidiEvent::tempo(par->curmiditk+(~pretendspeed?(~pretendswing?40.*par->speed/pretendswing:40)*pretendspeed/par->speed*i:40*i),~pretendspeed?1.*par->tempo*pretendspeed/(~pretendswing?pretendswing:par->speed):par->tempo)); + } } } } break; + case 21://fine vib + par->chefxflg[ch][efx]=1; + //memory implementation is non-compliant + 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)*2)); + break; case 24://set pan { uint64_t param=fxp/2; @@ -919,7 +1027,7 @@ class ITConverter 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) + if(par->chnote[ch]!=255&¬e<120&&(efx!=7||disablegxx)) par->f.tracks[par->minstch[std::make_pair(ch,previnst)]]. eventList.push_back(MidiEvent::noteOff(par->curmiditk,0,par->chnote[ch])); if(note>=120) @@ -931,7 +1039,7 @@ class ITConverter } else { - if(efx!=7) + if(efx!=7||disablegxx) { par->chnote[ch]=note;par->chage[ch]=0; par->f.tracks[par->minstch[std::make_pair(ch,par->chinst[ch])]]. @@ -940,6 +1048,7 @@ class ITConverter else { par->chportadstnote[ch]=note; + if(debuggxx)printf("dst->%d\n",par->chportadstnote[ch]); } } } @@ -957,7 +1066,7 @@ class ITConverter for(auto &i:minstch) { i.second=trcnt++; - printf("%d, %d -> %d\n",i.first.first,i.first.second,i.second); + printf("ch %d, inst %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; @@ -968,7 +1077,7 @@ class ITConverter { f.tracks.push_back(MidiTrack()); char buf[48]; - sprintf(buf,"%s @ ch%d",c.instr[i->first.second].name.c_str(),i->first.first); + snprintf(buf,48,"%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)); @@ -976,7 +1085,7 @@ class ITConverter } 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)); + f.tracks[0].eventList.push_back(MidiEvent::tempo(0,~pretendspeed?tempo/(~pretendswing?pretendswing:speed)*pretendspeed:tempo)); ITCellActionConv* cac=new ITCellActionConv(this); p=new ITPlayer(c,cac); for(int i=0;i<64;++i)//turn off any remaining notes @@ -994,7 +1103,7 @@ class ITConverter for(auto &j:i->eventList) if((j.type&0xF0)==0x90)df=false; } - if(df){auto j=i+1;f.tracks.erase(i);i=j;} + if(df&&!single_instr_mode){auto j=i+1;f.tracks.erase(i);i=j;} else ++i; } printf("final file has %lu tracks.\n",f.tracks.size()); @@ -1004,11 +1113,11 @@ class ITConverter { std::vector<size_t> tr={0}; for(auto &pr:minstch) - if(pr.first.second==instr) + if(pr.first.second==instr && pr.second != 0) tr.push_back(pr.second); if(tr.size()==1) { - printf("instr#%d seems unused, skipped\n",instr); + printf("instr #%d seems unused, skipped\n",instr); continue; } f.writeFile((std::string(path)+"."+std::to_string(instr)+".mid").c_str(),tr); |