diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bb3b6f96..85259de3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -795,6 +795,7 @@ src/engine/fileOpsSample.cpp src/engine/filePlayer.cpp src/engine/filter.cpp src/engine/instrument.cpp +src/engine/legacySample.cpp src/engine/macroInt.cpp src/engine/pattern.cpp src/engine/pitchTable.cpp diff --git a/src/engine/engine.h b/src/engine/engine.h index f84dcdbd8..f318feb4b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -695,6 +695,10 @@ class DivEngine { // change song (UNSAFE) void changeSong(size_t songIndex); + // convert legacy sample mode to normal + // returns whether conversion occurred + bool convertLegacySampleMode(); + void swapSystemUnsafe(int src, int dest, bool preserveOrder=true); // move an asset diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index ac517df40..4dbea03bf 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -1103,9 +1103,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } - // store channel count for later - int chCount=getChannelCount(ds.system[0]); - // handle compound systems if (ds.system[0]==DIV_SYSTEM_GENESIS) { ds.systemLen=2; @@ -1169,9 +1166,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.systemFlags[0].set("brokenPitch",true); } - // always convert to normal sample mode (I have no idea how will I do export) - ds.convertLegacySampleMode(chCount); - ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); if (active) quitDispatch(); @@ -1181,6 +1175,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { song=ds; changeSong(0); recalcChans(); + // always convert to normal sample mode (I have no idea how will I do export) + convertLegacySampleMode(); saveLock.unlock(); BUSY_END; if (active) { diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 922499952..2a7efb348 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -2181,11 +2181,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { addWarning("this song used partial pitch linearity, which has been removed from Furnace. you may have to adjust your song."); } - // removal of legacy sample mode - if (ds.version<239) { - ds.convertLegacySampleMode(tchans); - } - if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); @@ -2193,6 +2188,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { song=ds; changeSong(0); recalcChans(); + // removal of legacy sample mode + if (song.version<239) { + convertLegacySampleMode(); + } saveLock.unlock(); BUSY_END; if (active) { diff --git a/src/engine/legacySample.cpp b/src/engine/legacySample.cpp new file mode 100644 index 000000000..f3d3a5793 --- /dev/null +++ b/src/engine/legacySample.cpp @@ -0,0 +1,201 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// from this point onwards, a mess. + +#include "engine.h" + +bool DivEngine::convertLegacySampleMode() { + logD("converting legacy sample mode..."); + int legacyInsInit=-1; + + // quit if we don't have space for a legacy sample instrument + if (song.ins.size()>254) { + logW("no space left on instrument list!"); + return false; + } + + auto initSampleInsIfNeeded=[this,&legacyInsInit]() { + if (legacyInsInit==-1) { + DivInstrument* ins=new DivInstrument; + legacyInsInit=(int)song.ins.size(); + + ins->type=DIV_INS_AMIGA; + ins->amiga.useNoteMap=true; + ins->name="Legacy Samples"; + for (int i=0; i<120; i++) { + ins->amiga.noteMap[i].freq=48; // C-4 + ins->amiga.noteMap[i].map=i%12; + } + + song.ins.push_back(ins); + song.insLen=song.ins.size(); + checkAssetDir(song.insDir,song.ins.size()); + } + }; + + for (DivSubSong* h: song.subsong) { + for (int i=0; iordersLen; j++) { + int patIndex=h->orders.ord[i][j]; + if (didThisPattern[patIndex]) continue; + didThisPattern[patIndex]=true; + DivPattern* p=h->pat[i].data[patIndex]; + if (p==NULL) continue; + + for (int k=0; kpatLen; k++) { + // check for legacy mode toggle and sample bank changes + for (int l=0; lpat[i].effectCols; l++) { + int fxVal=p->newData[k][DIV_PAT_FXVAL(l)]; + if (fxVal<0) fxVal=0; + switch (p->newData[k][DIV_PAT_FX(l)]) { + case 0x17: // set legacy sample mode + if (fxVal==0) { + sampleMode=0; + } else { + sampleMode=1; + } + // clear effect + p->newData[k][DIV_PAT_FX(l)]=-1; + p->newData[k][DIV_PAT_FXVAL(l)]=-1; + break; + case 0xeb: // set sample bank + sampleBank=fxVal; + // clear effect + p->newData[k][DIV_PAT_FX(l)]=-1; + p->newData[k][DIV_PAT_FXVAL(l)]=-1; + logV("change bank to %d",sampleBank); + break; + } + } + + // check for instrument changes + if (p->newData[k][DIV_PAT_INS]!=-1) { + DivInstrument* ins=getIns(p->newData[k][DIV_PAT_INS]); + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample || ins->type==preferredInsType || ins->type==preferredInsType2) { + sampleMode=2; + } + } + + if (p->newData[k][DIV_PAT_NOTE]!=-1 && + p->newData[k][DIV_PAT_NOTE]!=DIV_NOTE_OFF && + p->newData[k][DIV_PAT_NOTE]!=DIV_NOTE_REL && + p->newData[k][DIV_PAT_NOTE]!=DIV_MACRO_REL) { + // we've got a note + if (sampleMode==1) { + initSampleInsIfNeeded(); + //p->newData[k][DIV_PAT_NOTE]=60+12*sampleBank+(p->newData[k][DIV_PAT_NOTE]%12); + p->newData[k][DIV_PAT_INS]=legacyInsInit; + } + } else if (p->newData[k][DIV_PAT_NOTE]==DIV_NOTE_OFF && noteOffDisablesSampleMode) { + sampleMode=0; + } + } + } + } + } + + return (legacyInsInit!=-1); +} diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 984765ea7..c659b8f2a 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -78,11 +78,15 @@ void DivPlatformSegaPCM::tick(bool sysTick) { if (NEW_ARP_STRAT) { chan[i].handleArp(); + if (chan[i].std.arp.had) { + if (chan[i].freqChanged) chan[i].pcm.freq=-1; + } } else if (chan[i].std.arp.had) { if (!chan[i].inPorta) { chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<7); } chan[i].freqChanged=true; + chan[i].pcm.freq=-1; } if (parent->song.newSegaPCM) if (chan[i].std.panL.had) { @@ -105,6 +109,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) { chan[i].pitch2=chan[i].std.pitch.val; } chan[i].freqChanged=true; + chan[i].pcm.freq=-1; } if (chan[i].std.phaseReset.had) { @@ -123,12 +128,13 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } } if (oldSlides) chan[i].freq&=~1; - if (chan[i].furnacePCM) { - double off=1.0; - if (chan[i].pcm.sample>=0 && chan[i].pcm.samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[i].pcm.sample); - off=(double)s->centerRate/parent->getCenterRate(); - } + + double off=1.0; + if (chan[i].pcm.sample>=0 && chan[i].pcm.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].pcm.sample); + off=(double)s->centerRate/parent->getCenterRate(); + } + if (chan[i].pcm.freq==-1) { chan[i].pcm.freq=MIN(255,((rate*0.5)+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+512)/(128.0*12.0)))*255)/rate)+(oldSlides?chan[i].pitch2:0); rWrite(7+(i<<3),chan[i].pcm.freq); } @@ -141,44 +147,23 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } else { chan[i].pcm.pos=0; } - if (chan[i].furnacePCM) { - DivSample* s=parent->getSample(chan[i].pcm.sample); - int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); - int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)); - if (actualLength>0xfeff) actualLength=0xfeff; - int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos; - rWrite(0x86+(i<<3),3+((actualPos>>16)<<3)); - rWrite(0x84+(i<<3),(actualPos)&0xff); - rWrite(0x85+(i<<3),(actualPos>>8)&0xff); - rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]); - if (!s->isLoopable()) { - rWrite(0x86+(i<<3),2+((actualPos>>16)<<3)); - } else { - int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart; - logV("sampleOff: %x loopPos: %x",actualPos,loopPos); - rWrite(4+(i<<3),loopPos&0xff); - rWrite(5+(i<<3),(loopPos>>8)&0xff); - rWrite(0x86+(i<<3),((actualPos>>16)<<3)); - } + DivSample* s=parent->getSample(chan[i].pcm.sample); + int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); + int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)); + if (actualLength>0xfeff) actualLength=0xfeff; + int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos; + rWrite(0x86+(i<<3),3+((actualPos>>16)<<3)); + rWrite(0x84+(i<<3),(actualPos)&0xff); + rWrite(0x85+(i<<3),(actualPos>>8)&0xff); + rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]); + if (!s->isLoopable()) { + rWrite(0x86+(i<<3),2+((actualPos>>16)<<3)); } else { - DivSample* s=parent->getSample(chan[i].pcm.sample); - int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); - int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)); - if (actualLength>0xfeff) actualLength=0xfeff; - int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos; - rWrite(0x86+(i<<3),3+((actualPos>>16)<<3)); - rWrite(0x84+(i<<3),(actualPos)&0xff); - rWrite(0x85+(i<<3),(actualPos>>8)&0xff); - rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]); - if (!s->isLoopable()) { - rWrite(0x86+(i<<3),2+((actualPos>>16)<<3)); - } else { - int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart; - rWrite(4+(i<<3),loopPos&0xff); - rWrite(5+(i<<3),(loopPos>>8)&0xff); - rWrite(0x86+(i<<3),((actualPos>>16)<<3)); - } - rWrite(7+(i<<3),chan[i].pcm.freq); + int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart; + logV("sampleOff: %x loopPos: %x",actualPos,loopPos); + rWrite(4+(i<<3),loopPos&0xff); + rWrite(5+(i<<3),(loopPos>>8)&0xff); + rWrite(0x86+(i<<3),((actualPos>>16)<<3)); } } chan[i].keyOn=false; @@ -198,43 +183,39 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (skipRegisterWrites) break; - if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SEGAPCM) { - chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127; - chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM); - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); - chan[c.chan].sampleNote=c.value; - c.value=ins->amiga.getFreq(c.value); - chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; - } - if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { - chan[c.chan].pcm.sample=-1; - rWrite(0x86+(c.chan<<3),3); - chan[c.chan].macroInit(NULL); - break; - } - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].note=c.value; - chan[c.chan].baseFreq=(c.value<<7); - chan[c.chan].freqChanged=true; - } - chan[c.chan].furnacePCM=true; - chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { - chan[c.chan].outVol=chan[c.chan].vol; - - if (parent->song.newSegaPCM) { - chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127; - chan[c.chan].chVolR=(chan[c.chan].outVol*chan[c.chan].chPanR)/127; - rWrite(2+(c.chan<<3),chan[c.chan].chVolL); - rWrite(3+(c.chan<<3),chan[c.chan].chVolR); - } - } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; - } else { - assert(false && "LEGACY SAMPLE MODE!!!"); + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127; + chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); + chan[c.chan].sampleNote=c.value; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; } + if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { + chan[c.chan].pcm.sample=-1; + rWrite(0x86+(c.chan<<3),3); + chan[c.chan].macroInit(NULL); + break; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=(c.value<<7); + chan[c.chan].freqChanged=true; + chan[c.chan].pcm.freq=-1; + } + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + + if (parent->song.newSegaPCM) { + chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127; + chan[c.chan].chVolR=(chan[c.chan].outVol*chan[c.chan].chPanR)/127; + rWrite(2+(c.chan<<3),chan[c.chan].chVolL); + rWrite(3+(c.chan<<3),chan[c.chan].chVolR); + } + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; break; } case DIV_CMD_NOTE_OFF: @@ -297,6 +278,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { case DIV_CMD_PITCH: { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; + chan[c.chan].pcm.freq=-1; break; } case DIV_CMD_NOTE_PORTA: { @@ -319,6 +301,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { } chan[c.chan].baseFreq=newFreq; chan[c.chan].freqChanged=true; + chan[c.chan].pcm.freq=-1; if (return2) { chan[c.chan].inPorta=false; return 2; @@ -328,6 +311,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { case DIV_CMD_LEGATO: { chan[c.chan].baseFreq=((c.value+chan[c.chan].sampleNoteDelta)<<7); chan[c.chan].freqChanged=true; + chan[c.chan].pcm.freq=-1; break; } case DIV_CMD_SAMPLE_POS: diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index 3eb0c8475..e00fb0d74 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -28,7 +28,7 @@ class DivPlatformSegaPCM: public DivDispatch { protected: struct Channel: public SharedChannel { - bool furnacePCM, isNewSegaPCM, setPos; + bool isNewSegaPCM, setPos; unsigned char chVolL, chVolR; unsigned char chPanL, chPanR; int macroVolMul; @@ -37,12 +37,11 @@ class DivPlatformSegaPCM: public DivDispatch { int sample; unsigned int pos; // <<8 unsigned short len; - unsigned char freq; - PCMChannel(): sample(-1), pos(0), len(0), freq(0) {} + short freq; + PCMChannel(): sample(-1), pos(0), len(0), freq(-1) {} } pcm; Channel(): SharedChannel(127), - furnacePCM(false), isNewSegaPCM(false), setPos(false), chVolL(127), diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 4f20b34e1..2afd0fddc 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -728,23 +728,4 @@ void DivSong::unload() { delete i; } subsong.clear(); -} - -// from this point onwards, a mess. - -void DivSong::convertLegacySampleMode(int chans) { - for (DivSubSong* h: subsong) { - for (int i=0; iordersLen; j++) { - DivPattern* p=h->pat[i].data[h->orders.ord[i][j]]; - if (p==NULL) continue; - - switch ( - for (int k=0; kpatLen; k++) { - - } - } - } - } -} +} \ No newline at end of file diff --git a/src/engine/song.h b/src/engine/song.h index 8d5d5c8c0..437e1159e 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -408,11 +408,6 @@ struct DivSong { */ void findSubSongs(int chans); - /** - * try to convert usage of legacy sample mode into normal mode. - */ - void convertLegacySampleMode(int chans); - /** * clear orders and patterns. */