total extinction of legacy sample mode, part 5

partially working converter
This commit is contained in:
tildearrow 2025-11-09 05:23:32 -05:00
parent 734f36b483
commit 0f5455831a
9 changed files with 279 additions and 119 deletions

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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) {

201
src/engine/legacySample.cpp Normal file
View file

@ -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; i<chans; i++) {
// 0: sample off
// 1: legacy mode
// 2: normal mode
unsigned char sampleMode=0;
unsigned char sampleBank=0;
DivInstrumentType preferredInsType=DIV_INS_AMIGA;
DivInstrumentType preferredInsType2=DIV_INS_AMIGA;
bool noteOffDisablesSampleMode=false;
switch (sysOfChan[i]) {
case DIV_SYSTEM_NES:
// NES PCM channel (on by default)
if (dispatchChanOfChan[i]!=4) {
continue;
}
sampleMode=1;
break;
case DIV_SYSTEM_YM2612:
// YM2612 DAC channel
if (dispatchChanOfChan[i]!=5) {
continue;
}
break;
case DIV_SYSTEM_YM2612_EXT:
// YM2612 DAC channel
if (dispatchChanOfChan[i]!=8) {
continue;
}
break;
case DIV_SYSTEM_PCE:
// any channel can be DAC'd
noteOffDisablesSampleMode=true;
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
// Neo Geo CD ADPCM channels
if (dispatchChanOfChan[i]<7) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
// Neo Geo CD ADPCM channels
if (dispatchChanOfChan[i]<10) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610B:
// ADPCM channels
if (dispatchChanOfChan[i]<9) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610B_EXT:
// ADPCM channels
if (dispatchChanOfChan[i]<12) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
// all channels can play back samples
sampleMode=1;
preferredInsType=DIV_INS_SEGAPCM;
break;
default: // not a chip with convertible channels
continue;
}
logV("- channel %d",i);
bool didThisPattern[DIV_MAX_PATTERNS];
memset(didThisPattern,0,DIV_MAX_PATTERNS*sizeof(bool));
for (int j=0; j<h->ordersLen; 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; k<h->patLen; k++) {
// check for legacy mode toggle and sample bank changes
for (int l=0; l<h->pat[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);
}

View file

@ -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.sample<parent->song.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.sample<parent->song.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:

View file

@ -28,7 +28,7 @@
class DivPlatformSegaPCM: public DivDispatch {
protected:
struct Channel: public SharedChannel<int> {
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<int>(127),
furnacePCM(false),
isNewSegaPCM(false),
setPos(false),
chVolL(127),

View file

@ -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; i<chans; i++) {
unsigned char sampleMode=0;
for (int j=0; j<h->ordersLen; j++) {
DivPattern* p=h->pat[i].data[h->orders.ord[i][j]];
if (p==NULL) continue;
switch (
for (int k=0; k<h->patLen; k++) {
}
}
}
}
}
}

View file

@ -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.
*/