diff --git a/papers/format.md b/papers/format.md index 998a4f82..9b43b65c 100644 --- a/papers/format.md +++ b/papers/format.md @@ -25,6 +25,9 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 42: Furnace 0.5.5pre1 +- 41: Furnace 0.5.4 +- 40: Furnace 0.5.3 - 39: Furnace 0.5.2 - 38: Furnace 0.5.2pre2 - 37: Furnace 0.5.2pre1 @@ -148,7 +151,9 @@ size | description 1 | limit slides (>=36) or reserved 1 | linear pitch (>=36) or reserved 1 | loop modality (>=36) or reserved - 17 | reserved + 1 | proper noise layout (>=42) or reserved + 1 | wave duty is volume (>=42) or reserved + 15 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 3a9d0807..76b87d52 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2077,6 +2077,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.linearPitch=true; ds.loopModality=0; } + if (ds.version<43) { + ds.properNoiseLayout=false; + ds.waveDutyIsVol=false; + } reader.readS(); // reserved int infoSeek=reader.readI(); @@ -2148,7 +2152,17 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.limitSlides=reader.readC(); ds.linearPitch=reader.readC(); ds.loopModality=reader.readC(); - for (int i=0; i<17; i++) reader.readC(); + if (ds.version>=43) { + ds.properNoiseLayout=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=43) { + ds.waveDutyIsVol=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<15; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -2522,7 +2536,9 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.limitSlides); w->writeC(song.linearPitch); w->writeC(song.loopModality); - for (int i=0; i<17; i++) { + w->writeC(song.properNoiseLayout); + w->writeC(song.waveDutyIsVol); + for (int i=0; i<15; i++) { w->writeC(0); } diff --git a/src/engine/engine.h b/src/engine/engine.h index 60efe630..6302ca7c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -11,8 +11,8 @@ #include #include -#define DIV_VERSION "0.5.4" -#define DIV_ENGINE_VERSION 42 +#define DIV_VERSION "0.5.5pre1" +#define DIV_ENGINE_VERSION 43 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 9809aa23..222191f6 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -144,6 +144,10 @@ void DivPlatformGB::tick() { DivInstrument* ins=parent->getIns(chan[i].ins); if (i!=2) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + } else { + if (parent->song.waveDutyIsVol) { + rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty&3)<<2]); + } } } if (chan[i].std.hadWave) { @@ -164,7 +168,10 @@ void DivPlatformGB::tick() { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { DivInstrument* ins=parent->getIns(chan[i].ins); if (i==3) { // noise - chan[i].freq=noiseTable[chan[i].baseFreq]; + int ntPos=chan[i].baseFreq; + if (ntPos<0) ntPos=0; + if (ntPos>255) ntPos=255; + chan[i].freq=noiseTable[ntPos]; } else { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); if (chan[i].freq>2047) chan[i].freq=2047; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index d604ded5..e87b9ce0 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -73,7 +73,7 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) } } -static unsigned char noiseTable[256]={ +static unsigned char noiseTable[253]={ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, @@ -155,7 +155,10 @@ void DivPlatformNES::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==3) { // noise - chan[i].freq=noiseTable[chan[i].baseFreq]; + int ntPos=chan[i].baseFreq; + if (ntPos<0) ntPos=0; + if (ntPos>252) ntPos=252; + chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]); } else { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); if (chan[i].freq>2047) chan[i].freq=2047; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 5543ea58..c0c1f00e 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -129,17 +129,23 @@ void DivPlatformPCE::tick() { if (chan[i].std.arpMode) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); // noise - chWrite(i,0x07,chan[i].noise?(0x80|noiseFreq[(chan[i].std.arp)%12]):0); + int noiseSeek=chan[i].std.arp; + if (noiseSeek<0) noiseSeek=0; + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } else { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - chWrite(i,0x07,chan[i].noise?(0x80|noiseFreq[(chan[i].note+chan[i].std.arp)%12]):0); + int noiseSeek=chan[i].note+chan[i].std.arp; + if (noiseSeek<0) noiseSeek=0; + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } } chan[i].freqChanged=true; } else { if (chan[i].std.arpMode && chan[i].std.finishedArp) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chWrite(i,0x07,chan[i].noise?(0x80|noiseFreq[chan[i].note%12]):0); + int noiseSeek=chan[i].note; + if (noiseSeek<0) noiseSeek=0; + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); chan[i].freqChanged=true; } } @@ -248,7 +254,9 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; - chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|noiseFreq[chan[c.chan].note%12]):0); + int noiseSeek=chan[c.chan].note; + if (noiseSeek<0) noiseSeek=0; + chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } chan[c.chan].active=true; chan[c.chan].keyOn=true; diff --git a/src/engine/song.h b/src/engine/song.h index 555a075d..cd9c460d 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -215,6 +215,8 @@ struct DivSong { // 1: fake reset on loop // 2: don't do anything on loop unsigned char loopModality; + bool properNoiseLayout; + bool waveDutyIsVol; DivOrders orders; std::vector ins; @@ -263,7 +265,9 @@ struct DivSong { tuning(440.0f), limitSlides(false), linearPitch(true), - loopModality(0) { + loopModality(0), + properNoiseLayout(false), + waveDutyIsVol(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e0e5710b..369f4f69 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3845,17 +3845,40 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { ImGui::TextWrapped("these flags are stored in the song when saving in .fur format, and are automatically enabled when saving in .dmf format."); ImGui::Checkbox("Limit slide range",&e->song.limitSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); + } ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); + } + ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); + } + ImGui::Checkbox("Game Boy instrument duty is wave volume",&e->song.waveDutyIsVol); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if enabled, an instrument with duty macro in the wave channel will be mapped to wavetable volume."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { e->song.loopModality=0; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to reset channels on loop. may trigger a voltage click on every loop!"); + } if (ImGui::RadioButton("Soft reset channels",e->song.loopModality==1)) { e->song.loopModality=1; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to turn channels off on loop."); + } if (ImGui::RadioButton("Do nothing",e->song.loopModality==2)) { e->song.loopModality=2; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to not reset channels on loop."); + } } ImGui::End(); }