aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--music/it2midi.cpp1011
1 files changed, 1011 insertions, 0 deletions
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 <cstdio>
+#include <cstring>
+#include <cstdint>
+#include <cmath>
+#include <stdexcept>
+#include <algorithm>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+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<n-1;++i)
+ printf("env node: %d %d\n",fgetc(f),readSW(f));
+ uint8_t lenvy=fgetc(f);
+ uint16_t lenvt=readSW(f);
+ printf("final node: %d %d\n",lenvy,lenvt);
+ aenvmaxt=(n&&lenvy<32)?lenvt:65536;
+ }
+ if(mxtkf)fscanf(mxtkf,"%lf",&aenvmaxt);
+ }
+};
+class ITContainer;
+class ITPattern;
+struct ITCell
+{
+ uint8_t mask,note,inst,vol,efx,fxp;
+ ITCell(){mask=note=inst=vol=efx=fxp=0;}
+};
+class ITRow
+{
+ std::vector<ITCell> 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<ITRow> rows;
+ uint16_t len,nrows;
+ std::vector<ITCell> lastval;
+ public:
+ ITPattern(ITContainer *c,int p);
+ ITRow& operator [](size_t s){if(s<rows.size())return rows[s];}
+ size_t rowCount(){return rows.size();}
+ void dumpPattern();
+ friend class ITRow;
+};
+class ITContainer
+{
+ friend class ITRow;
+ friend class ITPattern;
+ friend class ITPlayer;
+ friend class ITConverter;
+ private:
+ FILE *f,*imf;
+ uint16_t cord,cins,csmp,cpat;
+ uint16_t crv,cmv,flag,flagsp;
+ uint8_t initspeed,inittempo;
+ uint16_t msgl;
+ uint32_t msgp;
+ uint8_t chp[64],chv[64];
+ uint8_t *ord;
+ uint32_t *ppat;
+ uint32_t *pins;
+ uint32_t *psmp;
+ std::string title;
+ std::vector<ITInstrument> instr;
+ std::vector<ITSample> sample;
+ std::vector<ITPattern> 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;i<msgl;++i)if(msg[i]==13)msg[i]=10;
+ if(flagsp&1)
+ printf("song message:\n%s\n\n",msg);
+ printf("file containing max length of instruments?(press return for none)\n");
+ char pth[1024];fgets(pth,1024,stdin);pth[strlen(pth)-1]=0;
+ if(strlen(pth))imf=fopen(pth,"r");else imf=NULL;
+ instr.push_back(ITInstrument());
+ for(uint16_t i=0;i<cins;++i)
+ {
+ fseek(f,pins[i],SEEK_SET);
+ instr.push_back(ITInstrument());
+ instr.back().readInstStub(f,imf,i+1);
+ printf("instr %d: %s\n",i+1,instr.back().name.c_str());
+ }
+ for(uint16_t i=0;i<csmp;++i)
+ {
+ fseek(f,psmp[i],SEEK_SET);
+ sample.push_back(ITSample());
+ sample.back().readSampleStub(f);
+ }
+ for(uint16_t i=0;i<cpat;++i)
+ {
+ patterns.push_back(ITPattern(this,i));
+ //patterns.back().dumpPattern();
+ }
+ }
+ ~ITContainer()
+ {
+ delete[] ord;
+ delete[] ppat;
+ delete[] pins;
+ delete[] psmp;
+ fclose(f);
+ if(imf)fclose(imf);
+ }
+};
+ITRow::ITRow(ITContainer *c,ITPattern *pat)
+{
+ cells.resize(64);
+ for(uint8_t chmarker;chmarker=fgetc(c->f);)
+ {
+ 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;i<nrows;++i)
+ rows.push_back(ITRow(c,this));
+ if(ftell(c->f)-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<nrows;++i)
+ {
+ printf("|");
+ const char* notes="C-C#D-D#E-F-F#G-G#A-A#B-";
+ for(int ch=0;ch<64;++ch)
+ {
+ if(rows[i].cells[ch].note)
+ {
+ if(rows[i].cells[ch].note==0xff)
+ printf("== ");
+ else if(rows[i].cells[ch].note==0xfe)
+ printf("^^ ");
+ else if(rows[i].cells[ch].note>=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;i<c.patterns[pat].rowCount();++i)
+ {
+ for(uint8_t ch=0;ch<64;++ch)
+ {
+ uint8_t msk=0,note=0,inst=0,vol=0,efx=0,fxp=0;
+ ITCell& cell=c.patterns[pat][i][ch];
+ msk=cell.mask;
+ note=cell.note;
+ inst=cell.inst;
+ vol=cell.vol;
+ efx=cell.efx;
+ fxp=cell.fxp;
+ if(a)a->action(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;i<c.cord;++i)
+ {
+ if(c.ord[i]==255)break;
+ if(c.ord[i]==254)continue;
+ processPattern(c.ord[i],~skprow?skprow:0);
+ if(~skpord)
+ {
+ if(skpord<=i)puts("loop?");
+ else i=skpord-1;
+ skpord=-1;
+ }
+ }
+ }
+ void skipPattern(int ord=-1,int row=-1)
+ {
+ if(~ord)skpord=ord;
+ if(~row)skprow=row;
+ }
+};
+struct MidiEvent
+{
+ uint32_t time,type,p1,p2;
+ std::string str;
+ MidiEvent(uint32_t _t,uint32_t _tp,uint32_t _p1,uint32_t _p2,const char* s=NULL)
+ {
+ time=_t;type=_tp;p1=_p1;p2=_p2;
+ if(s)str=std::string(s);else str="";
+ }
+ 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);
+ return MidiEvent(t,0x80|ch,note,vel);
+ }
+ static MidiEvent noteOn(uint32_t t,uint32_t ch,uint32_t note,uint32_t vel)
+ {
+ return MidiEvent(t,0x90|ch,note,vel);
+ }
+ static MidiEvent cc(uint32_t t,uint32_t ch,uint32_t cc,uint32_t val)
+ {
+ return MidiEvent(t,0xB0|ch,cc,val);
+ }
+ static MidiEvent pc(uint32_t t,uint32_t ch,uint32_t p)
+ {
+ return MidiEvent(t,0xC0|ch,p,0);
+ }
+ static MidiEvent pb(uint32_t t,uint32_t ch,uint32_t val)
+ {
+ if(val>16383)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<MidiEvent> 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<uint8_t> &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<uint8_t> 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<j.p2;++i)buf.push_back(j.str[i]);
+ }
+ }
+ dumpVL(0,buf);buf.push_back(0xFF);buf.push_back(0x2F);buf.push_back(0);
+ writeDWBE(buf.size(),f);
+ fwrite(buf.data(),1,buf.size(),f);
+ }
+ public:
+ uint16_t divs;
+ std::vector<MidiTrack> 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<size_t> tracksw={})
+ {
+ for(MidiTrack &i:tracks)
+ std::stable_sort(i.eventList.begin(),i.eventList.end(),
+ [](const MidiEvent& a,const MidiEvent& b)->bool{
+ return a.time<b.time;
+ }
+ );
+ f=fopen(path,"wb");
+ fputs("MThd",f);
+ writeDWBE(6,f);
+ writeSWBE(1,f);
+ if(tracksw.size())
+ writeSWBE(tracksw.size(),f);
+ else
+ writeSWBE(tracks.size(),f);
+ writeSWBE(divs,f);
+ if(tracksw.size())
+ for(auto& i:tracksw)writeTrack(tracks[i]);
+ else
+ for(MidiTrack &i:tracks)writeTrack(i);
+ fclose(f);
+ }
+};
+class ITConverter
+{
+ friend class ITCellActionPre;
+ friend class ITCellActionConv;
+ private:
+ ITContainer& c;
+ std::map<std::pair<uint8_t,uint8_t>,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]].aenvmaxt<par->chage[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)&&note<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;tk<runfor;++tk)
+ {
+ par->chpitchm[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;tk<runfor;++tk)
+ {
+ par->chpitchm[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;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;
+ 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;i<par->speed;++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;i<par->speed;++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&&note<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<size_t> 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;
+}