aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--music/it2midi.cpp171
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&&note<120&&efx!=7)
+ if(par->chnote[ch]!=255&&note<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);