From efb89f7f94025542119071db2bb9975bfea7c890 Mon Sep 17 00:00:00 2001 From: LTVA1 <87536432+LTVA1@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:12:20 +0300 Subject: [PATCH 1/6] yeah --- src/engine/platform/nes.cpp | 52 ++++++++++++++++++++++++++++++++++--- src/engine/platform/nes.h | 8 ++++++ src/gui/sampleEdit.cpp | 15 +++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 4101b9f61..e48a581cc 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -28,7 +28,7 @@ struct _nla_table nla_table; #define CHIP_DIVIDER 16 -#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite((a),v)); if (dumpWrites) {addWrite((a),v);} } const char* regCheatSheetNES[]={ "S0Volume", "4000", @@ -86,10 +86,10 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \ if (dacAntiClickOn && dacAntiClickapu.odd_cycle=!nes->apu.odd_cycle; @@ -134,6 +142,14 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) { int out2[2]; for (size_t i=0; iTick(8); nes2_NP->TickFrameSequence(8); @@ -400,6 +416,35 @@ void DivPlatformNES::tick(bool sysTick) { logV("switching bank to %d",dpcmBank); if (dumpWrites) addWrite(0xffff0004,dpcmBank); } + //sample custom loop point... + + DivSample* lsamp = parent->getSample(dacSample); + + //how it works: + //when the initial sample info is written (see above) and playback is launched, + //the parameters (start point in memory and length) are locked until sample end + //is reached. + + //thus, if we write new data after just several APU clock cycles, it will be used only when + //sample finishes one full loop. + + //thus we can write sample's loop point as "start address" and sample's looped part length + //as "full sample length". + + //APU will play full sample once and then repeatedly cycle through the looped part. + + //sources: + //https://www.nesdev.org/wiki/APU_DMC + //https://www.youtube.com/watch?v=vB4P8x2Am6Y + + if(lsamp->loopEnd > lsamp->loopStart && goingToLoop) + { + int loop_start_addr = (sampleOffDPCM[dacSample] + lsamp->loopStart) / 8; + int loop_len = (lsamp->loopEnd - lsamp->loopStart) / 8; + + rWrite(0x4012,(loop_start_addr >> 6)&0xff); + rWrite(0x4013,(loop_len >> 4)&0xff); + } } } else { if (nextDPCMFreq>=0) { @@ -790,6 +835,7 @@ float DivPlatformNES::getPostAmp() { } void DivPlatformNES::reset() { + while (!writes.empty()) writes.pop(); for (int i=0; i<5; i++) { chan[i]=DivPlatformNES::Channel(); chan[i].std.setEngine(parent); diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index cccbe0b18..21e2779e5 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -24,6 +24,7 @@ #include "sound/nes_nsfplay/nes_apu.h" #include "sound/nes_nsfplay/5e01_apu.h" +#include "../../fixedQueue.h" class DivPlatformNES: public DivDispatch { struct Channel: public SharedChannel { @@ -44,6 +45,13 @@ class DivPlatformNES: public DivDispatch { Channel chan[5]; DivDispatchOscBuffer* oscBuf[5]; bool isMuted[5]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + }; + FixedQueue writes; int dacPeriod, dacRate, dpcmPos; unsigned int dacPos, dacAntiClick; int dacSample; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 88337e39c..08c0a7438 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -254,15 +254,26 @@ void FurnaceGUI::drawSampleEdit() { } break; case DIV_SYSTEM_NES: + { if (sample->loop) { - if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { - SAMPLE_WARN(warnLoopPos,_("NES: loop point ignored on DPCM (may only loop entire sample)")); + if (sample->loopStart&511) { + int tryWith=(sample->loopStart)&(~511); + if (tryWith>(int)sample->samples) tryWith-=512; + String alignHint=fmt::sprintf(_("NES: loop start must be a multiple of 512 (try with %d)"),tryWith); + SAMPLE_WARN(warnLoopStart,alignHint); + } + if ((sample->loopEnd)&127) { + int tryWith=(sample->loopEnd + 1)&(~127); //+1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC + if (tryWith>(int)sample->samples) tryWith-=128; + String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith); + SAMPLE_WARN(warnLoopEnd,alignHint); } } if (sample->samples>32648) { SAMPLE_WARN(warnLength,_("NES: maximum DPCM sample length is 32648")); } break; + } case DIV_SYSTEM_X1_010: if (sample->loop) { SAMPLE_WARN(warnLoop,_("X1-010: samples can't loop")); From c06759b235f49ede75ae274356df52bab02832c8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 24 Aug 2024 16:43:15 -0500 Subject: [PATCH 2/6] NES:codestyle --- src/engine/platform/nes.cpp | 49 +++++++++++++++++-------------------- src/gui/about.cpp | 1 + src/gui/sampleEdit.cpp | 6 ++--- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index e48a581cc..67fdbc11e 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -109,8 +109,7 @@ void DivPlatformNES::acquire_puNES(short** buf, size_t len) { for (size_t i=0; igetSample(dacSample); + // sample custom loop point... + DivSample* lsamp=parent->getSample(dacSample); - //how it works: - //when the initial sample info is written (see above) and playback is launched, - //the parameters (start point in memory and length) are locked until sample end - //is reached. + // how it works: + // when the initial sample info is written (see above) and playback is launched, + // the parameters (start point in memory and length) are locked until sample end + // is reached. - //thus, if we write new data after just several APU clock cycles, it will be used only when - //sample finishes one full loop. + // thus, if we write new data after just several APU clock cycles, it will be used only when + // sample finishes one full loop. - //thus we can write sample's loop point as "start address" and sample's looped part length - //as "full sample length". + // thus we can write sample's loop point as "start address" and sample's looped part length + // as "full sample length". - //APU will play full sample once and then repeatedly cycle through the looped part. + // APU will play full sample once and then repeatedly cycle through the looped part. - //sources: - //https://www.nesdev.org/wiki/APU_DMC - //https://www.youtube.com/watch?v=vB4P8x2Am6Y + // sources: + // https://www.nesdev.org/wiki/APU_DMC + // https://www.youtube.com/watch?v=vB4P8x2Am6Y - if(lsamp->loopEnd > lsamp->loopStart && goingToLoop) - { - int loop_start_addr = (sampleOffDPCM[dacSample] + lsamp->loopStart) / 8; - int loop_len = (lsamp->loopEnd - lsamp->loopStart) / 8; + if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) { + int loopStartAddr=(sampleOffDPCM[dacSample]+lsamp->loopStart)>>3; + int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3; - rWrite(0x4012,(loop_start_addr >> 6)&0xff); - rWrite(0x4013,(loop_len >> 4)&0xff); + rWrite(0x4012,(loopStartAddr>>6)&0xff); + rWrite(0x4013,(loopLen>>4)&0xff); } } } else { diff --git a/src/gui/about.cpp b/src/gui/about.cpp index efa95465b..66d229006 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -302,6 +302,7 @@ const char* aboutLine[]={ _N("NDS sound emulator by cam900"), "", _N("greetings to:"), + "floxy!", "NEOART Costa Rica", "Xenium Demoparty", "@party", diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 08c0a7438..aefe38cfb 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -253,8 +253,7 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535"); } break; - case DIV_SYSTEM_NES: - { + case DIV_SYSTEM_NES: { if (sample->loop) { if (sample->loopStart&511) { int tryWith=(sample->loopStart)&(~511); @@ -263,7 +262,8 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_WARN(warnLoopStart,alignHint); } if ((sample->loopEnd)&127) { - int tryWith=(sample->loopEnd + 1)&(~127); //+1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC + // +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC + int tryWith=(sample->loopEnd + 1)&(~127); if (tryWith>(int)sample->samples) tryWith-=128; String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith); SAMPLE_WARN(warnLoopEnd,alignHint); From 0ab9f6c6fd6979c352a073857b7f9d51f12b03d0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 24 Aug 2024 17:41:41 -0500 Subject: [PATCH 3/6] more.... --- src/engine/engine.cpp | 8 +- src/engine/engine.h | 10 +- src/engine/platform/sound/snes/SPC_DSP.cpp | 15 +-- src/engine/song.cpp | 150 ++++++++------------- src/engine/song.h | 2 +- src/engine/wavOps.cpp | 83 +++++------- src/gui/gui.cpp | 120 +++++++---------- src/gui/gui.h | 14 +- 8 files changed, 163 insertions(+), 239 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 18189a29b..077c1a026 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -203,11 +203,9 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { } } -void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders, int& length) -{ - if (curSubSong!=NULL) - { - curSubSong->findLength(loopOrder, loopRow, fadeoutLen, rowsForFadeout, hasFFxx, orders, song.grooves, length, chans, song.jumpTreatment, song.ignoreJumpAtEnd); +void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders, int& length) { + if (curSubSong!=NULL) { + curSubSong->findLength(loopOrder,loopRow,fadeoutLen,rowsForFadeout,hasFFxx,orders,song.grooves,length,chans,song.jumpTreatment,song.ignoreJumpAtEnd); } } diff --git a/src/engine/engine.h b/src/engine/engine.h index 3e942e8a6..d2879d1a1 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -475,7 +475,7 @@ class DivEngine { int midiOutTimeRate; float midiVolExp; int softLockCount; - int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, curExportChan /*for per-channel export progress*/, nextSpeed, elapsedBars, elapsedBeats, curSpeed; + int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, curExportChan, nextSpeed, elapsedBars, elapsedBeats, curSpeed; size_t curSubSongIndex; size_t bufferPos; double divider; @@ -1015,19 +1015,19 @@ class DivEngine { // get how many loops is left void getLoopsLeft(int& loops); - //get how many loops in total export needs to do + // get how many loops in total export needs to do void getTotalLoops(int& loops); // get current position in song void getCurSongPos(int& row, int& order); - //get how many files export needs to create + // get how many files export needs to create void getTotalAudioFiles(int& files); - //get which file is processed right now (progress for e.g. per-channel export) + // get which file is processed right now (progress for e.g. per-channel export) void getCurFileIndex(int& file); - //get fadeout state + // get fadeout state bool getIsFadingOut(); // add instrument diff --git a/src/engine/platform/sound/snes/SPC_DSP.cpp b/src/engine/platform/sound/snes/SPC_DSP.cpp index 25cddd884..1fa7dae7d 100644 --- a/src/engine/platform/sound/snes/SPC_DSP.cpp +++ b/src/engine/platform/sound/snes/SPC_DSP.cpp @@ -135,19 +135,12 @@ static short const gauss [512] = 1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, }; -void SPC_DSP::setupInterpolation(bool interpolate) -{ - for(int i = 0; i < voice_count; i++) - { - m.voices[i].interpolate = interpolate; - } -} +void SPC_DSP::setupInterpolation(bool interpolate){for(int i=0;iinterpolate) - { + if (v->interpolate) { int offset = v->interp_pos >> 4 & 0xFF; short const* fwd = gauss + 255 - offset; short const* rev = gauss + offset; // mirror left half of gaussian @@ -163,9 +156,7 @@ inline int SPC_DSP::interpolate( voice_t const* v ) CLAMP16( out ); out &= ~1; return out; - } - else - { + } else { return v->buf [(v->interp_pos >> 12) + v->buf_pos]; //Furnace addition -- no interpolation } } diff --git a/src/engine/song.cpp b/src/engine/song.cpp index e4c516b6e..6a204c3db 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -102,9 +102,8 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int return false; } -double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong) -{ - double hl=1; //count for 1 row +double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong) { + double hl=1; //count for 1 row if (hl<=0.0) hl=4.0; double timeBase=timeBaseFromSong+1; double speedSum=0; @@ -115,25 +114,23 @@ double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int if (timeBase<1.0) timeBase=1.0; if (speedSum<1.0) speedSum=1.0; if (vD<1) vD=1; - //return (60.0 * hz / (timeBase * hl * speedSum)) * (double)vN / (double)vD; - return 1.0 / ((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD / 60.0); + return 1.0/((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD/60.0); } -void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders_vec, std::vector& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat) -{ - length = 0; - hasFFxx = false; - rowsForFadeout = 0; +void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders_vec, std::vector& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat) { + length=0; + hasFFxx=false; + rowsForFadeout=0; - float secondsPerThisRow = 0.0f; + float secondsPerThisRow=0.0f; - DivGroovePattern curSpeeds = speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes - short curVirtualTempoN = virtualTempoN; - short curVirtualTempoD = virtualTempoD; - float curHz = hz; - double curDivider = (double)timeBase; + DivGroovePattern curSpeeds=speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes + short curVirtualTempoN=virtualTempoN; + short curVirtualTempoD=virtualTempoD; + float curHz=hz; + double curDivider=(double)timeBase; - double curLen = 0.0; //how many seconds passed since the start of song + double curLen=0.0; //how many seconds passed since the start of song int nextOrder=-1; int nextRow=0; @@ -145,49 +142,39 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& if (firstPat>0) { memset(wsWalked,255,32*firstPat); } - for (int i=firstPat; ilastSuspectedLoopEnd) - { + if (i>lastSuspectedLoopEnd) { lastSuspectedLoopEnd=i; } - for (int j=nextRow; j>3))&8191]&(1<<(j&7))) - { + if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) { return; } - for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; - if (subPat[k]->data[j][4+(l<<1)]==0xff) - { - hasFFxx = true; + if (subPat[k]->data[j][4+(l<<1)]==0xff) { + hasFFxx=true; - //FFxx makes YOU SHALL NOT PASS!!! move - orders_vec.push_back(j + 1); //order len - length += j + 1; //add length of order to song length + // FFxx makes YOU SHALL NOT PASS!!! move + orders_vec.push_back(j+1); // order len + length+=j+1; // add length of order to song length return; } - switch(subPat[k]->data[j][4+(l<<1)]) //track speed/BMP/Hz/tempo changes - { - case 0x09: // select groove pattern/speed 1 - { + switch (subPat[k]->data[j][4+(l<<1)]) { + case 0x09: { // select groove pattern/speed 1 if (grooves.empty()) { if (effectVal>0) curSpeeds.val[0]=effectVal; } else { @@ -198,8 +185,7 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& } break; } - case 0x0f: // speed 1/speed 2 - { + case 0x0f: { // speed 1/speed 2 if (curSpeeds.len==2 && grooves.empty()) { if (effectVal>0) curSpeeds.val[1]=effectVal; } else { @@ -207,68 +193,47 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& } break; } - case 0xfd: // virtual tempo num - { + case 0xfd: { // virtual tempo num if (effectVal>0) curVirtualTempoN=effectVal; break; } - case 0xfe: // virtual tempo den - { + case 0xfe: { // virtual tempo den if (effectVal>0) curVirtualTempoD=effectVal; break; } - case 0xf0: // set Hz by tempo (set bpm) - { + case 0xf0: { // set Hz by tempo (set bpm) curDivider=(double)effectVal*2.0/5.0; if (curDivider<1) curDivider=1; - //cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; - //clockDrift=0; - //subticks=0; break; } - default: break; } - if (subPat[k]->data[j][4+(l<<1)]==0x0d) - { - if (jumpTreatment==2) - { - if ((idata[j][4+(l<<1)]==0x0d) { + if (jumpTreatment==2) { + if ((idata[j][4+(l<<1)]==0x0b) - { - if (nextOrder==-1 || jumpTreatment==0) - { + } else if (subPat[k]->data[j][4+(l<<1)]==0x0b) { + if (nextOrder==-1 || jumpTreatment==0) { nextOrder=effectVal; - if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) - { + if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) { nextRow=0; } changingOrder=true; @@ -277,32 +242,29 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& } } - if(i > loopOrder || (i == loopOrder && j > loopRow)) - { - if(curLen <= fadeoutLen && fadeoutLen > 0.0) //we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows - { - secondsPerThisRow = calcRowLenInSeconds(speeds, curHz, curVirtualTempoN, curVirtualTempoD, curDivider); - curLen += secondsPerThisRow; + if (i>loopOrder || (i==loopOrder && j>loopRow)) { + // we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows + if (curLen<=fadeoutLen && fadeoutLen>0.0) { + secondsPerThisRow=calcRowLenInSeconds(speeds,curHz,curVirtualTempoN,curVirtualTempoD,curDivider); + curLen+=secondsPerThisRow; rowsForFadeout++; } } wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7); - if (nextOrder!=-1) - { + if (nextOrder!=-1) { i=nextOrder-1; - orders_vec.push_back(j + 1); //order len - length += j + 1; //add length of order to song length - jumped = true; + orders_vec.push_back(j+1); // order len + length+=j+1; // add length of order to song length + jumped=true; nextOrder=-1; break; } } - if(!jumped) //if no jump occured we add full pattern length - { - orders_vec.push_back(patLen); //order len - length += patLen; //add length of order to song length + if (!jumped) { // if no jump occured we add full pattern length + orders_vec.push_back(patLen); // order len + length+=patLen; // add length of order to song length } } } diff --git a/src/engine/song.h b/src/engine/song.h index 75b86ec71..0202a7ed7 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -186,7 +186,7 @@ struct DivSubSong { bool walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0); /** - * find song length in rows (up to specified loop point). Also find length of every row + * find song length in rows (up to specified loop point). */ void findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders, std::vector& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0); diff --git a/src/engine/wavOps.cpp b/src/engine/wavOps.cpp index f1e8de315..f87b60a44 100644 --- a/src/engine/wavOps.cpp +++ b/src/engine/wavOps.cpp @@ -33,79 +33,70 @@ bool DivEngine::isExporting() { return exporting; } -void DivEngine::getLoopsLeft(int& loops) { - if(totalLoops < 0 || exportLoopCount == 0) - { - loops = 0; +void DivEngine::getLoopsLeft(int &loops) { + if (totalLoops<0 || exportLoopCount==0) { + loops=0; return; } - loops = exportLoopCount - 1 - totalLoops; + loops=exportLoopCount-1-totalLoops; } -void DivEngine::getTotalLoops(int& loops) { - loops = exportLoopCount - 1; +void DivEngine::getTotalLoops(int &loops) { + loops=exportLoopCount-1; } -void DivEngine::getCurSongPos(int& row, int& order) { - row = curRow; - order = curOrder; +void DivEngine::getCurSongPos(int &row, int &order) { + row=curRow; + order=curOrder; } -void DivEngine::getTotalAudioFiles(int& files) -{ - files = 0; +void DivEngine::getTotalAudioFiles(int &files) { + files=0; - switch(exportMode) - { - case DIV_EXPORT_MODE_ONE: - { - files = 1; + switch (exportMode) { + case DIV_EXPORT_MODE_ONE: { + files=1; break; } - case DIV_EXPORT_MODE_MANY_SYS: - { - files = 1; //there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file + case DIV_EXPORT_MODE_MANY_SYS: { + files=1; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file break; } - case DIV_EXPORT_MODE_MANY_CHAN: - { - for(int i = 0; i < chans; i++) - { - if (exportChannelMask[i]) files++; + case DIV_EXPORT_MODE_MANY_CHAN: { + for (int i=0; iwalkSong(loopOrder,loopRow,loopEnd); - e->findSongLength(loopOrder, loopRow, audioExportOptions.fadeOut, songFadeoutSectionLength, songHasSongEndCommand, songOrdersLengths, songLength); //for progress estimation + e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation - songLoopedSectionLength = songLength; - for(int i = 0; i < loopOrder; i++) - { - songLoopedSectionLength -= songOrdersLengths[i]; + songLoopedSectionLength=songLength; + for (int i=0; isaveAudio(path.c_str(),audioExportOptions); - totalFiles = 0; + totalFiles=0; e->getTotalAudioFiles(totalFiles); - int totalLoops = 0; + int totalLoops=0; - lengthOfOneFile = songLength; + lengthOfOneFile=songLength; - if(!songHasSongEndCommand) - { + if (!songHasSongEndCommand) { e->getTotalLoops(totalLoops); - lengthOfOneFile += songLoopedSectionLength * totalLoops; - lengthOfOneFile += songFadeoutSectionLength; //account for fadeout + lengthOfOneFile+=songLoopedSectionLength*totalLoops; + lengthOfOneFile+=songFadeoutSectionLength; // account for fadeout } - totalLength = lengthOfOneFile * totalFiles; + totalLength=lengthOfOneFile*totalFiles; - curProgress = 0.0f; + curProgress=0.0f; displayExporting=true; } @@ -5861,69 +5859,53 @@ bool FurnaceGUI::loop() { MEASURE_BEGIN(popup); centerNextWindow(_("Rendering..."),canvasW,canvasH); - if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { + if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) { + // WHAT the HELL?! WAKE_UP; - if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN) - { + if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) { ImGui::Text(_("Please wait...")); } - float* progressLambda = &curProgress; - int curPosInRows = 0; - int* curPosInRowsLambda = &curPosInRows; - int loopsLeft = 0; - int* loopsLeftLambda = &loopsLeft; - int totalLoops = 0; - int* totalLoopsLambda = &totalLoops; - int curFile = 0; - int* curFileLambda = &curFile; - if(e->isExporting()) - { - e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda, loopsLeftLambda, totalLoopsLambda]() - { - int curRow = 0; - int curOrder = 0; - e->getCurSongPos(curRow, curOrder); - *curFileLambda = 0; - e->getCurFileIndex(*curFileLambda); + float* progressLambda=&curProgress; + int curPosInRows=0; + int* curPosInRowsLambda=&curPosInRows; + int loopsLeft=0; + int* loopsLeftLambda=&loopsLeft; + int totalLoops=0; + int* totalLoopsLambda=&totalLoops; + int curFile=0; + int* curFileLambda=&curFile; + if (e->isExporting()) { + e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda, + loopsLeftLambda, totalLoopsLambda] () { + int curRow=0; int curOrder=0; + e->getCurSongPos(curRow, curOrder); *curFileLambda=0; + e->getCurFileIndex(*curFileLambda); + *curPosInRowsLambda=curRow; for (int i=0; igetLoopsLeft(*loopsLeftLambda); - e->getTotalLoops(*totalLoopsLambda); - - if((*totalLoopsLambda) != (*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song - { - *curPosInRowsLambda -= (songLength - songLoopedSectionLength); //a hack so progress bar does not jump? - } - if(e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh - { - *curPosInRowsLambda -= (songLength - songLoopedSectionLength); //a hack so progress bar does not jump? - } - } - - *progressLambda = (float)((*curPosInRowsLambda) + - ((*totalLoopsLambda) - (*loopsLeftLambda)) * songLength + - lengthOfOneFile * (*curFileLambda)) - / (float)totalLength; - }); + if (!songHasSongEndCommand) { + e->getLoopsLeft(*loopsLeftLambda); e->getTotalLoops(*totalLoopsLambda); if ((*totalLoopsLambda)!=(*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song + { + *curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump? + } + if (e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh + { + // LIVE WITH IT damn it + *curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump? + } + } + // this horrible indentation courtesy of `indent` + *progressLambda=(float) ((*curPosInRowsLambda) + ((*totalLoopsLambda)- (*loopsLeftLambda)) * songLength + lengthOfOneFile * (*curFileLambda)) / (float) totalLength;}); } - ImGui::Text(_("Row %d of %d"), curPosInRows + - ((totalLoops) - (loopsLeft)) * songLength, lengthOfOneFile); + ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile); - if(audioExportOptions.mode == DIV_EXPORT_MODE_MANY_CHAN) - { - ImGui::Text(_("Channel %d of %d"), curFile + 1, totalFiles); + if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) { + ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles); } - ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0), fmt::sprintf("%.2f%%", curProgress * 100.0f).c_str()); + ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str()); if (ImGui::Button(_("Abort"))) { if (e->haltAudioFile()) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 1cdfb4f3f..4246a1982 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1723,13 +1723,13 @@ class FurnaceGUI { char emptyLabel[32]; char emptyLabel2[32]; - std::vector songOrdersLengths; //lengths of all orders (for drawing song export progress) - int songLength; //length of all the song in rows - int songLoopedSectionLength; //length of looped part of the song - int songFadeoutSectionLength; //length of fading part of the song - bool songHasSongEndCommand; //song has "Song end" command (FFxx) - int lengthOfOneFile; //length of one rendering pass. song length times num of loops + fadeout - int totalLength; //total length of render (lengthOfOneFile times num of files for per-channel export) + std::vector songOrdersLengths; // lengths of all orders (for drawing song export progress) + int songLength; // length of all the song in rows + int songLoopedSectionLength; // length of looped part of the song + int songFadeoutSectionLength; // length of fading part of the song + bool songHasSongEndCommand; // song has "Song end" command (FFxx) + int lengthOfOneFile; // length of one rendering pass. song length times num of loops + fadeout + int totalLength; // total length of render (lengthOfOneFile times num of files for per-channel export) float curProgress; int totalFiles; From bf8d51ca83b0fd0b09e27a4548d723dc5fcd061c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 24 Aug 2024 18:42:23 -0500 Subject: [PATCH 4/6] implement operator mask effect --- doc/6-sample/README.md | 2 +- doc/7-systems/genesis.md | 65 --------------------------------- doc/7-systems/ym2151.md | 3 ++ doc/7-systems/ym2203.md | 3 ++ doc/7-systems/ym2608.md | 3 ++ doc/7-systems/ym2610.md | 3 ++ doc/7-systems/ym2610b.md | 3 ++ doc/7-systems/ym2612.md | 3 ++ src/engine/dispatch.h | 2 + src/engine/platform/arcade.cpp | 6 +++ src/engine/platform/genesis.cpp | 7 ++++ src/engine/platform/tx81z.cpp | 3 ++ src/engine/platform/ym2203.cpp | 7 ++++ src/engine/platform/ym2608.cpp | 7 ++++ src/engine/platform/ym2610.cpp | 7 ++++ src/engine/platform/ym2610b.cpp | 7 ++++ src/engine/playback.cpp | 4 +- src/engine/sysDef.cpp | 2 + 18 files changed, 70 insertions(+), 67 deletions(-) delete mode 100644 doc/7-systems/genesis.md diff --git a/doc/6-sample/README.md b/doc/6-sample/README.md index 5ebe571c1..efad91458 100644 --- a/doc/6-sample/README.md +++ b/doc/6-sample/README.md @@ -61,7 +61,7 @@ if you need to use more samples, you may change the sample bank using effect `EB due to limitations in some of those sound chips, some restrictions exist: - Amiga: maximum frequency is 31469Hz, but anything over 28867 will sound glitchy on hardware. sample lengths and loop will be set to an even number, and your sample can't be longer than 131070. -- NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample). +- NES: if on DPCM mode, only a limited selection of frequencies is available. - SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz. - QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767. - ADPCM-A: no looping supported. your samples will play at around 18.518KHz. diff --git a/doc/7-systems/genesis.md b/doc/7-systems/genesis.md deleted file mode 100644 index ed310ba13..000000000 --- a/doc/7-systems/genesis.md +++ /dev/null @@ -1,65 +0,0 @@ -# Sega Genesis/Mega Drive - -a video game console that showed itself as the first true rival to Nintendo's video game market near-monopoly in the US during the '80s. - -this console is powered by two sound chips: the [Yamaha YM2612](ym2612.md) and [a derivative of the SN76489](sms.md). - -## effects - -- `10xy`: **set LFO parameters.** - - `x` toggles the LFO. - - `y` sets its speed. -- `11xx`: **set feedback of channel.** -- `12xx`: **set operator 1 level.** -- `13xx`: **set operator 2 level.** -- `14xx`: **set operator 3 level.** -- `15xx`: **set operator 4 level.** -- `16xy`: **set multiplier of operator.** - - `x` is the operator (1-4). - - `y` is the new MULT value.. -- `17xx`: **enable PCM channel.** - - this only works on channel 6. - - _this effect is here for compatibility reasons!_ it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used). -- `18xx`: **toggle extended channel 3 mode.** - - `0` disables it and `1` enables it. - - only in extended channel 3 chip. -- `19xx`: **set attack of all operators.** -- `1Axx`: **set attack of operator 1.** -- `1Bxx`: **set attack of operator 2.** -- `1Cxx`: **set attack of operator 3.** -- `1Dxx`: **set attack of operator 4.** -- `20xy`: **set PSG noise mode.** - - `x` controls whether to inherit frequency from PSG channel 3. - - `0`: use one of 3 preset frequencies (`C`: A-2; `C#`: A-3; `D`: A-4). - - `1`: use frequency of PSG channel 3. - - `y` controls whether to select noise or thin pulse. - - `0`: thin pulse. - - `1`: noise. - - - -## system modes - -## extended channel 3 - -in ExtCh mode, channel 3 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds. - -all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2-op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone. - -## CSM - -CSM is short for "Composite Sinusoidal Modeling". CSM works by sending key-on and key-off commands to channel 3 at a specific frequency, controlled by the added "CSM Timer" channel. this can be used to create vocal formants (speech synthesis!) or other complex effects. - -CSM is beyond the scope of this documentation. for more information, see this [brief SSG-EG and CSM video tutorial](https://www.youtube.com/watch?v=IKOR0TUlnWU). - -## DualPCM - -[info here.](ym2612.md) - -## Sega CD - -this isn't a mode so much as a chip configuration. it adds the [Ricoh RF5C68](ricoh.md) found in the Sega CD add-on, providing 8 channels of PCM. - -## chip config - -see [YM2612](ym2612.md) and [SN76489](sms.md) for respective configuration. diff --git a/doc/7-systems/ym2151.md b/doc/7-systems/ym2151.md index 298401704..cf07811bc 100644 --- a/doc/7-systems/ym2151.md +++ b/doc/7-systems/ym2151.md @@ -68,6 +68,9 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se - `5Dxx`: **set D2R/SR of operator 2.** - `5Exx`: **set D2R/SR of operator 3.** - `5Fxx`: **set D2R/SR of operator 4.** +- `60xx`: **set operator mask.** + - `xx` goes from `0` to `F`. it is a bitfield. + - each bit corresponds to an operator. ## info diff --git a/doc/7-systems/ym2203.md b/doc/7-systems/ym2203.md index a89436498..83356c05a 100644 --- a/doc/7-systems/ym2203.md +++ b/doc/7-systems/ym2203.md @@ -99,6 +99,9 @@ several variants of this chip were released as well, with more features. - `5Dxx`: **set D2R/SR of operator 2.** - `5Exx`: **set D2R/SR of operator 3.** - `5Fxx`: **set D2R/SR of operator 4.** +- `60xx`: **set operator mask.** + - `xx` goes from `0` to `F`. it is a bitfield. + - each bit corresponds to an operator. ## extended channel 3 diff --git a/doc/7-systems/ym2608.md b/doc/7-systems/ym2608.md index 1bbd272cb..bca23c1d9 100644 --- a/doc/7-systems/ym2608.md +++ b/doc/7-systems/ym2608.md @@ -99,6 +99,9 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built- - `5Dxx`: **set D2R/SR of operator 2.** - `5Exx`: **set D2R/SR of operator 3.** - `5Fxx`: **set D2R/SR of operator 4.** +- `60xx`: **set operator mask.** + - `xx` goes from `0` to `F`. it is a bitfield. + - each bit corresponds to an operator. ## extended channel 3 diff --git a/doc/7-systems/ym2610.md b/doc/7-systems/ym2610.md index 15f479933..449bc5a35 100644 --- a/doc/7-systems/ym2610.md +++ b/doc/7-systems/ym2610.md @@ -97,6 +97,9 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and [2 differen - `5Dxx`: **set D2R/SR of operator 2.** - `5Exx`: **set D2R/SR of operator 3.** - `5Fxx`: **set D2R/SR of operator 4.** +- `60xx`: **set operator mask.** + - `xx` goes from `0` to `F`. it is a bitfield. + - each bit corresponds to an operator. ## extended channel 2 diff --git a/doc/7-systems/ym2610b.md b/doc/7-systems/ym2610b.md index d91ed2221..df9f036c7 100644 --- a/doc/7-systems/ym2610b.md +++ b/doc/7-systems/ym2610b.md @@ -96,6 +96,9 @@ it is backward compatible with the original chip. - `5Dxx`: **set D2R/SR of operator 2.** - `5Exx`: **set D2R/SR of operator 3.** - `5Fxx`: **set D2R/SR of operator 4.** +- `60xx`: **set operator mask.** + - `xx` goes from `0` to `F`. it is a bitfield. + - each bit corresponds to an operator. ## extended channel 3 diff --git a/doc/7-systems/ym2612.md b/doc/7-systems/ym2612.md index 852dfa9ee..989a6d41a 100644 --- a/doc/7-systems/ym2612.md +++ b/doc/7-systems/ym2612.md @@ -82,6 +82,9 @@ thanks to the Z80 sound CPU, DualPCM can play two samples at once! this mode spl - `5Dxx`: **set D2R/SR of operator 2.** - `5Exx`: **set D2R/SR of operator 3.** - `5Fxx`: **set D2R/SR of operator 4.** +- `60xx`: **set operator mask.** + - `xx` goes from `0` to `F`. it is a bitfield. + - each bit corresponds to an operator. ## info diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 517adc662..f207ae359 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -266,6 +266,8 @@ enum DivDispatchCmds { DIV_CMD_FDS_MOD_AUTO, + DIV_CMD_FM_OPMASK, // (mask) + DIV_CMD_MAX }; diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 914453271..74cfcc6e7 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -761,6 +761,12 @@ int DivPlatformArcade::dispatch(DivCommand c) { immWrite(0x19,0x80|pmDepth); break; } + case DIV_CMD_FM_OPMASK: + chan[c.chan].opMask=c.value&15; + if (chan[c.chan].active) { + chan[c.chan].opMaskChanged=true; + } + break; case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index e7533dec7..1b8707896 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -1463,6 +1463,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_OPMASK: + if (c.chan>=psgChanOffs) break; + chan[c.chan].opMask=c.value&15; + if (chan[c.chan].active) { + chan[c.chan].opMaskChanged=true; + } + break; case DIV_CMD_FM_HARD_RESET: if (c.chan>=6) break; chan[c.chan].hardReset=c.value; diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 3fee5e04e..600ae724e 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -858,6 +858,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { immWrite(0x17,0x80|pmDepth); break; } + case DIV_CMD_FM_OPMASK: + // TODO: if OPZ supports op mask + break; case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 5a92c4dc9..2a19fea0f 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -1004,6 +1004,13 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_OPMASK: + if (c.chan>=psgChanOffs) break; + chan[c.chan].opMask=c.value&15; + if (chan[c.chan].active) { + chan[c.chan].opMaskChanged=true; + } + break; case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 76c67272d..046e20c20 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -1537,6 +1537,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_OPMASK: + if (c.chan>=psgChanOffs) break; + chan[c.chan].opMask=c.value&15; + if (chan[c.chan].active) { + chan[c.chan].opMaskChanged=true; + } + break; case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index adda4bc7e..038cbc807 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -1507,6 +1507,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_OPMASK: + if (c.chan>=psgChanOffs) break; + chan[c.chan].opMask=c.value&15; + if (chan[c.chan].active) { + chan[c.chan].opMaskChanged=true; + } + break; case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index e4d0dca85..8b12ca862 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -1576,6 +1576,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_OPMASK: + if (c.chan>=psgChanOffs) break; + chan[c.chan].opMask=c.value&15; + if (chan[c.chan].active) { + chan[c.chan].opMaskChanged=true; + } + break; case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 296183f8c..c886c9e87 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -264,7 +264,9 @@ const char* cmdName[]={ "BIFURCATOR_STATE_LOAD", "BIFURCATOR_PARAMETER", - "FDS_MOD_AUTO" + "FDS_MOD_AUTO", + + "FM_OPMASK" }; static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index a8e50461e..3d2850828 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -504,6 +504,7 @@ void DivEngine::registerSystems() { {0x5d, {DIV_CMD_FM_D2R, _("5Dxx: Set decay 2 of operator 2 (0 to 1F)"), constVal<1>, effectValAnd<31>}}, {0x5e, {DIV_CMD_FM_D2R, _("5Exx: Set decay 2 of operator 3 (0 to 1F)"), constVal<2>, effectValAnd<31>}}, {0x5f, {DIV_CMD_FM_D2R, _("5Fxx: Set decay 2 of operator 4 (0 to 1F)"), constVal<3>, effectValAnd<31>}}, + {0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}}, }; EffectHandlerMap fmOPMPostEffectHandlerMap(fmOPNPostEffectHandlerMap); @@ -514,6 +515,7 @@ void DivEngine::registerSystems() { {0x1e, {DIV_CMD_FM_AM_DEPTH, _("1Exx: Set AM depth (0 to 7F)"), effectValAnd<127>}}, {0x1f, {DIV_CMD_FM_PM_DEPTH, _("1Fxx: Set PM depth (0 to 7F)"), effectValAnd<127>}}, {0x55, {DIV_CMD_FM_SSG, _("55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"), effectOpVal<4>, effectValAnd<3>}}, + {0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}}, }); EffectHandlerMap fmOPZPostEffectHandlerMap(fmOPMPostEffectHandlerMap); From cbbf1a3cb4d31cae0589cc1be045a9313c7e4ded Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 25 Aug 2024 02:57:16 -0500 Subject: [PATCH 5/6] add SID3 to the spec --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index d8e12b7bd..98a98b07a 100644 --- a/papers/format.md +++ b/papers/format.md @@ -260,6 +260,7 @@ size | description | - 0xe4: µPD1771C - 1 channel (UNAVAILABLE) | - 0xf0: SID2 - 3 channels | - 0xf1: 5E01 - 5 channels + | - 0xf5: SID3 - 7 channels | - 0xfc: Pong - 1 channel | - 0xfd: Dummy System - 8 channels | - 0xfe: reserved for development From 68b45338f066535e4bd5d4a9af6ed9180907fbba Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 25 Aug 2024 03:00:00 -0500 Subject: [PATCH 6/6] GUI: tryWith fix from #2108 --- src/gui/sampleEdit.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index aefe38cfb..4f858af19 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -261,10 +261,10 @@ void FurnaceGUI::drawSampleEdit() { String alignHint=fmt::sprintf(_("NES: loop start must be a multiple of 512 (try with %d)"),tryWith); SAMPLE_WARN(warnLoopStart,alignHint); } - if ((sample->loopEnd)&127) { - // +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC - int tryWith=(sample->loopEnd + 1)&(~127); + if ((sample->loopEnd-8)&127) { + int tryWith=(sample->loopEnd-8)&(~127); if (tryWith>(int)sample->samples) tryWith-=128; + tryWith+=8; // +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith); SAMPLE_WARN(warnLoopEnd,alignHint); }