mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-15 17:25:06 +00:00
commit
37c733af6b
11 changed files with 459 additions and 16 deletions
|
@ -10,6 +10,8 @@ however, effects are continuous, which means you only need to type it once and t
|
||||||
- a note must be present for this effect to work.
|
- a note must be present for this effect to work.
|
||||||
- `04xy`: vibrato. `x` is the speed, while `y` is the depth.
|
- `04xy`: vibrato. `x` is the speed, while `y` is the depth.
|
||||||
- maximum vibrato depth is ±1 semitone.
|
- maximum vibrato depth is ±1 semitone.
|
||||||
|
- `07xy`: tremolo. `x` is the speed, while `y` is the depth.
|
||||||
|
- maximum tremolo depth is -60 volume steps.
|
||||||
- `08xy`: set panning. `x` is the left channel and `y` is the right one.
|
- `08xy`: set panning. `x` is the left channel and `y` is the right one.
|
||||||
- not all systems support this effect.
|
- not all systems support this effect.
|
||||||
- `09xx`: set speed 1.
|
- `09xx`: set speed 1.
|
||||||
|
@ -22,6 +24,9 @@ however, effects are continuous, which means you only need to type it once and t
|
||||||
- `0Dxx`: jump to next pattern.
|
- `0Dxx`: jump to next pattern.
|
||||||
- `0Fxx`: set speed 2.
|
- `0Fxx`: set speed 2.
|
||||||
|
|
||||||
|
- `9xxx`: set sample position to `xxx`\*0x100.
|
||||||
|
- not all systems support this effect.
|
||||||
|
|
||||||
- `Cxxx`: change song Hz.
|
- `Cxxx`: change song Hz.
|
||||||
- `xxx` may be from `000` to `3ff`.
|
- `xxx` may be from `000` to `3ff`.
|
||||||
|
|
||||||
|
@ -46,6 +51,14 @@ however, effects are continuous, which means you only need to type it once and t
|
||||||
- `EFxx`: add or subtract global pitch.
|
- `EFxx`: add or subtract global pitch.
|
||||||
- this effect is rather weird. use with caution.
|
- this effect is rather weird. use with caution.
|
||||||
- `80` is center.
|
- `80` is center.
|
||||||
|
- `F0xx`: change song Hz by BPM value.
|
||||||
|
- `F1xx`: single tick slide up.
|
||||||
|
- `F2xx`: single tick slide down.
|
||||||
|
- `F8xx`: single tick volume slide up.
|
||||||
|
- `F9xx`: single tick volume slide down.
|
||||||
|
- `FAxy`: fast volume slide (4x faster than `0Axy`).
|
||||||
|
- if `x` is 0 then this is a slide down.
|
||||||
|
- if `y` is 0 then this is a slide up.
|
||||||
- `FFxx`: end of song/stop playback.
|
- `FFxx`: end of song/stop playback.
|
||||||
|
|
||||||
additionally each system has its own effects. [click here for more details](../7-systems/README.md).
|
additionally each system has its own effects. [click here for more details](../7-systems/README.md).
|
||||||
|
|
|
@ -54,6 +54,7 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_SAMPLE_MODE,
|
DIV_CMD_SAMPLE_MODE,
|
||||||
DIV_CMD_SAMPLE_FREQ,
|
DIV_CMD_SAMPLE_FREQ,
|
||||||
DIV_CMD_SAMPLE_BANK,
|
DIV_CMD_SAMPLE_BANK,
|
||||||
|
DIV_CMD_SAMPLE_POS,
|
||||||
|
|
||||||
DIV_CMD_FM_LFO,
|
DIV_CMD_FM_LFO,
|
||||||
DIV_CMD_FM_LFO_WAVE,
|
DIV_CMD_FM_LFO_WAVE,
|
||||||
|
|
|
@ -55,6 +55,8 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
||||||
return "03xx: Portamento";
|
return "03xx: Portamento";
|
||||||
case 0x04:
|
case 0x04:
|
||||||
return "04xy: Vibrato (x: speed; y: depth)";
|
return "04xy: Vibrato (x: speed; y: depth)";
|
||||||
|
case 0x07:
|
||||||
|
return "07xy: Tremolo (x: speed; y: depth)";
|
||||||
case 0x08:
|
case 0x08:
|
||||||
return "08xy: Set panning (x: left; y: right)";
|
return "08xy: Set panning (x: left; y: right)";
|
||||||
case 0x09:
|
case 0x09:
|
||||||
|
@ -70,7 +72,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
||||||
case 0x0f:
|
case 0x0f:
|
||||||
return "0Fxx: Set speed 2";
|
return "0Fxx: Set speed 2";
|
||||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||||
return "Cxxx: Set tick rate";
|
return "Cxxx: Set tick rate (hz)";
|
||||||
case 0xe0:
|
case 0xe0:
|
||||||
return "E0xx: Set arp speed";
|
return "E0xx: Set arp speed";
|
||||||
case 0xe1:
|
case 0xe1:
|
||||||
|
@ -95,10 +97,25 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
||||||
return "EExx: Send external command";
|
return "EExx: Send external command";
|
||||||
case 0xef:
|
case 0xef:
|
||||||
return "EFxx: Set global tuning (quirky!)";
|
return "EFxx: Set global tuning (quirky!)";
|
||||||
|
case 0xf0:
|
||||||
|
return "F0xx: Set tick rate (bpm)";
|
||||||
|
case 0xf1:
|
||||||
|
return "F1xx: Single tick note slide up";
|
||||||
|
case 0xf2:
|
||||||
|
return "F2xx: Single tick note slide down";
|
||||||
|
case 0xf8:
|
||||||
|
return "F8xx: Single tick volume slide up";
|
||||||
|
case 0xf9:
|
||||||
|
return "F9xx: Single tick volume slide down";
|
||||||
|
case 0xfa:
|
||||||
|
return "FAxx: Fast volume slide (0y: down; x0: up)";
|
||||||
case 0xff:
|
case 0xff:
|
||||||
return "FFxx: Stop song";
|
return "FFxx: Stop song";
|
||||||
default:
|
default:
|
||||||
if (chan>=0 && chan<chans) {
|
if ((effect&0xf0)==0x90) {
|
||||||
|
return "9xxx: Set sample offset*256";
|
||||||
|
}
|
||||||
|
else if (chan>=0 && chan<chans) {
|
||||||
const char* ret=disCont[dispatchOfChan[chan]].dispatch->getEffectName(effect);
|
const char* ret=disCont[dispatchOfChan[chan]].dispatch->getEffectName(effect);
|
||||||
if (ret!=NULL) return ret;
|
if (ret!=NULL) return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,6 +242,7 @@ class DivEngine {
|
||||||
|
|
||||||
bool loadDMF(unsigned char* file, size_t len);
|
bool loadDMF(unsigned char* file, size_t len);
|
||||||
bool loadFur(unsigned char* file, size_t len);
|
bool loadFur(unsigned char* file, size_t len);
|
||||||
|
bool loadMod(unsigned char* file, size_t len);
|
||||||
|
|
||||||
bool initAudioBackend();
|
bool initAudioBackend();
|
||||||
bool deinitAudioBackend();
|
bool deinitAudioBackend();
|
||||||
|
|
|
@ -1223,6 +1223,329 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
||||||
|
struct InvalidHeaderException {};
|
||||||
|
bool success=false;
|
||||||
|
int chCount;
|
||||||
|
int ordCount;
|
||||||
|
std::vector<int> patPtr;
|
||||||
|
char magic[4]={0,0,0,0};
|
||||||
|
short defaultVols[31];
|
||||||
|
int sampLens[31];
|
||||||
|
// 0=arp, 1=pslide, 2=vib, 3=trem, 4=vslide
|
||||||
|
bool fxUsage[DIV_MAX_CHANS][5];
|
||||||
|
SafeReader reader=SafeReader(file,len);
|
||||||
|
warnings="";
|
||||||
|
try {
|
||||||
|
DivSong ds;
|
||||||
|
|
||||||
|
// check mod magic bytes
|
||||||
|
if (!reader.seek(1080,SEEK_SET)) {
|
||||||
|
throw EndOfFileException(&reader,reader.tell());
|
||||||
|
}
|
||||||
|
reader.read(magic,4);
|
||||||
|
if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0) {
|
||||||
|
chCount=4;
|
||||||
|
} else if(memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') {
|
||||||
|
chCount=magic[0]-'0';
|
||||||
|
} else if((memcmp(magic+2,"CH",2)==0 || memcmp(magic+2,"CN",2)==0)
|
||||||
|
&&(magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) {
|
||||||
|
chCount=((magic[0]-'0')*10)+(magic[1]-'0');
|
||||||
|
} else {
|
||||||
|
throw InvalidHeaderException();
|
||||||
|
}
|
||||||
|
// song name
|
||||||
|
reader.seek(0,SEEK_SET);
|
||||||
|
ds.name=reader.readString(20);
|
||||||
|
// samples
|
||||||
|
ds.sampleLen=31;
|
||||||
|
for (int i=0;i<31;i++) {
|
||||||
|
DivSample* sample=new DivSample;
|
||||||
|
sample->depth=8;
|
||||||
|
sample->name=reader.readString(22);
|
||||||
|
int slen=reader.readS_BE()*2;
|
||||||
|
sampLens[i]=slen;
|
||||||
|
if (slen==2) slen=0;
|
||||||
|
signed char fineTune=reader.readC()&0x0f;
|
||||||
|
if (fineTune>=8) fineTune-=16;
|
||||||
|
sample->rate=(int)(pow(2,fineTune/96.0)*COLOR_PAL/535);
|
||||||
|
sample->centerRate=sample->rate;
|
||||||
|
defaultVols[i]=reader.readC();
|
||||||
|
int loopStart=reader.readS_BE()*2;
|
||||||
|
int loopLen=reader.readS_BE()*2;
|
||||||
|
int loopEnd=loopStart+loopLen;
|
||||||
|
// bunch of checks since ProTracker abuses those for one-shot samples
|
||||||
|
if (loopStart>loopEnd || loopEnd<4 || loopLen<4) {
|
||||||
|
loopStart=0;
|
||||||
|
loopLen=0;
|
||||||
|
}
|
||||||
|
if(loopLen>=2) {
|
||||||
|
if(loopEnd<slen) slen=loopEnd;
|
||||||
|
sample->loopStart=loopStart;
|
||||||
|
}
|
||||||
|
sample->init(slen);
|
||||||
|
ds.sample.push_back(sample);
|
||||||
|
}
|
||||||
|
// orders
|
||||||
|
ds.ordersLen=ordCount=reader.readC();
|
||||||
|
reader.readC(); // restart position, unused
|
||||||
|
int patMax=0;
|
||||||
|
for (int i=0;i<128;i++) {
|
||||||
|
unsigned char pat=reader.readC();
|
||||||
|
if (pat>patMax) patMax=pat;
|
||||||
|
for (int j=0;j<chCount;j++) {
|
||||||
|
ds.orders.ord[j][i]=pat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.seek(1084,SEEK_SET);
|
||||||
|
// patterns
|
||||||
|
ds.patLen=64;
|
||||||
|
for (int ch=0;ch<chCount;ch++) {
|
||||||
|
for (int i=0;i<5;i++) {
|
||||||
|
fxUsage[ch][i]=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int pat=0;pat<=patMax;pat++) {
|
||||||
|
DivPattern* chpats[DIV_MAX_CHANS];
|
||||||
|
for (int ch=0;ch<chCount;ch++) {
|
||||||
|
chpats[ch]=ds.pat[ch].getPattern(pat,true);
|
||||||
|
}
|
||||||
|
for (int row=0;row<64;row++) {
|
||||||
|
for (int ch=0;ch<chCount;ch++) {
|
||||||
|
auto* dstrow=chpats[ch]->data[row];
|
||||||
|
unsigned char data[4];
|
||||||
|
reader.read(&data,4);
|
||||||
|
// instrument
|
||||||
|
short ins=(data[0]&0xf0)|(data[2]>>4);
|
||||||
|
if (ins>0) {
|
||||||
|
dstrow[2]=ins-1;
|
||||||
|
dstrow[3]=defaultVols[ins-1];
|
||||||
|
}
|
||||||
|
// note
|
||||||
|
int period=data[1]+((data[0]&0x0f)*256);
|
||||||
|
if (period>0 && period<0x0fff) {
|
||||||
|
short note=(short)round(log2(3424.0/period)*12);
|
||||||
|
dstrow[0]=((note-1)%12)+1;
|
||||||
|
dstrow[1]=(note-1)/12+1;
|
||||||
|
}
|
||||||
|
// effects are done later
|
||||||
|
short fxtyp=data[2]&0x0f;
|
||||||
|
short fxval=data[3];
|
||||||
|
dstrow[4]=fxtyp;
|
||||||
|
dstrow[5]=fxval;
|
||||||
|
switch(fxtyp) {
|
||||||
|
case 0:
|
||||||
|
if(fxval!=0) fxUsage[ch][0]=true;
|
||||||
|
break;
|
||||||
|
case 1: case 2: case 3:
|
||||||
|
fxUsage[ch][1]=true;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
fxUsage[ch][2]=true;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
fxUsage[ch][1]=true;
|
||||||
|
fxUsage[ch][4]=true;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
fxUsage[ch][2]=true;
|
||||||
|
fxUsage[ch][4]=true;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
fxUsage[ch][3]=true;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
if(fxval!=0) fxUsage[ch][4]=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// samples
|
||||||
|
size_t pos=reader.tell();
|
||||||
|
for (int i=0;i<31;i++) {
|
||||||
|
reader.seek(pos,SEEK_SET);
|
||||||
|
reader.read(ds.sample[i]->data8,ds.sample[i]->samples);
|
||||||
|
pos+=sampLens[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert effects
|
||||||
|
for (int ch=0;ch<=chCount;ch++) {
|
||||||
|
unsigned char fxCols=1;
|
||||||
|
for (int pat=0;pat<=patMax;pat++) {
|
||||||
|
auto* data=ds.pat[ch].getPattern(pat,false)->data;
|
||||||
|
short lastPitchEffect=-1;
|
||||||
|
short lastEffectState[5]={-1,-1,-1,-1,-1};
|
||||||
|
short setEffectState[5]={-1,-1,-1,-1,-1};
|
||||||
|
for (int row=0;row<64;row++) {
|
||||||
|
const short fxUsageTyp[5]={0x00,0x01,0x04,0x07,0xFA};
|
||||||
|
short effectState[5]={0,0,0,0,0};
|
||||||
|
unsigned char curFxCol=0;
|
||||||
|
short fxTyp=data[row][4];
|
||||||
|
short fxVal=data[row][5];
|
||||||
|
auto writeFxCol=[data,row,&curFxCol](short typ, short val) {
|
||||||
|
data[row][4+curFxCol*2]=typ;
|
||||||
|
data[row][5+curFxCol*2]=val;
|
||||||
|
curFxCol++;
|
||||||
|
};
|
||||||
|
writeFxCol(-1,-1);
|
||||||
|
curFxCol=0;
|
||||||
|
switch (fxTyp) {
|
||||||
|
case 0: // arp
|
||||||
|
effectState[0]=fxVal;
|
||||||
|
break;
|
||||||
|
case 5: // vol slide + porta
|
||||||
|
effectState[4]=fxVal;
|
||||||
|
fxTyp=3;
|
||||||
|
fxVal=0;
|
||||||
|
// fall through
|
||||||
|
case 1: // note slide up
|
||||||
|
case 2: // note slide down
|
||||||
|
case 3: // porta
|
||||||
|
if ((fxTyp==3)&&(fxVal==0)) {
|
||||||
|
if (setEffectState[1]<0) break;
|
||||||
|
fxVal=setEffectState[1];
|
||||||
|
}
|
||||||
|
setEffectState[1]=fxVal;
|
||||||
|
effectState[1]=fxVal;
|
||||||
|
if((effectState[1]!=lastEffectState[1])||
|
||||||
|
(fxTyp!=lastPitchEffect)||
|
||||||
|
(effectState[1]!=0&&data[row][0]>0)) {
|
||||||
|
writeFxCol(fxTyp,fxVal);
|
||||||
|
}
|
||||||
|
lastPitchEffect=fxTyp;
|
||||||
|
lastEffectState[1]=fxVal;
|
||||||
|
break;
|
||||||
|
case 6: // vol slide + vibrato
|
||||||
|
effectState[4]=fxVal;
|
||||||
|
fxTyp=4;
|
||||||
|
fxVal=0;
|
||||||
|
// fall through
|
||||||
|
case 4: // vibrato
|
||||||
|
if (fxVal==0) {
|
||||||
|
if (setEffectState[2]<0) break;
|
||||||
|
fxVal=setEffectState[2];
|
||||||
|
}
|
||||||
|
effectState[2]=fxVal;
|
||||||
|
setEffectState[2]=fxVal;
|
||||||
|
break;
|
||||||
|
case 7: // tremolo
|
||||||
|
if (fxVal==0) {
|
||||||
|
if (setEffectState[3]<0) break;
|
||||||
|
fxVal=setEffectState[3];
|
||||||
|
}
|
||||||
|
effectState[3]=fxVal;
|
||||||
|
setEffectState[3]=fxVal;
|
||||||
|
break;
|
||||||
|
case 9: // set offset
|
||||||
|
writeFxCol(0x90,fxVal);
|
||||||
|
break;
|
||||||
|
case 10: // vol slide
|
||||||
|
effectState[4]=fxVal;
|
||||||
|
break;
|
||||||
|
case 11: // jump to pos
|
||||||
|
case 13: // break to row
|
||||||
|
writeFxCol(fxTyp,fxVal);
|
||||||
|
break;
|
||||||
|
case 12: // set vol
|
||||||
|
data[row][3]=fxVal;
|
||||||
|
break;
|
||||||
|
case 15: // set speed
|
||||||
|
// TODO somehow handle VBlank tunes
|
||||||
|
if (fxVal>=0x20) {
|
||||||
|
writeFxCol(0xf0,fxVal);
|
||||||
|
} else {
|
||||||
|
writeFxCol(0x09,fxVal);
|
||||||
|
writeFxCol(0x0f,fxVal);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 14: // extended
|
||||||
|
fxTyp=fxVal>>4;
|
||||||
|
fxVal&=0x0f;
|
||||||
|
switch (fxTyp) {
|
||||||
|
case 1: // single note slide up
|
||||||
|
case 2: // single note slide down
|
||||||
|
writeFxCol(fxTyp-1+0xf1,fxVal);
|
||||||
|
break;
|
||||||
|
case 9: // retrigger
|
||||||
|
writeFxCol(0x0c,fxVal);
|
||||||
|
break;
|
||||||
|
case 10: // single vol slide up
|
||||||
|
case 11: // single vol slide down
|
||||||
|
writeFxCol(fxTyp-10+0xf8,fxVal);
|
||||||
|
break;
|
||||||
|
case 12: // note cut
|
||||||
|
case 13: // note delay
|
||||||
|
writeFxCol(fxTyp-12+0xec,fxVal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int i=0;i<5;i++) {
|
||||||
|
// pitch slide and volume slide needs to be kept active on new note
|
||||||
|
// even after target/max is reached
|
||||||
|
if (fxUsage[ch][i]&&((effectState[i]!=lastEffectState[i])||(effectState[i]!=0&&i==4&&data[row][3]>=0))) {
|
||||||
|
writeFxCol(fxUsageTyp[i],effectState[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(lastEffectState,effectState,sizeof(effectState));
|
||||||
|
if (curFxCol>fxCols) {
|
||||||
|
fxCols=curFxCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ds.pat[ch].effectRows=fxCols;
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.pal=false;
|
||||||
|
ds.hz=50;
|
||||||
|
ds.customTempo=false;
|
||||||
|
ds.systemLen=(chCount+3)/4;
|
||||||
|
for(int i=0;i<ds.systemLen;i++) {
|
||||||
|
ds.system[i]=DIV_SYSTEM_AMIGA;
|
||||||
|
ds.systemFlags[i]=1; // PAL
|
||||||
|
}
|
||||||
|
for(int i=0;i<chCount;i++) {
|
||||||
|
ds.chanShow[i]=true;
|
||||||
|
}
|
||||||
|
for(int i=chCount;i<ds.systemLen*4;i++) {
|
||||||
|
ds.pat[i].effectRows=1;
|
||||||
|
ds.chanShow[i]=false;
|
||||||
|
}
|
||||||
|
// instrument creation
|
||||||
|
ds.insLen=31;
|
||||||
|
for(int i=0;i<31;i++) {
|
||||||
|
DivInstrument* ins=new DivInstrument;
|
||||||
|
ins->type=DIV_INS_AMIGA;
|
||||||
|
ins->amiga.initSample=i;
|
||||||
|
ins->name=ds.sample[i]->name;
|
||||||
|
ds.ins.push_back(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active) quitDispatch();
|
||||||
|
isBusy.lock();
|
||||||
|
song.unload();
|
||||||
|
song=ds;
|
||||||
|
recalcChans();
|
||||||
|
renderSamples();
|
||||||
|
isBusy.unlock();
|
||||||
|
if (active) {
|
||||||
|
initDispatch();
|
||||||
|
syncReset();
|
||||||
|
}
|
||||||
|
success=true;
|
||||||
|
} catch (EndOfFileException e) {
|
||||||
|
logE("premature end of file!\n");
|
||||||
|
lastError="incomplete file";
|
||||||
|
} catch (InvalidHeaderException e) {
|
||||||
|
logE("invalid info header!\n");
|
||||||
|
lastError="invalid info header!";
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
bool DivEngine::load(unsigned char* f, size_t slen) {
|
bool DivEngine::load(unsigned char* f, size_t slen) {
|
||||||
unsigned char* file;
|
unsigned char* file;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -1233,6 +1556,14 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (memcmp(f,DIV_DMF_MAGIC,16)!=0 && memcmp(f,DIV_FUR_MAGIC,16)!=0) {
|
if (memcmp(f,DIV_DMF_MAGIC,16)!=0 && memcmp(f,DIV_FUR_MAGIC,16)!=0) {
|
||||||
|
// try loading as a .mod first before trying to decompress
|
||||||
|
logD("loading as .mod...\n");
|
||||||
|
if (loadMod(f,slen)) {
|
||||||
|
delete[] f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastError="not a .mod song";
|
||||||
logD("loading as zlib...\n");
|
logD("loading as zlib...\n");
|
||||||
// try zlib
|
// try zlib
|
||||||
z_stream zl;
|
z_stream zl;
|
||||||
|
|
|
@ -184,7 +184,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
||||||
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||||
chan[c.chan].sample=-1;
|
chan[c.chan].sample=-1;
|
||||||
}
|
}
|
||||||
chan[c.chan].audPos=0;
|
if (chan[c.chan].setPos) {
|
||||||
|
chan[c.chan].setPos=false;
|
||||||
|
} else {
|
||||||
|
chan[c.chan].audPos=0;
|
||||||
|
}
|
||||||
chan[c.chan].audSub=0;
|
chan[c.chan].audSub=0;
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
|
@ -276,6 +280,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
chan[c.chan].inPorta=c.value;
|
chan[c.chan].inPorta=c.value;
|
||||||
break;
|
break;
|
||||||
|
case DIV_CMD_SAMPLE_POS:
|
||||||
|
chan[c.chan].audPos=c.value;
|
||||||
|
chan[c.chan].setPos=true;
|
||||||
|
break;
|
||||||
case DIV_CMD_GET_VOLMAX:
|
case DIV_CMD_GET_VOLMAX:
|
||||||
return 64;
|
return 64;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -36,7 +36,7 @@ class DivPlatformAmiga: public DivDispatch {
|
||||||
unsigned char ins;
|
unsigned char ins;
|
||||||
int busClock;
|
int busClock;
|
||||||
int note;
|
int note;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave;
|
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
|
||||||
signed char vol, outVol;
|
signed char vol, outVol;
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
Channel():
|
Channel():
|
||||||
|
@ -60,6 +60,7 @@ class DivPlatformAmiga: public DivDispatch {
|
||||||
keyOff(false),
|
keyOff(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
useWave(false),
|
useWave(false),
|
||||||
|
setPos(false),
|
||||||
vol(64),
|
vol(64),
|
||||||
outVol(64) {}
|
outVol(64) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -62,6 +62,7 @@ const char* cmdName[DIV_CMD_MAX]={
|
||||||
"SAMPLE_MODE",
|
"SAMPLE_MODE",
|
||||||
"SAMPLE_FREQ",
|
"SAMPLE_FREQ",
|
||||||
"SAMPLE_BANK",
|
"SAMPLE_BANK",
|
||||||
|
"SAMPLE_POS",
|
||||||
|
|
||||||
"FM_LFO",
|
"FM_LFO",
|
||||||
"FM_LFO_WAVE",
|
"FM_LFO_WAVE",
|
||||||
|
@ -837,6 +838,9 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].vibratoRate=effectVal>>4;
|
chan[i].vibratoRate=effectVal>>4;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||||
break;
|
break;
|
||||||
|
case 0x07: // tremolo
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
case 0x0a: // volume ramp
|
case 0x0a: // volume ramp
|
||||||
if (effectVal!=0) {
|
if (effectVal!=0) {
|
||||||
if ((effectVal&15)!=0) {
|
if ((effectVal&15)!=0) {
|
||||||
|
@ -857,15 +861,18 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].retrigTick=0;
|
chan[i].retrigTick=0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 0x90: case 0x91: case 0x92: case 0x93:
|
||||||
|
case 0x94: case 0x95: case 0x96: case 0x97:
|
||||||
|
case 0x98: case 0x99: case 0x9a: case 0x9b:
|
||||||
|
case 0x9c: case 0x9d: case 0x9e: case 0x9f: // set samp. pos
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_POS,i,(((effect&0x0f)<<8)|effectVal)*256));
|
||||||
|
break;
|
||||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3: // set Hz
|
case 0xc0: case 0xc1: case 0xc2: case 0xc3: // set Hz
|
||||||
divider=((effect&0x3)<<8)|effectVal;
|
divider=((effect&0x3)<<8)|effectVal;
|
||||||
if (divider<10) divider=10;
|
if (divider<10) divider=10;
|
||||||
cycles=((int)(got.rate)<<MASTER_CLOCK_PREC)/divider;
|
cycles=((int)(got.rate)<<MASTER_CLOCK_PREC)/divider;
|
||||||
clockDrift=0;
|
clockDrift=0;
|
||||||
break;
|
break;
|
||||||
case 0xc4: // set Hz by tempo
|
|
||||||
// TODO
|
|
||||||
break;
|
|
||||||
case 0xe0: // arp speed
|
case 0xe0: // arp speed
|
||||||
if (effectVal>0) {
|
if (effectVal>0) {
|
||||||
song.arpLen=effectVal;
|
song.arpLen=effectVal;
|
||||||
|
@ -938,6 +945,52 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
case 0xef: // global pitch
|
case 0xef: // global pitch
|
||||||
globalPitch+=(signed char)(effectVal-0x80);
|
globalPitch+=(signed char)(effectVal-0x80);
|
||||||
break;
|
break;
|
||||||
|
case 0xf0: // set Hz by tempo
|
||||||
|
divider=(effectVal*2+2)/5;
|
||||||
|
if (divider<10) divider=10;
|
||||||
|
cycles=((int)(got.rate)<<MASTER_CLOCK_PREC)/divider;
|
||||||
|
clockDrift=0;
|
||||||
|
break;
|
||||||
|
case 0xf1: // single pitch ramp up
|
||||||
|
case 0xf2: // single pitch ramp down
|
||||||
|
if (effect==0xf1) {
|
||||||
|
chan[i].portaNote=song.limitSlides?0x60:255;
|
||||||
|
} else {
|
||||||
|
chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60;
|
||||||
|
}
|
||||||
|
chan[i].portaSpeed=effectVal;
|
||||||
|
chan[i].portaStop=true;
|
||||||
|
chan[i].nowYouCanStop=false;
|
||||||
|
chan[i].stopOnOff=false;
|
||||||
|
chan[i].scheduledSlideReset=false;
|
||||||
|
chan[i].inPorta=false;
|
||||||
|
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0));
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote));
|
||||||
|
chan[i].portaNote=-1;
|
||||||
|
chan[i].portaSpeed=-1;
|
||||||
|
chan[i].inPorta=false;
|
||||||
|
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||||
|
break;
|
||||||
|
case 0xf8: // single volume ramp up
|
||||||
|
chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax);
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
|
break;
|
||||||
|
case 0xf9: // single volume ramp down
|
||||||
|
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
|
break;
|
||||||
|
case 0xfa: // fast volume ramp
|
||||||
|
if (effectVal!=0) {
|
||||||
|
if ((effectVal&15)!=0) {
|
||||||
|
chan[i].volSpeed=-(effectVal&15)*256;
|
||||||
|
} else {
|
||||||
|
chan[i].volSpeed=(effectVal>>4)*256;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chan[i].volSpeed=0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 0xff: // stop song
|
case 0xff: // stop song
|
||||||
freelance=false;
|
freelance=false;
|
||||||
playing=false;
|
playing=false;
|
||||||
|
|
|
@ -91,10 +91,10 @@ short SafeReader::readS() {
|
||||||
}
|
}
|
||||||
|
|
||||||
short SafeReader::readS_BE() {
|
short SafeReader::readS_BE() {
|
||||||
if (curSeek+1>len) throw EndOfFileException(this,len);
|
if (curSeek+2>len) throw EndOfFileException(this,len);
|
||||||
unsigned short ret=*(unsigned short*)(&buf[curSeek]);
|
short ret=*(short*)(&buf[curSeek]);
|
||||||
curSeek+=2;
|
curSeek+=2;
|
||||||
return (short)((ret>>8)|((ret&0xff)<<8));
|
return ((ret>>8)&0xff)|(ret<<8);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SafeReader::readI() {
|
int SafeReader::readI() {
|
||||||
|
|
|
@ -4647,9 +4647,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
||||||
hasOpened=fileDialog->openLoad(
|
hasOpened=fileDialog->openLoad(
|
||||||
"Open File",
|
"Open File",
|
||||||
{"compatible files", "*.fur *.dmf",
|
{"compatible files", "*.fur *.dmf *.mod",
|
||||||
"all files", ".*"},
|
"all files", ".*"},
|
||||||
"compatible files{.fur,.dmf},.*",
|
"compatible files{.fur,.dmf,.mod},.*",
|
||||||
workingDirSong,
|
workingDirSong,
|
||||||
dpiScale
|
dpiScale
|
||||||
);
|
);
|
||||||
|
|
|
@ -45,7 +45,7 @@ const FurnaceGUIColors fxColors[16]={
|
||||||
GUI_COLOR_PATTERN_EFFECT_SPEED, // 0F
|
GUI_COLOR_PATTERN_EFFECT_SPEED, // 0F
|
||||||
};
|
};
|
||||||
|
|
||||||
const FurnaceGUIColors extFxColors[16]={
|
const FurnaceGUIColors extFxColors[32]={
|
||||||
GUI_COLOR_PATTERN_EFFECT_MISC, // E0
|
GUI_COLOR_PATTERN_EFFECT_MISC, // E0
|
||||||
GUI_COLOR_PATTERN_EFFECT_PITCH, // E1
|
GUI_COLOR_PATTERN_EFFECT_PITCH, // E1
|
||||||
GUI_COLOR_PATTERN_EFFECT_PITCH, // E2
|
GUI_COLOR_PATTERN_EFFECT_PITCH, // E2
|
||||||
|
@ -62,6 +62,22 @@ const FurnaceGUIColors extFxColors[16]={
|
||||||
GUI_COLOR_PATTERN_EFFECT_TIME, // ED
|
GUI_COLOR_PATTERN_EFFECT_TIME, // ED
|
||||||
GUI_COLOR_PATTERN_EFFECT_SONG, // EE
|
GUI_COLOR_PATTERN_EFFECT_SONG, // EE
|
||||||
GUI_COLOR_PATTERN_EFFECT_SONG, // EF
|
GUI_COLOR_PATTERN_EFFECT_SONG, // EF
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_SPEED, // F0
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_PITCH, // F1
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_PITCH, // F2
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // F3
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // F4
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // F5
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // F6
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // F7
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_VOLUME, // F8
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_VOLUME, // F9
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_VOLUME, // FA
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // FB
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // FC
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // FD
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_INVALID, // FE
|
||||||
|
GUI_COLOR_PATTERN_EFFECT_SONG, // FF
|
||||||
};
|
};
|
||||||
|
|
||||||
inline float randRange(float min, float max) {
|
inline float randRange(float min, float max) {
|
||||||
|
@ -256,16 +272,18 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]);
|
||||||
} else if (pat->data[i][index]<0x48) {
|
} else if (pat->data[i][index]<0x48) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]);
|
||||||
|
} else if (pat->data[i][index]<0x90) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
|
||||||
|
} else if (pat->data[i][index]<0xa0) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_MISC]);
|
||||||
} else if (pat->data[i][index]<0xc0) {
|
} else if (pat->data[i][index]<0xc0) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
|
||||||
} else if (pat->data[i][index]<0xd0) {
|
} else if (pat->data[i][index]<0xd0) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SPEED]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SPEED]);
|
||||||
} else if (pat->data[i][index]<0xe0) {
|
} else if (pat->data[i][index]<0xe0) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
|
||||||
} else if (pat->data[i][index]<0xf0) {
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[pat->data[i][index]-0xe0]]);
|
|
||||||
} else {
|
} else {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[pat->data[i][index]-0xe0]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine(0.0f,0.0f);
|
ImGui::SameLine(0.0f,0.0f);
|
||||||
|
|
Loading…
Reference in a new issue