From ffe582236c728867f37d032a85427945e7de59e1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 16:03:21 -0500 Subject: [PATCH 1/7] harden .fur file saver --- src/engine/fileOps.cpp | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 99b46319d..1da563aeb 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2535,13 +2535,40 @@ bool DivEngine::load(unsigned char* f, size_t slen) { SafeWriter* DivEngine::saveFur(bool notPrimary) { saveLock.lock(); - int insPtr[256]; - int wavePtr[256]; - int samplePtr[256]; + std::vector insPtr; + std::vector wavePtr; + std::vector samplePtr; std::vector patPtr; size_t ptrSeek; warnings=""; + // fail if values are out of range + if (song.ordersLen>256) { + logE("maximum song length is 256!"); + lastError="maximum song length is 256"; + return NULL; + } + if (song.patLen>256) { + logE("maximum pattern length is 256!"); + lastError="maximum pattern length is 256"; + return NULL; + } + if (song.ins.size()>256) { + logE("maximum number of instruments is 256!"); + lastError="maximum number of instruments is 256"; + return NULL; + } + if (song.wave.size()>256) { + logE("maximum number of wavetables is 256!"); + lastError="maximum number of wavetables is 256"; + return NULL; + } + if (song.sample.size()>256) { + logE("maximum number of samples is 256!"); + lastError="maximum number of samples is 256"; + return NULL; + } + if (!notPrimary) { song.isDMF=false; song.version=DIV_ENGINE_VERSION; @@ -2719,21 +2746,21 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { /// INSTRUMENT for (int i=0; itell(); + insPtr.push_back(w->tell()); ins->putInsData(w); } /// WAVETABLE for (int i=0; itell(); + wavePtr.push_back(w->tell()); wave->putWaveData(w); } /// SAMPLE for (int i=0; itell(); + samplePtr.push_back(w->tell()); w->write("SMPL",4); w->writeI(0); From a2ae7dbb3dccacb4b95131844e6a4c3698b5f601 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 16:18:24 -0500 Subject: [PATCH 2/7] allocate ID for YMZ280B --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index e6d35e832..5dec70821 100644 --- a/papers/format.md +++ b/papers/format.md @@ -215,6 +215,7 @@ size | description | - 0xb5: tildearrow Sound Unit - 8 channels | - 0xb6: OPN extended - 9 channels | - 0xb7: PC-98 extended - 19 channels + | - 0xb8: YMZ280B - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels From ce253230cebc1705b2bfa1443e3a9f93d096ab8f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 16:59:07 -0500 Subject: [PATCH 3/7] add AICA and YMZ ADPCM formats --- papers/format.md | 2 ++ src/engine/sample.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/engine/sample.h | 14 ++++++++++++-- src/gui/guiConst.cpp | 4 ++-- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/papers/format.md b/papers/format.md index 5dec70821..bd4b57a02 100644 --- a/papers/format.md +++ b/papers/format.md @@ -744,6 +744,8 @@ size | description 1 | depth | - 0: ZX Spectrum overlay drum (1-bit) | - 1: 1-bit NES DPCM (1-bit) + | - 2: AICA ADPCM + | - 3: YMZ ADPCM | - 4: QSound ADPCM | - 5: ADPCM-A | - 6: ADPCM-B diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index e46c267d1..e8bc0f303 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -93,6 +93,18 @@ bool DivSample::initInternal(unsigned char d, int count) { dataDPCM=new unsigned char[lengthDPCM]; memset(dataDPCM,0,lengthDPCM); break; + case 2: // AICA ADPCM + if (dataAICA!=NULL) delete[] dataAICA; + lengthAICA=(count+1)/2; + dataAICA=new unsigned char[(lengthAICA+255)&(~0xff)]; + memset(dataAICA,0,(lengthAICA+255)&(~0xff)); + break; + case 3: // YMZ ADPCM + if (dataZ!=NULL) delete[] dataZ; + lengthZ=(count+1)/2; + dataZ=new unsigned char[(lengthZ+255)&(~0xff)]; + memset(dataZ,0,(lengthZ+255)&(~0xff)); + break; case 4: // QSound ADPCM if (dataQSoundA!=NULL) delete[] dataQSoundA; lengthQSoundA=(count+1)/2; @@ -657,6 +669,12 @@ void DivSample::render() { } break; } + case 2: // AICA ADPCM + aica_decode(dataAICA,data16,samples); + break; + case 3: // YMZ ADPCM + ymz_decode(dataZ,data16,samples); + break; case 4: // QSound ADPCM bs_decode(dataQSoundA,data16,samples); break; @@ -709,6 +727,14 @@ void DivSample::render() { if (accum>127) accum=127; } } + if (depth!=2) { // AICA ADPCM + if (!initInternal(2,samples)) return; + aica_encode(data16,dataAICA,(samples+511)&(~0x1ff)); + } + if (depth!=3) { // YMZ ADPCM + if (!initInternal(3,samples)) return; + ymz_encode(data16,dataZ,(samples+511)&(~0x1ff)); + } if (depth!=4) { // QSound ADPCM if (!initInternal(4,samples)) return; bs_encode(data16,dataQSoundA,samples); @@ -745,6 +771,10 @@ void* DivSample::getCurBuf() { return data1; case 1: return dataDPCM; + case 2: + return dataAICA; + case 3: + return dataZ; case 4: return dataQSoundA; case 5: @@ -771,6 +801,10 @@ unsigned int DivSample::getCurBufLen() { return length1; case 1: return lengthDPCM; + case 2: + return lengthAICA; + case 3: + return lengthZ; case 4: return lengthQSoundA; case 5: @@ -881,6 +915,8 @@ DivSample::~DivSample() { if (data16) delete[] data16; if (data1) delete[] data1; if (dataDPCM) delete[] dataDPCM; + if (dataAICA) delete[] dataAICA; + if (dataZ) delete[] dataZ; if (dataQSoundA) delete[] dataQSoundA; if (dataA) delete[] dataA; if (dataB) delete[] dataB; diff --git a/src/engine/sample.h b/src/engine/sample.h index 6e4de0941..624139797 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -62,6 +62,8 @@ struct DivSample { // valid values are: // - 0: ZX Spectrum overlay drum (1-bit) // - 1: 1-bit NES DPCM (1-bit) + // - 2: AICA ADPCM + // - 3: YMZ ADPCM // - 4: QSound ADPCM // - 5: ADPCM-A // - 6: ADPCM-B @@ -77,6 +79,8 @@ struct DivSample { short* data16; // 16 unsigned char* data1; // 0 unsigned char* dataDPCM; // 1 + unsigned char* dataAICA; // 2 + unsigned char* dataZ; // 3 unsigned char* dataQSoundA; // 4 unsigned char* dataA; // 5 unsigned char* dataB; // 6 @@ -84,8 +88,8 @@ struct DivSample { unsigned char* dataBRR; // 9 unsigned char* dataVOX; // 10 - unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; - unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; + unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; + unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX; unsigned int offSegaPCM, offQSound, offX1_010, offSU; unsigned int samples; @@ -218,6 +222,8 @@ struct DivSample { data16(NULL), data1(NULL), dataDPCM(NULL), + dataAICA(NULL), + dataZ(NULL), dataQSoundA(NULL), dataA(NULL), dataB(NULL), @@ -228,6 +234,8 @@ struct DivSample { length16(0), length1(0), lengthDPCM(0), + lengthAICA(0), + lengthZ(0), lengthQSoundA(0), lengthA(0), lengthB(0), @@ -238,6 +246,8 @@ struct DivSample { off16(0), off1(0), offDPCM(0), + offAICA(0), + offZ(0), offQSoundA(0), offA(0), offB(0), diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index a02543aa5..0afee14ca 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -116,8 +116,8 @@ const char* insTypes[DIV_INS_MAX]={ const char* sampleDepths[17]={ "1-bit PCM", "1-bit DPCM", - NULL, - NULL, + "Yamaha AICA", + "YMZ/YMU ADPCM", "QSound ADPCM", "ADPCM-A", "ADPCM-B", From 2fc637494f91edfae7d4b1b5463aedfdf9a491e3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 17:23:33 -0500 Subject: [PATCH 4/7] old .dmf loading improvements - YMU759 samples are now properly loaded - improvements to version 7 and 8 format handling --- src/engine/fileOps.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 1da563aeb..8899f7980 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -190,7 +190,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.timeBase=reader.readC(); ds.speed1=reader.readC(); - if (ds.version>0x05) { + if (ds.version>0x07) { ds.speed2=reader.readC(); ds.pal=reader.readC(); ds.hz=(ds.pal)?60:50; @@ -788,7 +788,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { pitch=reader.readC(); vol=reader.readC(); } - if (ds.version<=0x05) { + if (ds.version<=0x08) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { @@ -798,15 +798,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->depth=16; } } else { - if (ds.version>0x05) { + if (ds.version>0x08) { sample->depth=16; } else { // it appears samples were stored as ADPCM back then - sample->depth=6; + sample->depth=3; } } if (length>0) { - if (ds.version>0x05) { + if (ds.version>0x08) { if (ds.version<0x0b) { data=new short[1+(length/2)]; reader.read(data,length); @@ -842,8 +842,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { delete[] data; } else { - // ADPCM? - // it appears to be a slightly modified version of ADPCM-B! + // YMZ ADPCM adpcmData=new unsigned char[length]; logV("%x",reader.tell()); reader.read(adpcmData,length); @@ -854,7 +853,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logE("%d: error while initializing sample!",i); } - memcpy(sample->dataB,adpcmData,length); + memcpy(sample->dataZ,adpcmData,length); delete[] adpcmData; } } From 06ec22d6a55614674d3f8017278d872712c8f6cf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 17:51:05 -0500 Subject: [PATCH 5/7] enforce asset limits --- src/engine/engine.cpp | 23 ++++++++++++ src/gui/doAction.cpp | 84 ++++++++++++++++++++++++++++--------------- src/gui/gui.cpp | 7 ++-- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 320425c7f..e96005405 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1594,6 +1594,7 @@ void DivEngine::unmuteAll() { } int DivEngine::addInstrument(int refChan) { + if (song.ins.size()>=256) return -1; BUSY_BEGIN; DivInstrument* ins=new DivInstrument; int insCount=(int)song.ins.size(); @@ -1624,6 +1625,10 @@ int DivEngine::addInstrument(int refChan) { } int DivEngine::addInstrumentPtr(DivInstrument* which) { + if (song.ins.size()>=256) { + delete which; + return -1; + } BUSY_BEGIN; saveLock.lock(); song.ins.push_back(which); @@ -1668,6 +1673,7 @@ void DivEngine::delInstrument(int index) { } int DivEngine::addWave() { + if (song.wave.size()>=256) return -1; BUSY_BEGIN; saveLock.lock(); DivWavetable* wave=new DivWavetable; @@ -1680,37 +1686,48 @@ int DivEngine::addWave() { } bool DivEngine::addWaveFromFile(const char* path) { + if (song.wave.size()>=256) { + lastError="too many wavetables!"; + return false; + } FILE* f=ps_fopen(path,"rb"); if (f==NULL) { + lastError=fmt::sprintf("%s",strerror(errno)); return false; } unsigned char* buf; ssize_t len; if (fseek(f,0,SEEK_END)!=0) { fclose(f); + lastError=fmt::sprintf("could not seek to end: %s",strerror(errno)); return false; } len=ftell(f); if (len<0) { fclose(f); + lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); return false; } if (len==(SIZE_MAX>>1)) { fclose(f); + lastError="file size is invalid!"; return false; } if (len==0) { fclose(f); + lastError="file is empty"; return false; } if (fseek(f,0,SEEK_SET)!=0) { fclose(f); + lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); return false; } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { logW("did not read entire wavetable file buffer!"); delete[] buf; + lastError=fmt::sprintf("could not read entire file: %s",strerror(errno)); return false; } fclose(f); @@ -1790,6 +1807,7 @@ bool DivEngine::addWaveFromFile(const char* path) { } catch (EndOfFileException& e) { delete wave; delete[] buf; + lastError="premature end of file"; return false; } @@ -1816,6 +1834,7 @@ void DivEngine::delWave(int index) { } int DivEngine::addSample() { + if (song.sample.size()>=256) return -1; BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; @@ -1830,6 +1849,10 @@ int DivEngine::addSample() { } int DivEngine::addSampleFromFile(const char* path) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return -1; + } BUSY_BEGIN; warnings=""; diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 2e1dd3779..8a71d9a38 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -525,14 +525,22 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + MARK_MODIFIED; + } break; case GUI_ACTION_INS_LIST_DUPLICATE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { int prevIns=curIns; curIns=e->addInstrument(cursor.xCoarse); - (*e->song.ins[curIns])=(*e->song.ins[prevIns]); - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + (*e->song.ins[curIns])=(*e->song.ins[prevIns]); + MARK_MODIFIED; + } } break; case GUI_ACTION_INS_LIST_OPEN: @@ -571,14 +579,22 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_ADD: curWave=e->addWave(); - MARK_MODIFIED; + if (curWave==-1) { + showError("too many wavetables!"); + } else { + MARK_MODIFIED; + } break; case GUI_ACTION_WAVE_LIST_DUPLICATE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { int prevWave=curWave; curWave=e->addWave(); - (*e->song.wave[curWave])=(*e->song.wave[prevWave]); - MARK_MODIFIED; + if (curWave==-1) { + showError("too many wavetables!"); + } else { + (*e->song.wave[curWave])=(*e->song.wave[prevWave]); + MARK_MODIFIED; + } } break; case GUI_ACTION_WAVE_LIST_OPEN: @@ -614,31 +630,39 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); + if (curSample==-1) { + showError("too many samples!"); + } else { + MARK_MODIFIED; + } updateSampleTex=true; - MARK_MODIFIED; break; case GUI_ACTION_SAMPLE_LIST_DUPLICATE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { DivSample* prevSample=e->getSample(curSample); curSample=e->addSample(); - updateSampleTex=true; - e->lockEngine([this,prevSample]() { - DivSample* sample=e->getSample(curSample); - if (sample!=NULL) { - sample->rate=prevSample->rate; - sample->centerRate=prevSample->centerRate; - sample->name=prevSample->name; - sample->loopStart=prevSample->loopStart; - sample->depth=prevSample->depth; - if (sample->init(prevSample->samples)) { - if (prevSample->getCurBuf()!=NULL) { - memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + if (curSample==-1) { + showError("too many samples!"); + } else { + e->lockEngine([this,prevSample]() { + DivSample* sample=e->getSample(curSample); + if (sample!=NULL) { + sample->rate=prevSample->rate; + sample->centerRate=prevSample->centerRate; + sample->name=prevSample->name; + sample->loopStart=prevSample->loopStart; + sample->depth=prevSample->depth; + if (sample->init(prevSample->samples)) { + if (prevSample->getCurBuf()!=NULL) { + memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + } } } - } - e->renderSamples(); - }); - MARK_MODIFIED; + e->renderSamples(); + }); + MARK_MODIFIED; + } + updateSampleTex=true; } break; case GUI_ACTION_SAMPLE_LIST_OPEN: @@ -1154,11 +1178,15 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; curIns=e->addInstrument(cursor.xCoarse); - e->song.ins[curIns]->type=DIV_INS_AMIGA; - e->song.ins[curIns]->name=sample->name; - e->song.ins[curIns]->amiga.initSample=curSample; - nextWindow=GUI_WINDOW_INS_EDIT; - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + e->song.ins[curIns]->type=DIV_INS_AMIGA; + e->song.ins[curIns]->name=sample->name; + e->song.ins[curIns]->amiga.initSample=curSample; + nextWindow=GUI_WINDOW_INS_EDIT; + MARK_MODIFIED; + } break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c52809b05..00f0da2b3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3161,8 +3161,11 @@ bool FurnaceGUI::loop() { break; } case GUI_FILE_WAVE_OPEN: - e->addWaveFromFile(copyOfName.c_str()); - MARK_MODIFIED; + if (!e->addWaveFromFile(copyOfName.c_str())) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + MARK_MODIFIED; + } break; case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); From 14053f70cb691e520e1cf6f126419692d8c852ef Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 18:19:07 -0500 Subject: [PATCH 6/7] DO NOT USE - THIS FAILS - WORK IN PROGRESS --- CMakeLists.txt | 1 + src/engine/platform/ym2203ext.cpp | 47 ++++++++++++++------------- src/engine/platform/ym2203ext.h | 6 ++-- src/engine/song.cpp | 2 ++ src/engine/song.h | 53 +++++++++++++++++++++++++------ 5 files changed, 73 insertions(+), 36 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52cef63c1..b7b111f82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,6 +354,7 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp src/engine/platform/ym2203.cpp +src/engine/platform/ym2203ext.cpp src/engine/platform/ym2608.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 440dce6a0..7948d51e7 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -17,20 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "ym2610bext.h" +#include "ym2203ext.h" #include "../engine.h" #include #include "ym2610shared.h" #include "fmshared_OPN.h" -int DivPlatformYM2610BExt::dispatch(DivCommand c) { +int DivPlatformYM2203Ext::dispatch(DivCommand c) { if (c.chan<2) { - return DivPlatformYM2610B::dispatch(c); + return DivPlatformYM2203::dispatch(c); } if (c.chan>5) { c.chan-=3; - return DivPlatformYM2610B::dispatch(c); + return DivPlatformYM2203::dispatch(c); } int ch=c.chan-2; int ordch=orderedOps[ch]; @@ -384,7 +384,7 @@ static int opChanOffsH[4]={ 0xad, 0xae, 0xac, 0xa6 }; -void DivPlatformYM2610BExt::tick(bool sysTick) { +void DivPlatformYM2203Ext::tick(bool sysTick) { if (extMode) { bool writeSomething=false; unsigned char writeMask=2; @@ -401,7 +401,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { } } - DivPlatformYM2610B::tick(sysTick); + DivPlatformYM2203::tick(sysTick); bool writeNoteOn=false; unsigned char writeMask=2; @@ -438,13 +438,13 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { } } -void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { +void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) { if (ch<2) { - DivPlatformYM2610B::muteChannel(ch,mute); + DivPlatformYM2203::muteChannel(ch,mute); return; } if (ch>5) { - DivPlatformYM2610B::muteChannel(ch-3,mute); + DivPlatformYM2203::muteChannel(ch-3,mute); return; } isOpMuted[ch-2]=mute; @@ -462,7 +462,7 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { } } -void DivPlatformYM2610BExt::forceIns() { +void DivPlatformYM2203Ext::forceIns() { for (int i=0; i<6; i++) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -494,7 +494,6 @@ void DivPlatformYM2610BExt::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -518,23 +517,23 @@ void DivPlatformYM2610BExt::forceIns() { } } -void* DivPlatformYM2610BExt::getChanState(int ch) { +void* DivPlatformYM2203Ext::getChanState(int ch) { if (ch>=6) return &chan[ch-3]; if (ch>=2) return &opChan[ch-2]; return &chan[ch]; } -DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { +DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; return NULL; } -void DivPlatformYM2610BExt::reset() { - DivPlatformYM2610B::reset(); +void DivPlatformYM2203Ext::reset() { + DivPlatformYM2203::reset(); for (int i=0; i<4; i++) { - opChan[i]=DivPlatformYM2610BExt::OpChannel(); + opChan[i]=DivPlatformYM2203Ext::OpChannel(); opChan[i].vol=127; } @@ -543,12 +542,12 @@ void DivPlatformYM2610BExt::reset() { extMode=true; } -bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) { +bool DivPlatformYM2203Ext::keyOffAffectsArp(int ch) { return (ch>8); } -void DivPlatformYM2610BExt::notifyInsChange(int ins) { - DivPlatformYM2610B::notifyInsChange(ins); +void DivPlatformYM2203Ext::notifyInsChange(int ins) { + DivPlatformYM2203::notifyInsChange(ins); for (int i=0; i<4; i++) { if (opChan[i].ins==ins) { opChan[i].insChanged=true; @@ -556,8 +555,8 @@ void DivPlatformYM2610BExt::notifyInsChange(int ins) { } } -int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { - DivPlatformYM2610B::init(parent,channels,sugRate,flags); +int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2203::init(parent,channels,sugRate,flags); for (int i=0; i<4; i++) { isOpMuted[i]=false; } @@ -566,9 +565,9 @@ int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, un return 19; } -void DivPlatformYM2610BExt::quit() { - DivPlatformYM2610B::quit(); +void DivPlatformYM2203Ext::quit() { + DivPlatformYM2203::quit(); } -DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { +DivPlatformYM2203Ext::~DivPlatformYM2203Ext() { } diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 4e3f7c5ff..7dde3abb4 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -19,9 +19,9 @@ #include "../dispatch.h" -#include "ym2610b.h" +#include "ym2203.h" -class DivPlatformYM2610BExt: public DivPlatformYM2610B { +class DivPlatformYM2203Ext: public DivPlatformYM2203 { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; @@ -47,5 +47,5 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { void notifyInsChange(int ins); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); - ~DivPlatformYM2610BExt(); + ~DivPlatformYM2203Ext(); }; diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 7a815eb16..4c142c5f3 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -19,6 +19,8 @@ #include "song.h" +DivOrders emptyOrders; + void DivSong::clearSongData() { for (int i=0; i ins; - DivChannelData pat[DIV_MAX_CHANS]; + DivChannelData* pat; std::vector wave; std::vector sample; - bool chanShow[DIV_MAX_CHANS]; - unsigned char chanCollapse[DIV_MAX_CHANS]; + std::vector subsongs; + + bool* chanShow; + unsigned char* chanCollapse; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivWavetable nullWave; @@ -457,10 +491,11 @@ struct DivSong { systemPan[i]=0; systemFlags[i]=0; } - for (int i=0; i Date: Sun, 15 May 2022 01:42:49 -0500 Subject: [PATCH 7/7] dev95 - multiple songs in a single file (READ) experimental feature! proceed with caution. if you experience song corruption or crashes, report issue immediately! files with multiple songs will be readable in older versions of Furnace, but only the first song will be read in those versions. issue #199 --- CMakeLists.txt | 1 + papers/format.md | 89 +++++-- src/engine/engine.cpp | 233 +++++++++++------- src/engine/engine.h | 28 ++- src/engine/fileOps.cpp | 501 +++++++++++++++++++++++++++------------ src/engine/pattern.cpp | 2 +- src/engine/playback.cpp | 50 ++-- src/engine/song.cpp | 19 +- src/engine/song.h | 54 ++--- src/engine/sysDef.cpp | 4 +- src/gui/actionUtil.h | 4 +- src/gui/channels.cpp | 6 +- src/gui/cursor.cpp | 54 ++--- src/gui/doAction.cpp | 32 +-- src/gui/editControls.cpp | 8 +- src/gui/editing.cpp | 157 ++++++------ src/gui/gui.cpp | 27 ++- src/gui/gui.h | 17 +- src/gui/guiConst.cpp | 1 + src/gui/orders.cpp | 22 +- src/gui/pattern.cpp | 80 +++---- src/gui/settings.cpp | 1 + src/gui/songInfo.cpp | 35 +-- src/gui/subSongs.cpp | 94 ++++++++ 24 files changed, 974 insertions(+), 545 deletions(-) create mode 100644 src/gui/subSongs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7b111f82..fe9118685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -445,6 +445,7 @@ src/gui/settings.cpp src/gui/songInfo.cpp src/gui/songNotes.cpp src/gui/stats.cpp +src/gui/subSongs.cpp src/gui/sysConf.cpp src/gui/sysEx.cpp src/gui/util.cpp diff --git a/papers/format.md b/papers/format.md index bd4b57a02..c7672c29e 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,11 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 95: Furnace dev95 +- 94: Furnace dev94 +- 93: Furnace dev93 +- 92: Furnace dev92 +- 91: Furnace dev91 - 90: Furnace dev90 - 89: Furnace dev89 - 88: Furnace dev88 @@ -122,26 +127,26 @@ size | description -----|------------------------------------ 4 | "INFO" block ID 4 | reserved - 1 | time base - 1 | speed 1 - 1 | speed 2 - 1 | initial arpeggio time - 4f | ticks per second + 1 | time base (of first song) + 1 | speed 1 (of first song) + 1 | speed 2 (of first song) + 1 | initial arpeggio time (of first song) + 4f | ticks per second (of first song) | - 60 is NTSC | - 50 is PAL - 2 | pattern length + 2 | pattern length (of first song) | - the limit is 256. - 2 | orders length + 2 | orders length (of first song) | - the limit is 256 (>=80) or 127 (<80). - 1 | highlight A - 1 | highlight B + 1 | highlight A (of first song) + 1 | highlight B (of first song) 2 | instrument count | - the limit is 256. 2 | wavetable count | - the limit is 256. 2 | sample count | - the limit is 256. - 4 | pattern count + 4 | pattern count (global) 32 | list of sound chips | - possible soundchips: | - 0x00: end of list @@ -258,20 +263,20 @@ size | description 4?? | pointers to wavetables 4?? | pointers to samples 4?? | pointers to patterns - ??? | orders + ??? | orders (of first song) | - a table of bytes | - size=channels*ordLen | - read orders then channels | - the maximum value of a cell is FF (>=80) or 7F (<80). - ??? | effect columns + ??? | effect columns (of first song) | - size=channels - 1?? | channel hide status + 1?? | channel hide status (of first song) | - size=channels - 1?? | channel collapse status + 1?? | channel collapse status (of first song) | - size=channels - S?? | channel names + S?? | channel names (of first song) | - a list of channelCount C strings - S?? | channel short names + S?? | channel short names (of first song) | - same as above STR | song comment 4f | master volume, 1.0f=100% (>=59) @@ -292,6 +297,55 @@ size | description 1 | pitch macro is linear (>=90) or reserved 1 | pitch slide speed in full linear pitch mode (>=94) or reserved 18 | reserved + --- | **additional subsongs** (>=95) + STR | first subsong name + STR | first subsong comment + 1 | number of additional subsongs + 3 | reserved + 4?? | pointers to subsong data +``` + +# subsong + +from version 95 onwards, Furnace supports storing multiple songs on a single file. +the way it's currently done is really weird, but it provides for some backwards compatibility (previous versions will only load the first subsong which is already defined in the `INFO` block). + +``` +size | description +-----|------------------------------------ + 4 | "SONG" block ID + 4 | reserved + 1 | time base + 1 | speed 1 + 1 | speed 2 + 1 | initial arpeggio time + 4f | ticks per second + | - 60 is NTSC + | - 50 is PAL + 2 | pattern length + | - the limit is 256. + 2 | orders length + | - the limit is 256. + 1 | highlight A + 1 | highlight B + 4 | reserved + STR | subsong name + STR | subsong comment + ??? | orders + | - a table of bytes + | - size=channels*ordLen + | - read orders then channels + | - the maximum value of a cell is FF. + ??? | effect columns + | - size=channels + 1?? | channel hide status + | - size=channels + 1?? | channel collapse status + | - size=channels + S?? | channel names + | - a list of channelCount C strings + S?? | channel short names + | - same as above ``` # instrument @@ -772,7 +826,8 @@ size | description 4 | reserved 2 | channel 2 | pattern index - 4 | reserved + 2 | subsong (>=95) or reserved + 2 | reserved ??? | pattern data | - size: rows*(4+effectColumns*2)*2 | - read shorts in this order: diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e96005405..01f1a3a2e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -139,18 +139,18 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { int nextRow=0; int effectVal=0; DivPattern* pat[DIV_MAX_CHANS]; - for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } - for (int j=nextRow; jpatLen; j++) { nextRow=0; for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { nextOrder=i+1; nextRow=effectVal; } @@ -695,6 +695,7 @@ void DivEngine::createNew(const int* description) { saveLock.lock(); song.unload(); song=DivSong(); + changeSong(0); if (description!=NULL) { initSongWithDesc(description); } @@ -716,45 +717,55 @@ void DivEngine::swapChannels(int src, int dest) { } for (int i=0; i<256; i++) { - song.orders.ord[dest][i]^=song.orders.ord[src][i]; - song.orders.ord[src][i]^=song.orders.ord[dest][i]; - song.orders.ord[dest][i]^=song.orders.ord[src][i]; + curOrders->ord[dest][i]^=curOrders->ord[src][i]; + curOrders->ord[src][i]^=curOrders->ord[dest][i]; + curOrders->ord[dest][i]^=curOrders->ord[src][i]; - DivPattern* prev=song.pat[src].data[i]; - song.pat[src].data[i]=song.pat[dest].data[i]; - song.pat[dest].data[i]=prev; + DivPattern* prev=curPat[src].data[i]; + curPat[src].data[i]=curPat[dest].data[i]; + curPat[dest].data[i]=prev; } - song.pat[src].effectCols^=song.pat[dest].effectCols; - song.pat[dest].effectCols^=song.pat[src].effectCols; - song.pat[src].effectCols^=song.pat[dest].effectCols; + curPat[src].effectCols^=curPat[dest].effectCols; + curPat[dest].effectCols^=curPat[src].effectCols; + curPat[src].effectCols^=curPat[dest].effectCols; - String prevChanName=song.chanName[src]; - String prevChanShortName=song.chanShortName[src]; - bool prevChanShow=song.chanShow[src]; - bool prevChanCollapse=song.chanCollapse[src]; + String prevChanName=curSubSong->chanName[src]; + String prevChanShortName=curSubSong->chanShortName[src]; + bool prevChanShow=curSubSong->chanShow[src]; + bool prevChanCollapse=curSubSong->chanCollapse[src]; - song.chanName[src]=song.chanName[dest]; - song.chanShortName[src]=song.chanShortName[dest]; - song.chanShow[src]=song.chanShow[dest]; - song.chanCollapse[src]=song.chanCollapse[dest]; - song.chanName[dest]=prevChanName; - song.chanShortName[dest]=prevChanShortName; - song.chanShow[dest]=prevChanShow; - song.chanCollapse[dest]=prevChanCollapse; + curSubSong->chanName[src]=curSubSong->chanName[dest]; + curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; + curSubSong->chanShow[src]=curSubSong->chanShow[dest]; + curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest]; + curSubSong->chanName[dest]=prevChanName; + curSubSong->chanShortName[dest]=prevChanShortName; + curSubSong->chanShow[dest]=prevChanShow; + curSubSong->chanCollapse[dest]=prevChanCollapse; } void DivEngine::stompChannel(int ch) { logV("stomping channel %d",ch); for (int i=0; i<256; i++) { - song.orders.ord[ch][i]=0; + curOrders->ord[ch][i]=0; } - song.pat[ch].wipePatterns(); - song.pat[ch].effectCols=1; - song.chanName[ch]=""; - song.chanShortName[ch]=""; - song.chanShow[ch]=true; - song.chanCollapse[ch]=false; + curPat[ch].wipePatterns(); + curPat[ch].effectCols=1; + curSubSong->chanName[ch]=""; + curSubSong->chanShortName[ch]=""; + curSubSong->chanShow[ch]=true; + curSubSong->chanCollapse[ch]=false; +} + +void DivEngine::changeSong(size_t songIndex) { + if (songIndex>=song.subsong.size()) return; + curSubSong=song.subsong[songIndex]; + curPat=song.subsong[songIndex]->pat; + curOrders=&song.subsong[songIndex]->orders; + curSubSongIndex=songIndex; + curOrder=0; + curRow=0; } void DivEngine::swapChannelsP(int src, int dest) { @@ -767,6 +778,41 @@ void DivEngine::swapChannelsP(int src, int dest) { BUSY_END; } +void DivEngine::changeSongP(size_t index) { + if (index>=song.subsong.size()) return; + if (index==curSubSongIndex) return; + stop(); + BUSY_BEGIN; + saveLock.lock(); + changeSong(index); + saveLock.unlock(); + BUSY_END; +} + +int DivEngine::addSubSong() { + if (song.subsong.size()>=127) return -1; + BUSY_BEGIN; + saveLock.lock(); + song.subsong.push_back(new DivSubSong); + saveLock.unlock(); + BUSY_END; + return song.subsong.size()-1; +} + +bool DivEngine::removeSubSong(int index) { + if (song.subsong.size()<=1) return false; + stop(); + BUSY_BEGIN; + saveLock.lock(); + song.subsong[index]->clearData(); + delete song.subsong[index]; + song.subsong.erase(song.subsong.begin()+index); + changeSong(0); + saveLock.unlock(); + BUSY_END; + return true; +} + void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { int chanCount=chans; quitDispatch(); @@ -1321,15 +1367,15 @@ void DivEngine::reset() { } extValue=0; extValuePresent=0; - speed1=song.speed1; - speed2=song.speed2; + speed1=curSubSong->speed1; + speed2=curSubSong->speed2; firstTick=false; nextSpeed=speed1; divider=60; - if (song.customTempo) { - divider=song.hz; + if (curSubSong->customTempo) { + divider=curSubSong->hz; } else { - if (song.pal) { + if (curSubSong->pal) { divider=60; } else { divider=50; @@ -1476,6 +1522,10 @@ int DivEngine::getRow() { return curRow; } +size_t DivEngine::getCurrentSubSong() { + return curSubSongIndex; +} + unsigned char DivEngine::getSpeed1() { return speed1; } @@ -1485,9 +1535,9 @@ unsigned char DivEngine::getSpeed2() { } float DivEngine::getHz() { - if (song.customTempo) { - return song.hz; - } else if (song.pal) { + if (curSubSong->customTempo) { + return curSubSong->hz; + } else if (curSubSong->pal) { return 60.0; } else { return 50.0; @@ -1659,10 +1709,10 @@ void DivEngine::delInstrument(int index) { song.insLen=song.ins.size(); for (int i=0; idata[k][2]>index) { - song.pat[i].data[j]->data[k][2]--; + if (curPat[i].data[j]==NULL) continue; + for (int k=0; kpatLen; k++) { + if (curPat[i].data[j]->data[k][2]>index) { + curPat[i].data[j]->data[k][2]--; } } } @@ -2048,18 +2098,18 @@ void DivEngine::delSample(int index) { void DivEngine::addOrder(bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0xff) return; + if (curSubSong->ordersLen>=0xff) return; BUSY_BEGIN_SOFT; if (duplicate) { for (int i=0; iord[i][curOrder]; } } else { bool used[256]; for (int i=0; iordersLen; j++) { + used[curOrders->ord[i][j]]=true; } order[i]=0xff; for (int j=0; j<256; j++) { @@ -2073,19 +2123,19 @@ void DivEngine::addOrder(bool duplicate, bool where) { if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; icurOrder; j--) { - song.orders.ord[i][j]=song.orders.ord[i][j-1]; + for (int j=curSubSong->ordersLen; j>curOrder; j--) { + curOrders->ord[i][j]=curOrders->ord[i][j-1]; } - song.orders.ord[i][curOrder+1]=order[i]; + curOrders->ord[i][curOrder+1]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); curOrder++; if (playing && !freelance) { @@ -2097,21 +2147,21 @@ void DivEngine::addOrder(bool duplicate, bool where) { void DivEngine::deepCloneOrder(bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0xff) return; + if (curSubSong->ordersLen>=0xff) return; warnings=""; BUSY_BEGIN_SOFT; for (int i=0; iord[i][curOrder]; // find free slot for (int j=0; j<256; j++) { logD("finding free slot in %d...",j); - if (song.pat[i].data[j]==NULL) { + if (curPat[i].data[j]==NULL) { int origOrd=order[i]; order[i]=j; - DivPattern* oldPat=song.pat[i].getPattern(origOrd,false); - DivPattern* pat=song.pat[i].getPattern(j,true); + DivPattern* oldPat=curPat[i].getPattern(origOrd,false); + DivPattern* pat=curPat[i].getPattern(j,true); memcpy(pat->data,oldPat->data,256*32*sizeof(short)); logD("found at %d",j); didNotFind=false; @@ -2125,19 +2175,19 @@ void DivEngine::deepCloneOrder(bool where) { if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; icurOrder; j--) { - song.orders.ord[i][j]=song.orders.ord[i][j-1]; + for (int j=curSubSong->ordersLen; j>curOrder; j--) { + curOrders->ord[i][j]=curOrders->ord[i][j-1]; } - song.orders.ord[i][curOrder+1]=order[i]; + curOrders->ord[i][curOrder+1]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); curOrder++; if (playing && !freelance) { @@ -2148,17 +2198,17 @@ void DivEngine::deepCloneOrder(bool where) { } void DivEngine::deleteOrder() { - if (song.ordersLen<=1) return; + if (curSubSong->ordersLen<=1) return; BUSY_BEGIN_SOFT; saveLock.lock(); for (int i=0; iordersLen; j++) { + curOrders->ord[i][j]=curOrders->ord[i][j+1]; } } - song.ordersLen--; + curSubSong->ordersLen--; saveLock.unlock(); - if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1; + if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1; if (playing && !freelance) { playSub(false); } @@ -2173,9 +2223,9 @@ void DivEngine::moveOrderUp() { } saveLock.lock(); for (int i=0; iord[i][curOrder]^=curOrders->ord[i][curOrder-1]; + curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder]; + curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1]; } saveLock.unlock(); curOrder--; @@ -2187,15 +2237,15 @@ void DivEngine::moveOrderUp() { void DivEngine::moveOrderDown() { BUSY_BEGIN_SOFT; - if (curOrder>=song.ordersLen-1) { + if (curOrder>=curSubSong->ordersLen-1) { BUSY_END; return; } saveLock.lock(); for (int i=0; iord[i][curOrder]^=curOrders->ord[i][curOrder+1]; + curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder]; + curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1]; } saveLock.unlock(); curOrder++; @@ -2208,12 +2258,12 @@ void DivEngine::moveOrderDown() { void DivEngine::exchangeIns(int one, int two) { for (int i=0; idata[k][2]==one) { - song.pat[i].data[j]->data[k][2]=two; - } else if (song.pat[i].data[j]->data[k][2]==two) { - song.pat[i].data[j]->data[k][2]=one; + if (curPat[i].data[j]==NULL) continue; + for (int k=0; kpatLen; k++) { + if (curPat[i].data[j]->data[k][2]==one) { + curPat[i].data[j]->data[k][2]=two; + } else if (curPat[i].data[j]->data[k][2]==two) { + curPat[i].data[j]->data[k][2]=one; } } } @@ -2417,7 +2467,7 @@ void DivEngine::autoNoteOffAll() { void DivEngine::setOrder(unsigned char order) { BUSY_BEGIN_SOFT; curOrder=order; - if (order>=song.ordersLen) curOrder=0; + if (order>=curSubSong->ordersLen) curOrder=0; if (playing && !freelance) { playSub(false); } @@ -2440,15 +2490,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { void DivEngine::setSongRate(float hz, bool pal) { BUSY_BEGIN; saveLock.lock(); - song.pal=!pal; - song.hz=hz; + curSubSong->pal=!pal; + curSubSong->hz=hz; // what? - song.customTempo=true; + curSubSong->customTempo=true; divider=60; - if (song.customTempo) { - divider=song.hz; + if (curSubSong->customTempo) { + divider=curSubSong->hz; } else { - if (song.pal) { + if (curSubSong->pal) { divider=60; } else { divider=50; @@ -2891,5 +2941,6 @@ bool DivEngine::quit() { if (yrw801ROM!=NULL) delete[] yrw801ROM; if (tg100ROM!=NULL) delete[] tg100ROM; if (mu5ROM!=NULL) delete[] mu5ROM; + song.unload(); return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 4e446721f..5bb77c7e6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev94" -#define DIV_ENGINE_VERSION 94 +#define DIV_VERSION "dev95" +#define DIV_ENGINE_VERSION 95 // for imports #define DIV_VERSION_MOD 0xff01 @@ -304,6 +304,7 @@ class DivEngine { bool hasLoadedSomething; int softLockCount; int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; + size_t curSubSongIndex; double divider; int cycles; double clockDrift; @@ -411,8 +412,14 @@ class DivEngine { void swapChannels(int src, int dest); void stompChannel(int ch); + // change song (UNSAFE) + void changeSong(size_t songIndex); + public: DivSong song; + DivOrders* curOrders; + DivChannelData* curPat; + DivSubSong* curSubSong; DivInstrument* tempIns; DivSystem sysOfChan[DIV_MAX_CHANS]; int dispatchOfChan[DIV_MAX_CHANS]; @@ -603,6 +610,9 @@ class DivEngine { // get current row int getRow(); + // get current subsong + size_t getCurrentSubSong(); + // get speed 1 unsigned char getSpeed1(); @@ -799,6 +809,15 @@ class DivEngine { // public swap channels void swapChannelsP(int src, int dest); + // public change song + void changeSongP(size_t index); + + // add subsong + int addSubSong(); + + // remove subsong + bool removeSubSong(int index); + // change system void changeSystem(int index, DivSystem which, bool preserveOrder=true); @@ -900,6 +919,7 @@ class DivEngine { curOrder(0), remainingLoops(-1), nextSpeed(3), + curSubSongIndex(0), divider(60), cycles(0), clockDrift(0), @@ -935,6 +955,8 @@ class DivEngine { metroAmp(0.0f), metroVol(1.0f), totalProcessed(0), + curOrders(NULL), + curPat(NULL), tempIns(NULL), oscBuf{NULL,NULL}, oscSize(1), @@ -959,6 +981,8 @@ class DivEngine { sysFileMapFur[i]=DIV_SYSTEM_NULL; sysFileMapDMF[i]=DIV_SYSTEM_NULL; } + + changeSong(0); } }; #endif diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8899f7980..c63601a4f 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -184,57 +184,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logI("reading module data..."); if (ds.version>0x0c) { - ds.hilightA=reader.readC(); - ds.hilightB=reader.readC(); + ds.subsong[0]->hilightA=reader.readC(); + ds.subsong[0]->hilightB=reader.readC(); } - ds.timeBase=reader.readC(); - ds.speed1=reader.readC(); + ds.subsong[0]->timeBase=reader.readC(); + ds.subsong[0]->speed1=reader.readC(); if (ds.version>0x07) { - ds.speed2=reader.readC(); - ds.pal=reader.readC(); - ds.hz=(ds.pal)?60:50; - ds.customTempo=reader.readC(); + ds.subsong[0]->speed2=reader.readC(); + ds.subsong[0]->pal=reader.readC(); + ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50; + ds.subsong[0]->customTempo=reader.readC(); } else { - ds.speed2=ds.speed1; + ds.subsong[0]->speed2=ds.subsong[0]->speed1; } if (ds.version>0x0a) { String hz=reader.readString(3); - if (ds.customTempo) { + if (ds.subsong[0]->customTempo) { try { - ds.hz=std::stoi(hz); + ds.subsong[0]->hz=std::stoi(hz); } catch (std::exception& e) { logW("invalid custom Hz!"); - ds.hz=60; + ds.subsong[0]->hz=60; } } } if (ds.version>0x17) { - ds.patLen=reader.readI(); + ds.subsong[0]->patLen=reader.readI(); } else { - ds.patLen=(unsigned char)reader.readC(); + ds.subsong[0]->patLen=(unsigned char)reader.readC(); } - ds.ordersLen=(unsigned char)reader.readC(); + ds.subsong[0]->ordersLen=(unsigned char)reader.readC(); - if (ds.patLen<0) { + if (ds.subsong[0]->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } - if (ds.patLen>256) { + if (ds.subsong[0]->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } - if (ds.ordersLen<0) { + if (ds.subsong[0]->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } - if (ds.ordersLen>127) { + if (ds.subsong[0]->ordersLen>127) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -242,54 +242,54 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } if (ds.version<20 && ds.version>3) { - ds.arpLen=reader.readC(); + ds.subsong[0]->arpLen=reader.readC(); } else { - ds.arpLen=1; + ds.subsong[0]->arpLen=1; } if (ds.system[0]==DIV_SYSTEM_YMU759) { - switch (ds.timeBase) { + switch (ds.subsong[0]->timeBase) { case 0: - ds.hz=248; + ds.subsong[0]->hz=248; break; case 1: - ds.hz=200; + ds.subsong[0]->hz=200; break; case 2: - ds.hz=100; + ds.subsong[0]->hz=100; break; case 3: - ds.hz=50; + ds.subsong[0]->hz=50; break; case 4: - ds.hz=25; + ds.subsong[0]->hz=25; break; case 5: - ds.hz=20; + ds.subsong[0]->hz=20; break; default: - ds.hz=248; + ds.subsong[0]->hz=248; break; } - ds.customTempo=true; - ds.timeBase=0; + ds.subsong[0]->customTempo=true; + ds.subsong[0]->timeBase=0; addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } logV("%x",reader.tell()); - logI("reading pattern matrix (%d * %d = %d)...",ds.ordersLen,getChannelCount(ds.system[0]),ds.ordersLen*getChannelCount(ds.system[0])); + logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0])); for (int i=0; i0x7f) { - logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); - lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + ds.subsong[0]->orders.ord[i][j]=reader.readC(); + if (ds.subsong[0]->orders.ord[i][j]>0x7f) { + logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); + lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); delete[] file; return false; } if (ds.version>0x18) { // 1.1 pattern names - ds.pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); + ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); } } if (ds.version>0x03 && ds.version<0x06 && i<16) { @@ -637,9 +637,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logV("%x",reader.tell()); - logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen); + logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen); for (int i=0; ipat[i]; if (ds.version<0x0a) { chan.effectCols=1; } else { @@ -652,10 +652,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { delete[] file; return false; } - for (int j=0; jordersLen; j++) { + DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true); if (ds.version>0x08) { // current pattern format - for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readS(); // octave @@ -723,7 +723,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } else { // historic pattern format if (i<16) pat->data[0][2]=historicColIns[i]; - for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readC(); // octave @@ -905,6 +905,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -929,13 +930,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int insPtr[256]; unsigned int wavePtr[256]; unsigned int samplePtr[256]; + unsigned int subSongPtr[256]; std::vector patPtr; + int numberOfSubSongs=0; char magic[5]; memset(magic,0,5); SafeReader reader=SafeReader(file,len); warnings=""; try { DivSong ds; + DivSubSong* subSong=ds.subsong[0]; if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); @@ -1045,44 +1049,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } reader.readI(); - ds.timeBase=reader.readC(); - ds.speed1=reader.readC(); - ds.speed2=reader.readC(); - ds.arpLen=reader.readC(); - ds.hz=reader.readF(); - ds.pal=(ds.hz>=53); - ds.customTempo=true; + subSong->timeBase=reader.readC(); + subSong->speed1=reader.readC(); + subSong->speed2=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + subSong->pal=(subSong->hz>=53); + subSong->customTempo=true; - ds.patLen=reader.readS(); - ds.ordersLen=reader.readS(); + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); - ds.hilightA=reader.readC(); - ds.hilightB=reader.readC(); + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); ds.insLen=reader.readS(); ds.waveLen=reader.readS(); ds.sampleLen=reader.readS(); int numberOfPats=reader.readI(); - if (ds.patLen<0) { + if (subSong->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } - if (ds.patLen>256) { + if (subSong->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } - if (ds.ordersLen<0) { + if (subSong->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } - if (ds.ordersLen>256) { + if (subSong->ordersLen>256) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -1295,18 +1299,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(samplePtr,ds.sampleLen*4); for (int i=0; iordersLen); for (int i=0; iordersLen; j++) { + subSong->orders.ord[i][j]=reader.readC(); } } for (int i=0; i8) { - logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols); - lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols); + subSong->pat[i].effectCols=reader.readC(); + if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) { + logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); delete[] file; return false; } @@ -1314,25 +1318,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version>=39) { for (int i=0; ichanShow[i]=reader.readC(); } for (int i=0; ichanCollapse[i]=reader.readC(); } if (ds.version<92) { for (int i=0; i0) ds.chanCollapse[i]=3; + if (subSong->chanCollapse[i]>0) subSong->chanCollapse[i]=3; } } for (int i=0; ichanName[i]=reader.readString(); } for (int i=0; ichanShortName[i]=reader.readString(); } ds.notes=reader.readString(); @@ -1405,6 +1409,88 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // subsongs + if (ds.version>=95) { + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + numberOfSubSongs=(unsigned char)reader.readC(); + reader.readC(); // reserved + reader.readC(); + reader.readC(); + // pointers + for (int i=0; itimeBase=reader.readC(); + subSong->speed1=reader.readC(); + subSong->speed2=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + subSong->pal=(subSong->hz>=53); + subSong->customTempo=true; + + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); + + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); + + reader.readI(); // reserved + + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + + logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); + for (int j=0; jordersLen; k++) { + subSong->orders.ord[j][k]=reader.readC(); + } + } + + for (int i=0; ipat[i].effectCols=reader.readC(); + } + + for (int i=0; ichanShow[i]=reader.readC(); + } + + for (int i=0; ichanCollapse[i]=reader.readC(); + } + + for (int i=0; ichanName[i]=reader.readString(); + } + + for (int i=0; ichanShortName[i]=reader.readString(); + } + } + } + // read instruments for (int i=0; i=95) { + subs=reader.readS(); + } else { + reader.readS(); + } + reader.readS(); - logD("- %d, %d",chan,index); + logD("- %d, %d, %d",subs,chan,index); if (chan<0 || chan>=tchans) { logE("pattern channel out of range!",i); @@ -1586,14 +1678,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } - DivPattern* pat=ds.pat[chan].getPattern(index,true); - for (int j=0; jpat[chan].getPattern(index,true); + for (int j=0; jpatLen; j++) { pat->data[j][0]=reader.readS(); pat->data[j][1]=reader.readS(); pat->data[j][2]=reader.readS(); pat->data[j][3]=reader.readS(); - for (int k=0; kpat[chan].effectCols; k++) { pat->data[j][4+(k<<1)]=reader.readS(); pat->data[j][5+(k<<1)]=reader.readS(); } @@ -1615,6 +1714,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -1740,8 +1840,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.sampleLen=ds.sample.size(); // orders - ds.ordersLen=ordCount=reader.readC(); - if (ds.ordersLen<1 || ds.ordersLen>127) { + ds.subsong[0]->ordersLen=ordCount=reader.readC(); + if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) { logD("invalid order count!"); throw EndOfFileException(&reader,reader.tell()); } @@ -1761,7 +1861,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { unsigned char pat=reader.readC(); if (pat>patMax) patMax=pat; for (int j=0; jorders.ord[j][i]=pat; } } @@ -1778,7 +1878,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } // patterns - ds.patLen=64; + ds.subsong[0]->patLen=64; for (int ch=0; chpat[ch].getPattern(pat,true); } for (int row=0; row<64; row++) { for (int ch=0; chdata; + auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data; short lastPitchEffect=-1; short lastEffectState[5]={-1,-1,-1,-1,-1}; short setEffectState[5]={-1,-1,-1,-1,-1}; @@ -1988,25 +2088,25 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } } } - ds.pat[ch].effectCols=fxCols; + ds.subsong[0]->pat[ch].effectCols=fxCols; } - ds.pal=false; - ds.hz=50; - ds.customTempo=false; + ds.subsong[0]->pal=false; + ds.subsong[0]->hz=50; + ds.subsong[0]->customTempo=false; ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)?2:0); // PAL } for(int i=0; ichanShow[i]=true; + ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1); + ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1); } for(int i=chCount; ipat[i].effectCols=1; + ds.subsong[0]->chanShow[i]=false; } // instrument creation @@ -2024,6 +2124,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -2118,8 +2219,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { newVibrato=reader.readI(); } if (blockVersion>=4) { - ds.hilightA=reader.readI(); - ds.hilightB=reader.readI(); + ds.subsong[0]->hilightA=reader.readI(); + ds.subsong[0]->hilightB=reader.readI(); } if (expansions&8) if (blockVersion>=5) { // N163 channels n163Chans=reader.readI(); @@ -2135,12 +2236,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { logV("custom Hz: %d",customHz); logV("new vibrato: %d",newVibrato); logV("N163 channels: %d",n163Chans); - logV("highlight 1: %d",ds.hilightA); - logV("highlight 2: %d",ds.hilightB); + logV("highlight 1: %d",ds.subsong[0]->hilightA); + logV("highlight 2: %d",ds.subsong[0]->hilightB); logV("split point: %d",speedSplitPoint); if (customHz!=0) { - ds.hz=customHz; + ds.subsong[0]->hz=customHz; } // initialize channels @@ -2201,7 +2302,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { for (int j=0; j<=totalSongs; j++) { unsigned char effectCols=reader.readC(); if (j==0) { - ds.pat[i].effectCols=effectCols+1; + ds.subsong[0]->pat[i].effectCols=effectCols+1; } logV("- song %d has %d effect columns",j,effectCols); } @@ -2532,26 +2633,39 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } +struct PatToWrite { + unsigned short subsong, chan, pat; + PatToWrite(unsigned short s, unsigned short c, unsigned short p): + subsong(s), + chan(c), + pat(p) {} +}; + SafeWriter* DivEngine::saveFur(bool notPrimary) { saveLock.lock(); + std::vector subSongPtr; std::vector insPtr; std::vector wavePtr; std::vector samplePtr; std::vector patPtr; - size_t ptrSeek; + size_t ptrSeek, subSongPtrSeek; + size_t subSongIndex=0; + DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; // fail if values are out of range - if (song.ordersLen>256) { + /* + if (subSong->ordersLen>256) { logE("maximum song length is 256!"); lastError="maximum song length is 256"; return NULL; } - if (song.patLen>256) { + if (subSong->patLen>256) { logE("maximum pattern length is 256!"); lastError="maximum pattern length is 256"; return NULL; } + */ if (song.ins.size()>256) { logE("maximum number of instruments is 256!"); lastError="maximum number of instruments is 256"; @@ -2594,14 +2708,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // high short is channel // low short is pattern number - std::vector patsToWrite; + std::vector patsToWrite; bool alreadyAdded[256]; for (int i=0; iordersLen; k++) { + if (alreadyAdded[subs->orders.ord[i][k]]) continue; + patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); + alreadyAdded[subs->orders.ord[i][k]]=true; + } } } @@ -2609,15 +2726,15 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->write("INFO",4); w->writeI(0); - w->writeC(song.timeBase); - w->writeC(song.speed1); - w->writeC(song.speed2); - w->writeC(song.arpLen); - w->writeF(song.hz); - w->writeS(song.patLen); - w->writeS(song.ordersLen); - w->writeC(song.hilightA); - w->writeC(song.hilightB); + w->writeC(subSong->timeBase); + w->writeC(subSong->speed1); + w->writeC(subSong->speed2); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); w->writeS(song.insLen); w->writeS(song.waveLen); w->writeS(song.sampleLen); @@ -2694,29 +2811,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } for (int i=0; iwriteC(song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); } } for (int i=0; iwriteC(song.pat[i].effectCols); + w->writeC(subSong->pat[i].effectCols); } for (int i=0; iwriteC(song.chanShow[i]); + w->writeC(subSong->chanShow[i]); } for (int i=0; iwriteC(song.chanCollapse[i]); + w->writeC(subSong->chanCollapse[i]); } for (int i=0; iwriteString(song.chanName[i],false); + w->writeString(subSong->chanName[i],false); } for (int i=0; iwriteString(song.chanShortName[i],false); + w->writeString(subSong->chanShortName[i],false); } w->writeString(song.notes,false); @@ -2741,6 +2858,67 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; i<18; i++) { w->writeC(0); } + + // subsong list + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + w->writeC((unsigned char)(song.subsong.size()-1)); + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + subSongPtrSeek=w->tell(); + // subsong pointers (we'll seek here later) + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(0); + } + + /// SUBSONGS + for (subSongIndex=1; subSongIndextell()); + w->write("SONG",4); + w->writeI(0); + + w->writeC(subSong->timeBase); + w->writeC(subSong->speed1); + w->writeC(subSong->speed2); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); + w->writeI(0); // reserved + + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + + for (int i=0; iordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(subSong->pat[i].effectCols); + } + + for (int i=0; iwriteC(subSong->chanShow[i]); + } + + for (int i=0; iwriteC(subSong->chanCollapse[i]); + } + + for (int i=0; iwriteString(subSong->chanName[i],false); + } + + for (int i=0; iwriteString(subSong->chanShortName[i],false); + } + } /// INSTRUMENT for (int i=0; i>16].getPattern(i&0xffff,false); + for (PatToWrite& i: patsToWrite) { + DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); w->write("PATR",4); w->writeI(0); - w->writeS(i>>16); - w->writeS(i&0xffff); + w->writeS(i.chan); + w->writeS(i.pat); + w->writeS(i.subsong); - w->writeI(0); // reserved + w->writeS(0); // reserved - for (int j=0; jpatLen; j++) { w->writeS(pat->data[j][0]); // note w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][3]); // volume - w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects + w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects } w->writeString(pat->name,false); @@ -2805,21 +2984,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(insPtr[i]); } - // wavetable pointers (we'll seek here later) + // wavetable pointers for (int i=0; iwriteI(wavePtr[i]); } - // sample pointers (we'll seek here later) + // sample pointers for (int i=0; iwriteI(samplePtr[i]); } - // pattern pointers (we'll seek here later) + // pattern pointers for (int i: patPtr) { w->writeI(i); } + /// SUBSONG POINTERS + w->seek(subSongPtrSeek,SEEK_SET); + + // subsong pointers + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(subSongPtr[i]); + } + saveLock.unlock(); return w; } @@ -2890,7 +3077,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } // fail if values are out of range - if (song.ordersLen>127) { + if (curSubSong->ordersLen>127) { logE("maximum .dmf song length is 127!"); lastError="maximum .dmf song length is 127"; return NULL; @@ -2906,10 +3093,10 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } for (int i=0; i0x7f) { - logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]); - lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + if (curOrders->ord[i][j]>0x7f) { + logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]); + lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]); return NULL; } } @@ -2952,31 +3139,35 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { // song info w->writeString(song.name,true); w->writeString(song.author,true); - w->writeC(song.hilightA); - w->writeC(song.hilightB); + w->writeC(curSubSong->hilightA); + w->writeC(curSubSong->hilightB); - w->writeC(song.timeBase); - w->writeC(song.speed1); - w->writeC(song.speed2); - w->writeC(song.pal); - w->writeC(song.customTempo); + w->writeC(curSubSong->timeBase); + w->writeC(curSubSong->speed1); + w->writeC(curSubSong->speed2); + w->writeC(curSubSong->pal); + w->writeC(curSubSong->customTempo); char customHz[4]; memset(customHz,0,4); - snprintf(customHz,4,"%d",(int)song.hz); + snprintf(customHz,4,"%d",(int)curSubSong->hz); w->write(customHz,3); - w->writeI(song.patLen); - w->writeC(song.ordersLen); + w->writeI(curSubSong->patLen); + w->writeC(curSubSong->ordersLen); for (int i=0; iwriteC(song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + w->writeC(curOrders->ord[i][j]); if (version>=25) { - DivPattern* pat=song.pat[i].getPattern(j,false); + DivPattern* pat=curPat[i].getPattern(j,false); w->writeString(pat->name,true); } } } + if (song.subsong.size()>1) { + addWarning("only the currently selected subsong will be saved"); + } + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("duty precision will be lost"); @@ -3138,15 +3329,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } for (int i=0; iwriteC(song.pat[i].effectCols); + w->writeC(curPat[i].effectCols); - for (int j=0; jordersLen; j++) { + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); + for (int k=0; kpatLen; k++) { w->writeS(pat->data[k][0]); // note w->writeS(pat->data[k][1]); // octave w->writeS(pat->data[k][3]); // volume - w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects + w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects w->writeS(pat->data[k][2]); // instrument } } diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 4f4663cae..0a561376b 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -49,7 +49,7 @@ void DivChannelData::wipePatterns() { } } -void DivPattern::copyOn(DivPattern *dest) { +void DivPattern::copyOn(DivPattern* dest) { dest->name=name; memcpy(dest->data,data,sizeof(data)); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6a27dc65e..7a2356a7e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -30,7 +30,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; - if (++curOrder>=song.ordersLen) { + if (++curOrder>=curSubSong->ordersLen) { endOfSong=true; curOrder=0; } @@ -266,11 +266,11 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char void DivEngine::processRow(int i, bool afterDelay) { int whatOrder=afterDelay?chan[i].delayOrder:curOrder; int whatRow=afterDelay?chan[i].delayRow:curRow; - DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][whatOrder],false); // pre effects if (!afterDelay) { bool returnAfterPre=false; - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -290,7 +290,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } break; case 0x0d: // next order - if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { + if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { changeOrd=-2; changePos=effectVal; } @@ -405,7 +405,7 @@ void DivEngine::processRow(int i, bool afterDelay) { bool panChanged=false; // effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -548,7 +548,7 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xe0: // arp speed if (effectVal>0) { - song.arpLen=effectVal; + curSubSong->arpLen=effectVal; } break; case 0xe1: // portamento up @@ -727,7 +727,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].noteOnInhibit=false; // post effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -745,10 +745,10 @@ void DivEngine::nextRow() { strcpy(pb1,""); strcpy(pb3,""); for (int i=0; iord[i][curOrder]); strcat(pb1,pb); - DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false); + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false); snprintf(pb2,4095,"\x1b[37m %s", formatNote(pat->data[curRow][0],pat->data[curRow][1])); strcat(pb3,pb2); @@ -764,7 +764,7 @@ void DivEngine::nextRow() { snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]); strcat(pb3,pb2); } - for (int j=0; jdata[curRow][4+(j<<1)]==-1) { strcat(pb3,"\x1b[m--"); } else { @@ -796,32 +796,32 @@ void DivEngine::nextRow() { if (changeOrd==-2) changeOrd=curOrder+1; if (changeOrd<=curOrder) endOfSong=true; curOrder=changeOrd; - if (curOrder>=song.ordersLen) { + if (curOrder>=curSubSong->ordersLen) { curOrder=0; endOfSong=true; } changeOrd=-1; } if (haltOn==DIV_HALT_PATTERN) halted=true; - } else if (playing) if (++curRow>=song.patLen) { + } else if (playing) if (++curRow>=curSubSong->patLen) { nextOrder(); if (haltOn==DIV_HALT_PATTERN) halted=true; } if (song.brokenSpeedSel) { - if ((song.patLen&1) && curOrder&1) { - ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1); + if ((curSubSong->patLen&1) && curOrder&1) { + ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); nextSpeed=(curRow&1)?speed1:speed2; } else { - ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1); + ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1); nextSpeed=(curRow&1)?speed2:speed1; } } else { if (speedAB) { - ticks=speed2*(song.timeBase+1); + ticks=speed2*(curSubSong->timeBase+1); nextSpeed=speed1; } else { - ticks=speed1*(song.timeBase+1); + ticks=speed1*(curSubSong->timeBase+1); nextSpeed=speed2; } speedAB=!speedAB; @@ -829,7 +829,7 @@ void DivEngine::nextRow() { // post row details for (int i=0; iord[i][curOrder],false); if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) { if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { if (!chan[i].legato) { @@ -838,7 +838,7 @@ void DivEngine::nextRow() { if (song.oneTickCut) { bool doPrepareCut=true; - for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { doPrepareCut=false; break; @@ -1007,7 +1007,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { if (--chan[i].arpTicks<1) { - chan[i].arpTicks=song.arpLen; + chan[i].arpTicks=curSubSong->arpLen; chan[i].arpStage++; if (chan[i].arpStage>2) chan[i].arpStage=0; switch (chan[i].arpStage) { @@ -1048,7 +1048,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } - if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); + if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); } if (haltOn==DIV_HALT_TICK) halted=true; @@ -1240,11 +1240,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (!freelance && stepPlay!=-1 && subticks==1) { unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC); if (realPos>=size) realPos=size-1; - if (song.hilightA>0) { - if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1; + if (curSubSong->hilightA>0) { + if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1; } - if (song.hilightB>0) { - if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2; + if (curSubSong->hilightB>0) { + if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2; } } if (nextTick()) { diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 4c142c5f3..216a1bc4d 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -19,9 +19,7 @@ #include "song.h" -DivOrders emptyOrders; - -void DivSong::clearSongData() { +void DivSubSong::clearData() { for (int i=0; iclearData(); + delete i; + } + subsong.clear(); + subsong.push_back(new DivSubSong); +} + void DivSong::clearInstruments() { for (DivInstrument* i: ins) { delete i; @@ -73,7 +80,9 @@ void DivSong::unload() { sample.clear(); sampleLen=0; - for (int i=0; iclearData(); + delete i; } + subsong.clear(); } diff --git a/src/engine/song.h b/src/engine/song.h index b89d56ad0..219225e8f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -110,8 +110,6 @@ enum DivSystem { DIV_SYSTEM_DUMMY }; -extern DivOrders emptyOrders; - struct DivSubSong { String name, notes; unsigned char hilightA, hilightB; @@ -126,8 +124,23 @@ struct DivSubSong { bool chanShow[DIV_MAX_CHANS]; unsigned char chanCollapse[DIV_MAX_CHANS]; + String chanName[DIV_MAX_CHANS]; + String chanShortName[DIV_MAX_CHANS]; - DivSubSong() { + void clearData(); + + DivSubSong(): + hilightA(4), + hilightB(16), + timeBase(0), + speed1(6), + speed2(6), + arpLen(1), + pal(true), + customTempo(false), + hz(60.0), + patLen(64), + ordersLen(1) { for (int i=0; i ins; - DivChannelData* pat; std::vector wave; std::vector sample; - std::vector subsongs; - - bool* chanShow; - unsigned char* chanCollapse; + std::vector subsong; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivWavetable nullWave; @@ -435,17 +434,6 @@ struct DivSong { manInfo(""), createdDate(""), revisionDate(""), - hilightA(4), - hilightB(16), - timeBase(0), - speed1(6), - speed2(6), - arpLen(1), - pal(true), - customTempo(false), - hz(60.0), - patLen(64), - ordersLen(1), insLen(0), waveLen(0), sampleLen(0), @@ -491,11 +479,7 @@ struct DivSong { systemPan[i]=0; systemFlags[i]=0; } - subsongs.push_back(DivSubSong()); - chanShow=subsongs[0].chanShow; - chanCollapse=subsongs[0].chanCollapse; - orders=&subsongs[0].orders; - pat=subsongs[0].pat; + subsong.push_back(new DivSubSong); system[0]=DIV_SYSTEM_YM2612; system[1]=DIV_SYSTEM_SMS; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 2c90142e0..10f8907f4 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -283,7 +283,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { const char* DivEngine::getChannelName(int chan) { if (chan<0 || chan>chans) return "??"; - if (!song.chanName[chan].empty()) return song.chanName[chan].c_str(); + if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); if (sysDefs[sysOfChan[chan]]==NULL) return "??"; const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]]; @@ -293,7 +293,7 @@ const char* DivEngine::getChannelName(int chan) { const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>chans) return "??"; - if (!song.chanShortName[chan].empty()) return song.chanShortName[chan].c_str(); + if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); if (sysDefs[sysOfChan[chan]]==NULL) return "??"; const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]]; diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h index 752a54423..81685af48 100644 --- a/src/gui/actionUtil.h +++ b/src/gui/actionUtil.h @@ -20,7 +20,7 @@ #define DETERMINE_FIRST \ int firstChannel=0; \ for (int i=0; igetTotalChannelCount(); i++) { \ - if (e->song.chanShow[i]) { \ + if (e->curSubSong->chanShow[i]) { \ firstChannel=i; \ break; \ } \ @@ -29,7 +29,7 @@ #define DETERMINE_LAST \ int lastChannel=0; \ for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ - if (e->song.chanShow[i]) { \ + if (e->curSubSong->chanShow[i]) { \ lastChannel=i+1; \ break; \ } \ diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 397ac2f65..c88075611 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -38,7 +38,7 @@ void FurnaceGUI::drawChannels() { ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Checkbox("##Visible",&e->song.chanShow[i]); + ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]); ImGui::SameLine(); ImGui::BeginDisabled(i==0); if (ImGui::Button(ICON_FA_CHEVRON_UP)) { @@ -53,10 +53,10 @@ void FurnaceGUI::drawChannels() { ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); + ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->curSubSong->chanName[i]); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); + ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->curSubSong->chanShortName[i]); ImGui::PopID(); } ImGui::EndTable(); diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 972beee02..244880735 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -32,7 +32,7 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) { selStart.xCoarse=firstChannel; selStart.xFine=0; selEnd.xCoarse=lastChannel-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; selStart.y=y; selEnd.y=y; } else { @@ -56,7 +56,7 @@ void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) { if (selectingFull) { DETERMINE_LAST; selEnd.xCoarse=lastChannel-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; selEnd.y=y; } else { selEnd.xCoarse=xCoarse; @@ -94,21 +94,21 @@ void FurnaceGUI::finishSelection() { if (selStart.xCoarse<0) selStart.xCoarse=0; if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; if (selStart.y<0) selStart.y=0; - if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; + if (selStart.y>=e->curSubSong->patLen) selStart.y=e->curSubSong->patLen-1; if (selEnd.xCoarse<0) selEnd.xCoarse=0; if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; if (selEnd.y<0) selEnd.y=0; - if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; + if (selEnd.y>=e->curSubSong->patLen) selEnd.y=e->curSubSong->patLen-1; if (cursor.xCoarse<0) cursor.xCoarse=0; if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; if (cursor.y<0) cursor.y=0; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; - if (e->song.chanCollapse[selStart.xCoarse]==3) { + if (e->curSubSong->chanCollapse[selStart.xCoarse]==3) { selStart.xFine=0; } - if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) { - selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + if (e->curSubSong->chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->curSubSong->chanCollapse[selEnd.xCoarse])) { + selEnd.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; } e->setMidiBaseChan(cursor.xCoarse); @@ -126,7 +126,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { demandScrollX=true; if (x>0) { for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?(4-e->song.chanCollapse[cursor.xCoarse]):(3+e->song.pat[cursor.xCoarse].effectCols*2))) { + if (++cursor.xFine>=(e->curSubSong->chanCollapse[cursor.xCoarse]?(4-e->curSubSong->chanCollapse[cursor.xCoarse]):(3+e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=0; if (++cursor.xCoarse>=lastChannel) { if (settings.wrapHorizontal!=0 && !select) { @@ -134,10 +134,10 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (settings.wrapHorizontal==2) y++; } else { cursor.xCoarse=lastChannel-1; - cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?(3-e->song.chanCollapse[cursor.xCoarse]):(2+e->song.pat[cursor.xCoarse].effectCols*2); + cursor.xFine=e->curSubSong->chanCollapse[cursor.xCoarse]?(3-e->curSubSong->chanCollapse[cursor.xCoarse]):(2+e->curPat[cursor.xCoarse].effectCols*2); } } else { - while (!e->song.chanShow[cursor.xCoarse]) { + while (!e->curSubSong->chanShow[cursor.xCoarse]) { cursor.xCoarse++; if (cursor.xCoarse>=e->getTotalChannelCount()) break; } @@ -150,21 +150,21 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; if (settings.wrapHorizontal==2) y--; } else { cursor.xCoarse=firstChannel; cursor.xFine=0; } } else { - while (!e->song.chanShow[cursor.xCoarse]) { + while (!e->curSubSong->chanShow[cursor.xCoarse]) { cursor.xCoarse--; if (cursor.xCoarse<0) break; } - if (e->song.chanCollapse[cursor.xCoarse]) { - cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse]; + if (e->curSubSong->chanCollapse[cursor.xCoarse]) { + cursor.xFine=3-e->curSubSong->chanCollapse[cursor.xCoarse]; } else { - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; } } } @@ -175,18 +175,18 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (y>0) { for (int i=0; i=e->song.patLen) { + if (cursor.y>=e->curSubSong->patLen) { if (settings.wrapVertical!=0 && !select) { cursor.y=0; if (settings.wrapVertical==2) { - if ((!e->isPlaying() || !followPattern) && curOrder<(e->song.ordersLen-1)) { + if ((!e->isPlaying() || !followPattern) && curOrder<(e->curSubSong->ordersLen-1)) { setOrder(curOrder+1); } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } } } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } } } @@ -195,7 +195,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { cursor.y--; if (cursor.y<0) { if (settings.wrapVertical!=0 && !select) { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; if (settings.wrapVertical==2) { if ((!e->isPlaying() || !followPattern) && curOrder>0) { setOrder(curOrder-1); @@ -229,7 +229,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) { do { cursor.xCoarse--; if (cursor.xCoarse<0) break; - } while (!e->song.chanShow[cursor.xCoarse]); + } while (!e->curSubSong->chanShow[cursor.xCoarse]); if (cursor.xCoarse=e->getTotalChannelCount()) break; - } while (!e->song.chanShow[cursor.xCoarse]); + } while (!e->curSubSong->chanShow[cursor.xCoarse]); if (cursor.xCoarse>=lastChannel) { if (overflow) { cursor.xCoarse=firstChannel; @@ -290,14 +290,14 @@ void FurnaceGUI::moveCursorTop(bool select) { void FurnaceGUI::moveCursorBottom(bool select) { finishSelection(); curNibble=false; - if (cursor.y==e->song.patLen-1) { + if (cursor.y==e->curSubSong->patLen-1) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; if (cursor.xCoarse<0) cursor.xCoarse=0; - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; demandScrollX=true; } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } if (!select) { selStart=cursor; @@ -310,7 +310,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { void FurnaceGUI::editAdvance() { finishSelection(); cursor.y+=editStep; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; selStart=cursor; selEnd=cursor; updateScroll(cursor.y); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 8a71d9a38..9a364e233 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -470,7 +470,7 @@ void FurnaceGUI::doAction(int what) { e->unmuteAll(); break; case GUI_ACTION_PAT_NEXT_ORDER: - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } break; @@ -481,21 +481,21 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_COLLAPSE: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - if (e->song.chanCollapse[cursor.xCoarse]==0) { - e->song.chanCollapse[cursor.xCoarse]=3; - } else if (e->song.chanCollapse[cursor.xCoarse]>0) { - e->song.chanCollapse[cursor.xCoarse]--; + if (e->curSubSong->chanCollapse[cursor.xCoarse]==0) { + e->curSubSong->chanCollapse[cursor.xCoarse]=3; + } else if (e->curSubSong->chanCollapse[cursor.xCoarse]>0) { + e->curSubSong->chanCollapse[cursor.xCoarse]--; } break; case GUI_ACTION_PAT_INCREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectCols++; - if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8; + e->curPat[cursor.xCoarse].effectCols++; + if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8; break; case GUI_ACTION_PAT_DECREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectCols--; - if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1; + e->curPat[cursor.xCoarse].effectCols--; + if (e->curPat[cursor.xCoarse].effectCols<1) e->curPat[cursor.xCoarse].effectCols=1; break; case GUI_ACTION_PAT_INTERPOLATE: doInterpolate(); @@ -1196,7 +1196,7 @@ void FurnaceGUI::doAction(int what) { } break; case GUI_ACTION_ORDERS_DOWN: - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } break; @@ -1209,7 +1209,7 @@ void FurnaceGUI::doAction(int what) { orderCursor=firstChannel; break; } - } while (!e->song.chanShow[orderCursor]); + } while (!e->curSubSong->chanShow[orderCursor]); break; } case GUI_ACTION_ORDERS_RIGHT: { @@ -1221,20 +1221,20 @@ void FurnaceGUI::doAction(int what) { orderCursor=lastChannel-1; break; } - } while (!e->song.chanShow[orderCursor]); + } while (!e->curSubSong->chanShow[orderCursor]); break; } case GUI_ACTION_ORDERS_INCREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { - e->song.orders.ord[orderCursor][curOrder]++; + if (e->curOrders->ord[orderCursor][curOrder]<0x7f) { + e->curOrders->ord[orderCursor][curOrder]++; } break; } case GUI_ACTION_ORDERS_DECREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - if (e->song.orders.ord[orderCursor][curOrder]>0) { - e->song.orders.ord[orderCursor][curOrder]--; + if (e->curOrders->ord[orderCursor][curOrder]>0) { + e->curOrders->ord[orderCursor][curOrder]--; } break; } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 722c38f2a..199a7de00 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -45,7 +45,7 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Edit Step"); ImGui::SameLine(); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -148,7 +148,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -217,7 +217,7 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Step"); ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##EditStep",&editStep,0,0)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -317,7 +317,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SetCursorPosX(cursor); ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 8832b7fbb..ea12eb591 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -44,8 +44,8 @@ const char* noteNameNormal(short note, short octave) { void FurnaceGUI::prepareUndo(ActionType action) { switch (action) { case GUI_UNDO_CHANGE_ORDER: - oldOrders=e->song.orders; - oldOrdersLen=e->song.ordersLen; + memcpy(&oldOrders,e->curOrders,sizeof(DivOrders)); + oldOrdersLen=e->curSubSong->ordersLen; break; case GUI_UNDO_PATTERN_EDIT: case GUI_UNDO_PATTERN_DELETE: @@ -63,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false)->copyOn(oldPat[i]); + e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; } @@ -78,18 +78,19 @@ void FurnaceGUI::makeUndo(ActionType action) { s.selEnd=selEnd; s.order=curOrder; s.nibble=curNibble; + size_t subSong=e->getCurrentSubSong(); switch (action) { case GUI_UNDO_CHANGE_ORDER: for (int i=0; isong.orders.ord[i][j]) { - s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); + if (oldOrders.ord[i][j]!=e->curOrders->ord[i][j]) { + s.ord.push_back(UndoOrderData(subSong,i,j,oldOrders.ord[i][j],e->curOrders->ord[i][j])); } } } s.oldOrdersLen=oldOrdersLen; - s.newOrdersLen=e->song.ordersLen; - if (oldOrdersLen!=e->song.ordersLen) { + s.newOrdersLen=e->curSubSong->ordersLen; + if (oldOrdersLen!=e->curSubSong->ordersLen) { doPush=true; } if (!s.ord.empty()) { @@ -112,11 +113,11 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false); - for (int j=0; jsong.patLen; j++) { + DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false); + for (int j=0; jcurSubSong->patLen; j++) { for (int k=0; k<32; k++) { if (p->data[j][k]!=oldPat[i]->data[j][k]) { - s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); + s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); } } } @@ -137,15 +138,15 @@ void FurnaceGUI::makeUndo(ActionType action) { void FurnaceGUI::doSelectAll() { finishSelection(); curNibble=false; - if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) { - if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern + if (selStart.xFine==0 && selEnd.xFine==2+e->curPat[selEnd.xCoarse].effectCols*2) { + if (selStart.y==0 && selEnd.y==e->curSubSong->patLen-1) { // select entire pattern selStart.xCoarse=0; selStart.xFine=0; selEnd.xCoarse=e->getTotalChannelCount()-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; } else { // select entire column selStart.y=0; - selEnd.y=e->song.patLen-1; + selEnd.y=e->curSubSong->patLen-1; } } else { int selStartX=0; @@ -153,26 +154,26 @@ void FurnaceGUI::doSelectAll() { // find row position for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { + if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { + if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); - if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down + if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->curSubSong->patLen-1)) { // up-down selStart.y=0; - selEnd.y=e->song.patLen-1; + selEnd.y=e->curSubSong->patLen-1; } else { // left-right selStart.xFine=0; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; } } } @@ -198,9 +199,9 @@ void FurnaceGUI::doDelete() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.patLen; j++) { - if (jsong.patLen-1) { + for (int j=selStart.y; jcurSubSong->patLen; j++) { + if (jcurSubSong->patLen-1) { if (iFine==0) { pat->data[j][iFine]=pat->data[j+1][iFine]; } @@ -267,11 +268,11 @@ void FurnaceGUI::doInsert() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { + for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) { if (j==selStart.y) { if (iFine==0) { pat->data[j][iFine]=0; @@ -299,9 +300,9 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][0],pat->data[j][1]); if (cut) { @@ -432,7 +433,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int j=cursor.y; char note[4]; - for (size_t i=2; isong.patLen; i++) { + for (size_t i=2; icurSubSong->patLen; i++) { size_t charPos=0; int iCoarse=cursor.xCoarse; int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; @@ -440,10 +441,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { String& line=data[i]; while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); if (line[charPos]=='|') { iCoarse++; - if (iCoarsesong.chanShow[iCoarse]) { + if (iCoarsecurSubSong->chanShow[iCoarse]) { iCoarse++; if (iCoarse>=lastChannel) break; } @@ -530,7 +531,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { - if (iFine<(3+e->song.pat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; + if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; } } } @@ -543,7 +544,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; - if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrdersong.ordersLen-1) { + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->curSubSong->patLen && curOrdercurSubSong->ordersLen-1) { j=0; curOrder++; } @@ -554,7 +555,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (settings.cursorPastePos) { cursor.y=j; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; updateScroll(cursor.y); } @@ -567,8 +568,8 @@ void FurnaceGUI::doChangeIns(int ins) { int iCoarse=selStart.xCoarse; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); for (int j=selStart.y; j<=selEnd.y; j++) { if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { pat->data[j][2]=ins; @@ -587,9 +588,9 @@ void FurnaceGUI::doInterpolate() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][iFine+1]; } for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->song.patLen) break; + if ((j+selStart.y)>=e->curSubSong->patLen) break; if ((j%multiplier)!=0) { if (iFine==0) { pat->data[j+selStart.y][0]=0; @@ -921,9 +922,10 @@ void FurnaceGUI::doUndo() { switch (us.type) { case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.oldOrdersLen; + e->curSubSong->ordersLen=us.oldOrdersLen; for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.oldVal; + e->changeSongP(i.subSong); + e->curOrders->ord[i.chan][i.ord]=i.oldVal; } break; case GUI_UNDO_PATTERN_EDIT: @@ -942,7 +944,8 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + e->changeSongP(i.subSong); + DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; } if (!e->isPlaying() || !followPattern) { @@ -967,9 +970,10 @@ void FurnaceGUI::doRedo() { switch (us.type) { case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.newOrdersLen; + e->curSubSong->ordersLen=us.newOrdersLen; for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.newVal; + e->changeSongP(i.subSong); + e->curOrders->ord[i.chan][i.ord]=i.newVal; } break; case GUI_UNDO_PATTERN_EDIT: @@ -988,7 +992,8 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + e->changeSongP(i.subSong); + DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; } if (!e->isPlaying()) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 00f0da2b3..634482e91 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -793,9 +793,9 @@ void FurnaceGUI::prepareLayout() { } float FurnaceGUI::calcBPM(int s1, int s2, float hz) { - float hl=e->song.hilightA; + float hl=e->curSubSong->hilightA; if (hl<=0.0f) hl=4.0f; - float timeBase=e->song.timeBase+1; + float timeBase=e->curSubSong->timeBase+1; float speedSum=s1+s2; if (timeBase<1.0f) timeBase=1.0f; if (speedSum<1.0f) speedSum=1.0f; @@ -857,7 +857,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { } void FurnaceGUI::noteInput(int num, int key, int vol) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -901,7 +901,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { } void FurnaceGUI::valueInput(int num, bool direct, int target) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); if (target==-1) target=cursor.xFine+1; if (direct) { @@ -965,7 +965,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { editAdvance(); } else { if (settings.effectCursorDir==2) { - if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectCols*2))) { + if (++cursor.xFine>=(3+(e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=3; } } else { @@ -1123,7 +1123,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=valueKeys.at(ev.key.keysym.sym); if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { e->lockSave([this,num]() { - e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num); + e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; @@ -1132,7 +1132,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { orderCursor++; if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; } else if (orderEditMode==3) { - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } } @@ -2031,7 +2031,7 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::PopFont(); ImGui::SameLine(); if (ImGui::Button("Set")) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); latchIns=pat->data[cursor.y][2]; latchVol=pat->data[cursor.y][3]; latchEffect=pat->data[cursor.y][4]; @@ -2817,12 +2817,12 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); - ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; if (cursor.xCoarse>=0 && cursor.xCoarsegetTotalChannelCount()) { - DivPattern* p=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],false); + DivPattern* p=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false); if (cursor.xFine>=0) switch (cursor.xFine) { case 0: // note if (p->data[cursor.y][0]>0) { @@ -2883,6 +2883,7 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); + drawSubSongs(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3491,7 +3492,7 @@ bool FurnaceGUI::loop() { stop(); e->lockEngine([this]() { for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],true); + DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],true); memset(pat->data,-1,256*32*sizeof(short)); for (int j=0; j<256; j++) { pat->data[j][0]=0; @@ -3650,6 +3651,7 @@ bool FurnaceGUI::init() { regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); + subSongsOpen=e->getConfBool("subSongsOpen",true); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); @@ -3841,6 +3843,7 @@ bool FurnaceGUI::finish() { e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); + e->setConf("subSongsOpen",subSongsOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -3961,6 +3964,7 @@ FurnaceGUI::FurnaceGUI(): logOpen(false), effectListOpen(false), chanOscOpen(false), + subSongsOpen(true), /* editControlsDocked(false), ordersDocked(false), @@ -3988,6 +3992,7 @@ FurnaceGUI::FurnaceGUI(): logDocked(false), effectListDocked(false), chanOscDocked(false), + subSongsDocked(false), */ selecting(false), selectingFull(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 4a5045129..b976a7cb7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -231,7 +231,8 @@ enum FurnaceGUIWindows { GUI_WINDOW_REGISTER_VIEW, GUI_WINDOW_LOG, GUI_WINDOW_EFFECT_LIST, - GUI_WINDOW_CHAN_OSC + GUI_WINDOW_CHAN_OSC, + GUI_WINDOW_SUBSONGS }; enum FurnaceGUIFileDialogs { @@ -337,6 +338,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_LOG, GUI_ACTION_WINDOW_EFFECT_LIST, GUI_ACTION_WINDOW_CHAN_OSC, + GUI_ACTION_WINDOW_SUBSONGS, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -550,9 +552,10 @@ enum ActionType { }; struct UndoPatternData { - int chan, pat, row, col; + int subSong, chan, pat, row, col; short oldVal, newVal; - UndoPatternData(int c, int p, int r, int co, short v1, short v2): + UndoPatternData(int s, int c, int p, int r, int co, short v1, short v2): + subSong(s), chan(c), pat(p), row(r), @@ -562,9 +565,10 @@ struct UndoPatternData { }; struct UndoOrderData { - int chan, ord; + int subSong, chan, ord; unsigned char oldVal, newVal; - UndoOrderData(int c, int o, unsigned char v1, unsigned char v2): + UndoOrderData(int s, int c, int o, unsigned char v1, unsigned char v2): + subSong(s), chan(c), ord(o), oldVal(v1), @@ -1010,12 +1014,14 @@ class FurnaceGUI { bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; + bool subSongsOpen; /* there ought to be a better way... bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked; bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked; bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked; + bool subSongsDocked; */ SelectionPoint selStart, selEnd, cursor; @@ -1246,6 +1252,7 @@ class FurnaceGUI { void drawNewSong(); void drawLog(); void drawEffectList(); + void drawSubSongs(); void parseKeybinds(); void promptKey(int which); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 0afee14ca..31aad6432 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -485,6 +485,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_CHANNELS", "Channels", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), + D("WINDOW_SUBSONGS", "Subsongs", 0), D("EFFECT_LIST", "Effect List", 0), D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 2311a3980..e9886a1ae 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -37,7 +37,7 @@ void FurnaceGUI::drawOrders() { ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale); int displayChans=0; for (int i=0; igetTotalChannelCount(); i++) { - if (e->song.chanShow[i]) displayChans++; + if (e->curSubSong->chanShow[i]) displayChans++; } ImGui::PushFont(patFont); bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); @@ -56,12 +56,12 @@ void FurnaceGUI::drawOrders() { ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); for (int i=0; igetTotalChannelCount(); i++) { - if (!e->song.chanShow[i]) continue; + if (!e->curSubSong->chanShow[i]) continue; ImGui::TableNextColumn(); ImGui::Text("%s",e->getChannelShortName(i)); } ImGui::PopStyleColor(); - for (int i=0; isong.ordersLen; i++) { + for (int i=0; icurSubSong->ordersLen; i++) { ImGui::TableNextRow(0,lineHeight); if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); ImGui::TableNextColumn(); @@ -84,16 +84,16 @@ void FurnaceGUI::drawOrders() { } ImGui::PopStyleColor(); for (int j=0; jgetTotalChannelCount(); j++) { - if (!e->song.chanShow[j]) continue; + if (!e->curSubSong->chanShow[j]) continue; ImGui::TableNextColumn(); - DivPattern* pat=e->song.pat[j].getPattern(e->song.orders.ord[j][i],false); + DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false); /*if (!pat->name.empty()) { snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i); } else {*/ - snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i); + snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i); //} - ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->song.orders.ord[j][i]==e->song.orders.ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) { if (curOrder==i) { if (orderEditMode==0) { @@ -101,10 +101,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]<0xff) e->song.orders.ord[k][i]++; + if (e->curOrders->ord[k][i]<0xff) e->curOrders->ord[k][i]++; } } else { - if (e->song.orders.ord[j][i]<0xff) e->song.orders.ord[j][i]++; + if (e->curOrders->ord[j][i]<0xff) e->curOrders->ord[j][i]++; } }); e->walkSong(loopOrder,loopRow,loopEnd); @@ -137,10 +137,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--; } } else { - if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; + if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--; } }); e->walkSong(loopOrder,loopRow,loopEnd); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index ae9bedb2c..b7ca4bed9 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -43,21 +43,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int return; } // check if we are in range - if (ord<0 || ord>=e->song.ordersLen) { + if (ord<0 || ord>=e->curSubSong->ordersLen) { return; } - if (i<0 || i>=e->song.patLen) { + if (i<0 || i>=e->curSubSong->patLen) { return; } bool isPushing=false; ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE]; ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE]; ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX]; - if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2]; rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2]; - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1]; rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1]; @@ -68,9 +68,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); - } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + } else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1])); } } else { @@ -79,9 +79,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); - } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + } else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1])); } else { isPushing=false; @@ -106,7 +106,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // for each column for (int j=0; j