/*
* 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)
*
* 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
* 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
* custom controller mapping for each instrument
*
*/
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <cmath>
#include <stdexcept>
#include <algorithm>
#include <functional>
#include <map>
#include <string>
#include <utility>
#include <vector>
const int debuggxx=0;
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(std::vector<size_t> tracksw={})
{
puts("Midi File dump");
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())
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);
if(par->chvelm[ch]==0)par->chvelm[ch]=1;
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 && curnote>=12)
par->chvelm[ch]=par->c.sample[par->c.instr[par->chinst[ch]].sampleref[curnote-12]-1].defvol*2;
if(par->chvelm[ch]>127)
par->chvelm[ch]=127;
if(par->chvelm[ch]==0)
{
//printf("!!!!inst %d samp %d note %d def vol %d\n",par->chinst[ch],par->c.instr[par->chinst[ch]].sampleref[curnote]-1,curnote,par->c.sample[par->c.instr[par->chinst[ch]].sampleref[curnote]-1].defvol*2);
par->chvelm[ch]=1;
}
}
if((mask>>0&1)&¬e<120&&!((mask>>3&1)&&efx==7))//reset for pitch slides (E/F, but not 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?");
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];
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;
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;
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 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+40*ctk,0,curnote));
vel = retrigmod[vmod](vel);
eventList.push_back(MidiEvent::noteOn(par->curmiditk+40*ctk,0,curnote, vel));
ctk += tspan;
}
eventList.push_back(MidiEvent::noteOff(par->curmiditk+40*ctk,0,curnote));
}
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 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;
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]));
}
else
{
par->chportadstnote[ch]=note;
if(debuggxx)printf("dst->%d\n",par->chportadstnote[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("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;
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];
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));
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&&!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());
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 && pr.second != 0)
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;
}