From 807848cfee501f2d50cdd386c199b6c4b1e4f181 Mon Sep 17 00:00:00 2001 From: ZeroByteOrg Date: Wed, 29 Jun 2022 16:59:47 -0500 Subject: [PATCH 001/194] Fix LFO disable/enable behavior for YM2151. --- src/engine/platform/arcade.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 1be61fa23..47cc1b600 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -172,7 +172,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si w.addrOrVal=true; } } - + OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); @@ -182,13 +182,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si for (int i=0; i<8; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]; } - + if (o[0]<-32768) o[0]=-32768; if (o[0]>32767) o[0]=32767; if (o[1]<-32768) o[1]=-32768; if (o[1]>32767) o[1]=32767; - + bufL[h]=o[0]; bufR[h]=o[1]; } @@ -211,7 +211,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz delay=1; } } - + fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { @@ -225,7 +225,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz os[1]=out_ymfm.data[1]; if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; - + bufL[h]=os[0]; bufR[h]=os[1]; } @@ -616,6 +616,12 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { + if(c.value==0) { + rWrite(0x01,0x02); + } + else { + rWrite(0x01,0x00); + } rWrite(0x18,c.value); break; } @@ -938,6 +944,8 @@ void DivPlatformArcade::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x01,0x02); // LFO Off + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); From d3cd7bbb8151bb63007f061a79ca069eaea290fa Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 3 Jul 2022 01:42:47 +0700 Subject: [PATCH 002/194] Add generic PCM DAC system For use with NGP DAC and some arcade system combos --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 6 +- src/engine/platform/pcmdac.cpp | 370 +++++++++++++++++++++++++++++++ src/engine/platform/pcmdac.h | 99 +++++++++ src/gui/guiConst.cpp | 1 + src/gui/sysConf.cpp | 8 +- 6 files changed, 481 insertions(+), 4 deletions(-) create mode 100644 src/engine/platform/pcmdac.cpp create mode 100644 src/engine/platform/pcmdac.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5312692a6..dbf3ada7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,6 +448,7 @@ src/engine/platform/scc.cpp src/engine/platform/ymz280b.cpp src/engine/platform/namcowsg.cpp src/engine/platform/rf5c68.cpp +src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp ) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 227f60679..8d8db5c5a 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -54,6 +54,7 @@ #include "platform/su.h" #include "platform/swan.h" #include "platform/lynx.h" +#include "platform/zxbeeper.h" #include "platform/bubsyswsg.h" #include "platform/n163.h" #include "platform/pet.h" @@ -64,9 +65,9 @@ #include "platform/scc.h" #include "platform/ymz280b.h" #include "platform/rf5c68.h" +#include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" -#include "platform/zxbeeper.h" #include "song.h" void DivDispatchContainer::setRates(double gotRate) { @@ -395,6 +396,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformNamcoWSG; ((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30); break; + case DIV_SYSTEM_PCM_DAC: + dispatch=new DivPlatformPCMDAC; + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp new file mode 100644 index 000000000..314ba7dd1 --- /dev/null +++ b/src/engine/platform/pcmdac.cpp @@ -0,0 +1,370 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define _USE_MATH_DEFINES +#include "pcmdac.h" +#include "../engine.h" +#include + +// to ease the driver, freqency register is a 8.16 counter relative to output sample rate +#define CHIP_FREQBASE 65536 + +void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t len) { + const int depthScale=(15-outDepth); + int output=0; + for (size_t h=start; hdata[oscBuf->needle++]=0; + continue; + } + if (chan.useWave || (chan.sample>=0 && chan.samplesong.sampleLen)) { + chan.audPos+=chan.freq>>16; + chan.audSub+=(chan.freq&0xffff); + if (chan.audSub>=0x10000) { + chan.audSub-=0x10000; + chan.audPos+=1; + } + if (chan.useWave) { + if (chan.audPos>=(unsigned int)(chan.audLen<<1)) { + chan.audPos=0; + } + output=(chan.ws.output[chan.audPos]^0x80)<<8; + } else { + DivSample* s=parent->getSample(chan.sample); + if (s->samples>0) { + if (chan.audPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + chan.audPos=s->loopStart; + } else { + chan.sample=-1; + } + } + if (chan.audPossamples) { + output=s->data16[chan.audPos]; + } + } else { + chan.sample=-1; + } + } + } + output=output*chan.vol*chan.envVol/16384; + oscBuf->data[oscBuf->needle++]=output; + if (outStereo) { + bufL[h]=((output*chan.panL)>>(depthScale+8))<>(depthScale+8))<>depthScale)<getIns(chan.ins,DIV_INS_AMIGA); + double off=1.0; + if (!chan.useWave && chan.sample>=0 && chan.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan.sample); + off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + } + chan.freq=off*parent->calcFreq(chan.baseFreq,chan.pitch,false,2,chan.pitch2,chipClock,CHIP_FREQBASE); + if (chan.freq>16777215) chan.freq=16777215; + if (chan.keyOn) { + if (!chan.std.vol.had) { + chan.envVol=64; + } + chan.keyOn=false; + } + if (chan.keyOff) { + chan.keyOff=false; + } + chan.freqChanged=false; + } +} + +int DivPlatformPCMDAC::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA); + if (ins->amiga.useWave) { + chan.useWave=true; + chan.audLen=(ins->amiga.waveLen+1)>>1; + if (chan.insChanged) { + if (chan.wave<0) { + chan.wave=0; + chan.ws.setWidth(chan.audLen<<1); + chan.ws.changeWave1(chan.wave); + } + } + } else { + chan.sample=ins->amiga.getSample(c.value); + chan.useWave=false; + } + if (c.value!=DIV_NOTE_NULL) { + chan.baseFreq=round(NOTE_FREQUENCY(c.value)); + } + if (chan.useWave || chan.sample<0 || chan.sample>=parent->song.sampleLen) { + chan.sample=-1; + } + if (chan.setPos) { + chan.setPos=false; + } else { + chan.audPos=0; + } + chan.audSub=0; + if (c.value!=DIV_NOTE_NULL) { + chan.freqChanged=true; + chan.note=c.value; + } + chan.active=true; + chan.keyOn=true; + chan.macroInit(ins); + if (!parent->song.brokenOutVol && !chan.std.vol.will) { + chan.envVol=64; + } + if (chan.useWave) { + chan.ws.init(ins,chan.audLen<<1,255,chan.insChanged); + } + chan.insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan.sample=-1; + chan.active=false; + chan.keyOff=true; + chan.macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan.std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan.ins!=c.value || c.value2==1) { + chan.ins=c.value; + chan.insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan.vol!=c.value) { + chan.vol=c.value; + if (!chan.std.vol.has) { + chan.envVol=64; + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan.vol; + break; + case DIV_CMD_PANNING: + chan.panL=c.value; + chan.panR=c.value2; + break; + case DIV_CMD_PITCH: + chan.pitch=c.value; + chan.freqChanged=true; + break; + case DIV_CMD_WAVE: + if (!chan.useWave) break; + chan.wave=c.value; + chan.keyOn=true; + chan.ws.changeWave1(chan.wave); + break; + case DIV_CMD_NOTE_PORTA: { + DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA); + chan.sample=ins->amiga.getSample(c.value2); + int destFreq=round(NOTE_FREQUENCY(c.value2)); + bool return2=false; + if (destFreq>chan.baseFreq) { + chan.baseFreq+=c.value; + if (chan.baseFreq>=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } else { + chan.baseFreq-=c.value; + if (chan.baseFreq<=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } + chan.freqChanged=true; + if (return2) { + chan.inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan.baseFreq=round(NOTE_FREQUENCY(c.value+((chan.std.arp.will && !chan.std.arp.mode)?(chan.std.arp.val):(0)))); + chan.freqChanged=true; + chan.note=c.value; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan.active && c.value2) { + if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_AMIGA)); + } + chan.inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + if (chan.useWave) break; + chan.audPos=c.value; + chan.setPos=true; + break; + case DIV_CMD_GET_VOLMAX: + return 255; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPCMDAC::muteChannel(int ch, bool mute) { + isMuted=mute; +} + +void DivPlatformPCMDAC::forceIns() { + chan.insChanged=true; + chan.freqChanged=true; + chan.audPos=0; + chan.sample=-1; +} + +void* DivPlatformPCMDAC::getChanState(int ch) { + return &chan; +} + +DivDispatchOscBuffer* DivPlatformPCMDAC::getOscBuffer(int ch) { + return oscBuf; +} + +void DivPlatformPCMDAC::reset() { + chan=DivPlatformPCMDAC::Channel(); + chan.std.setEngine(parent); + chan.ws.setEngine(parent); + chan.ws.init(NULL,32,255); +} + +bool DivPlatformPCMDAC::isStereo() { + return true; +} + +DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) { + return &chan.std; +} + +void DivPlatformPCMDAC::notifyInsChange(int ins) { + if (chan.ins==ins) { + chan.insChanged=true; + } +} + +void DivPlatformPCMDAC::notifyWaveChange(int wave) { + if (chan.useWave && chan.wave==wave) { + chan.ws.changeWave1(wave); + } +} + +void DivPlatformPCMDAC::notifyInsDeletion(void* ins) { + chan.std.notifyInsDeletion((DivInstrument*)ins); +} + +void DivPlatformPCMDAC::setFlags(unsigned int flags) { + // default to 44100Hz 16-bit stereo + if (!flags) flags=0x1f0000|44099; + rate=(flags&0xffff)+1; + // rate can't be too low or the resampler will break + if (rate<1000) rate=1000; + chipClock=rate; + outDepth=(flags>>16)&0xf; + outStereo=(flags>>20)&1; +} + +int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + oscBuf=new DivDispatchOscBuffer; + isMuted=false; + setFlags(flags); + reset(); + return 1; +} + +void DivPlatformPCMDAC::quit() { + delete oscBuf; +} diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h new file mode 100644 index 000000000..c372219c2 --- /dev/null +++ b/src/engine/platform/pcmdac.h @@ -0,0 +1,99 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _PCM_DAC_H +#define _PCM_DAC_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "../waveSynth.h" + +class DivPlatformPCMDAC: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2; + unsigned int audLoc; + unsigned short audLen; + unsigned int audPos; + int audSub; + int sample, wave, ins; + int note; + int panL, panR; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos; + int vol, envVol; + DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + audLoc(0), + audLen(0), + audPos(0), + audSub(0), + sample(-1), + wave(-1), + ins(-1), + panL(255), + panR(255), + note(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + useWave(false), + setPos(false), + vol(255), + envVol(64) {} + }; + Channel chan; + DivDispatchOscBuffer* oscBuf; + bool isMuted; + int outDepth; + bool outStereo; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + DivMacroInt* getChanMacroInt(int ch); + void setFlags(unsigned int flags); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); +}; + +#endif diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 80f9b5d85..566a12e12 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -894,6 +894,7 @@ const int availableSystems[]={ DIV_SYSTEM_MSM6258, DIV_SYSTEM_MSM6295, DIV_SYSTEM_RF5C68, + DIV_SYSTEM_PCM_DAC, 0 // don't remove this last one! }; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b34dd0b28..89446e3be 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -609,16 +609,18 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool break; } case DIV_SYSTEM_PCM_DAC: { + // default to 44100Hz 16-bit stereo + if (!flags) copyOfFlags=flags=0x1f0000|44099; int sampRate=(flags&65535)+1; int bitDepth=((flags>>16)&15)+1; bool stereo=(flags>>20)&1; ImGui::Text("Output rate:"); - if (CWSliderInt("##SampRate",&sampRate,1,65536)) { - if (sampRate<1) sampRate=1; + if (CWSliderInt("##SampRate",&sampRate,1000,65536)) { + if (sampRate<1000) sampRate=1000; if (sampRate>65536) sampRate=65536; copyOfFlags=(flags&(~65535))|(sampRate-1); } rightClickable - ImGui::Text("Output depth:"); + ImGui::Text("Output bit depth:"); if (CWSliderInt("##BitDepth",&bitDepth,1,16)) { if (bitDepth<1) bitDepth=1; if (bitDepth>16) bitDepth=16; From f8425b817f97eed3d942bd0c1c63726cc2456329 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 3 Jul 2022 20:11:04 +0700 Subject: [PATCH 003/194] Fix GCC errors --- src/engine/platform/pcmdac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h index c372219c2..60d2a6fc0 100644 --- a/src/engine/platform/pcmdac.h +++ b/src/engine/platform/pcmdac.h @@ -55,9 +55,9 @@ class DivPlatformPCMDAC: public DivDispatch { sample(-1), wave(-1), ins(-1), + note(0), panL(255), panR(255), - note(0), active(false), insChanged(true), freqChanged(false), From 8104b717cdebdc1305e8d56e18d36d1b2a7201aa Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Jul 2022 13:48:37 -0500 Subject: [PATCH 004/194] i knew it --- .../bass/{acoustic bass.dmp => Acoustic Bass 2.dmp} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename instruments/FM/bass/{acoustic bass.dmp => Acoustic Bass 2.dmp} (100%) diff --git a/instruments/FM/bass/acoustic bass.dmp b/instruments/FM/bass/Acoustic Bass 2.dmp similarity index 100% rename from instruments/FM/bass/acoustic bass.dmp rename to instruments/FM/bass/Acoustic Bass 2.dmp From 15ab8cc49bbf120dc821a2046cde39c9d7cf497d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 12 Jul 2022 18:45:54 -0500 Subject: [PATCH 005/194] YM2612: fix a CSM issue with key off --- src/engine/platform/genesisext.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 1c44bcfb9..bfe771a6d 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -418,6 +418,16 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } } if (writeSomething) { + if (chan[7].active) { // CSM + writeMask^=0xf0; + } + /*printf( + "Mask: %c %c %c %c\n", + (writeMask&0x10)?'1':'-', + (writeMask&0x20)?'2':'-', + (writeMask&0x40)?'3':'-', + (writeMask&0x80)?'4':'-' + );*/ immWrite(0x28,writeMask); } } @@ -478,6 +488,13 @@ void DivPlatformGenesisExt::tick(bool sysTick) { if (chan[7].active) { // CSM writeMask^=0xf0; } + /*printf( + "Mask: %c %c %c %c\n", + (writeMask&0x10)?'1':'-', + (writeMask&0x20)?'2':'-', + (writeMask&0x40)?'3':'-', + (writeMask&0x80)?'4':'-' + );*/ immWrite(0x28,writeMask); } From 2f98da56756ccb82b940bc69f4591eed17de1e7d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 12 Jul 2022 19:15:10 -0500 Subject: [PATCH 006/194] GUI: sample editor icon improvements --- src/gui/sampleEdit.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 7264baed5..b684a0c77 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -335,7 +335,7 @@ void FurnaceGUI::drawSampleEdit() { if (silenceSize<0) silenceSize=0; if (silenceSize>16777215) silenceSize=16777215; } - if (ImGui::Button("Resize")) { + if (ImGui::Button("Go")) { int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; sample->prepareUndo(true); e->lockEngine([this,sample,pos]() { @@ -512,14 +512,14 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { + if (ImGui::Button(ICON_FA_PLAY "##PreviewSample")) { e->previewSample(curSample); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Preview sample"); } ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { + if (ImGui::Button(ICON_FA_STOP "##StopSample")) { e->stopSamplePreview(); } if (ImGui::IsItemHovered()) { @@ -991,7 +991,7 @@ void FurnaceGUI::drawSampleEdit() { if (silenceSize<0) silenceSize=0; if (silenceSize>16777215) silenceSize=16777215; } - if (ImGui::Button("Resize")) { + if (ImGui::Button("Go")) { int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; sample->prepareUndo(true); e->lockEngine([this,sample,pos]() { From 936a95c1123780f4eff04058b20dd02d579c86b2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 13 Jul 2022 16:47:09 -0500 Subject: [PATCH 007/194] fix build on Arch not my fault that PipeWire is shipped in a broken state --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5312692a6..8f0c46698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,6 +221,11 @@ if (USE_SDL2) set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE) set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE) endif() + # https://github.com/libsdl-org/SDL/issues/5535 + # disable PipeWire support due to an unfixable bug: + # Looks like their headers have a C90 violation... I imagine they're probably on C99 so not the craziest bug in the world. Definitely file this at the PipeWire repository as well so they know this is out there. + set(SDL_PIPEWIRE OFF CACHE BOOL "Use Pipewire audio" FORCE) + # https://github.com/libsdl-org/SDL/issues/1481 # On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote: # If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses. From 5f92a6ffa6fe41f1b229c00a3a46f5fcbc56f270 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 14 Jul 2022 00:14:33 -0500 Subject: [PATCH 008/194] possibly fix major issue with NFD --- extern/nfd-modified/src/nfd_win.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/nfd-modified/src/nfd_win.cpp b/extern/nfd-modified/src/nfd_win.cpp index 9273bb1e7..b4fa5a5fa 100644 --- a/extern/nfd-modified/src/nfd_win.cpp +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -368,7 +368,7 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath ) { - if ( !defaultPath || strlen(defaultPath) == 0 ) + if ( !defaultPath || strlen(defaultPath) == 0 || strcmp(defaultPath,"\\")==0 ) return NFD_OKAY; wchar_t *defaultPathW = {0}; From 28a2db7a57ac3c8a3586144c5697d2eaeb4ee820 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 14 Jul 2022 01:59:55 -0500 Subject: [PATCH 009/194] GUI: system file picker error feedback --- src/gui/fileDialog.cpp | 20 +++++++++++++++----- src/gui/fileDialog.h | 3 +++ src/gui/gui.cpp | 7 +++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index f64f72cbb..bd54bf0fe 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -25,9 +25,10 @@ struct NFDState { }; // TODO: filter -void _nfdThread(const NFDState state, std::atomic* ok, String* result) { +void _nfdThread(const NFDState state, std::atomic* ok, String* result, bool* errorOutput) { nfdchar_t* out=NULL; nfdresult_t ret=NFD_CANCEL; + errorOutput=false; if (state.isSave) { ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback); @@ -49,6 +50,7 @@ void _nfdThread(const NFDState state, std::atomic* ok, String* result) { case NFD_ERROR: (*result)=""; logE("NFD error! %s\n",NFD_GetError()); + (*errorOutput)=true; break; default: logE("NFD unknown return code %d!\n",ret); @@ -68,14 +70,16 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult); + _nfdThread(NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult,&hasError); #else - dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult); + dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult,&hasError); #endif #else dialogO=new pfd::open_file(header,path,filter); + hasError=!pfd::settings::available(); #endif } else { + hasError=false; ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,0,clickCallback); } @@ -92,14 +96,16 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult); + _nfdThread(NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult,&hasError); #else - dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult); + dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult,&hasError); #endif #else dialogS=new pfd::save_file(header,path,filter); + hasError=!pfd::settings::available(); #endif } else { + hasError=false; ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); } @@ -193,6 +199,10 @@ bool FurnaceGUIFileDialog::isOpen() { return opened; } +bool FurnaceGUIFileDialog::isError() { + return hasError; +} + String FurnaceGUIFileDialog::getPath() { if (sysDialog) { if (curPath.size()>1) { diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 6724eb951..f3edd2884 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -28,6 +28,7 @@ class FurnaceGUIFileDialog { bool sysDialog; bool opened; bool saving; + bool hasError; String curPath; String fileName; #ifdef USE_NFD @@ -46,12 +47,14 @@ class FurnaceGUIFileDialog { void close(); bool render(const ImVec2& min, const ImVec2& max); bool isOpen(); + bool isError(); String getPath(); String getFileName(); explicit FurnaceGUIFileDialog(bool system): sysDialog(system), opened(false), saving(false), + hasError(false), dialogO(NULL), dialogS(NULL) {} }; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 642572d38..c6a58c083 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3221,6 +3221,13 @@ bool FurnaceGUI::loop() { workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; } + if (fileDialog->isError()) { +#if defined(_WIN32) || defined(__APPLE__) + showError("there was an error in the file dialog! you may want to report this issue to:\nhttps://github.com/tildearrow/furnace/issues\ncheck the Log Viewer (window > log viewer) for more information.\n\nfor now please disable the system file picker in Settings > General."); +#else + showError("Zenity/KDialog not available!\nplease install one of these, or disable the system file picker in Settings > General."); +#endif + } if (fileDialog->accepted()) { fileName=fileDialog->getFileName(); if (fileName!="") { From bad11bc21e38466690e1c230c17b207ac0a4b95c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 14 Jul 2022 02:00:51 -0500 Subject: [PATCH 010/194] whoops --- src/gui/fileDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index bd54bf0fe..0bcc00a4b 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -28,7 +28,7 @@ struct NFDState { void _nfdThread(const NFDState state, std::atomic* ok, String* result, bool* errorOutput) { nfdchar_t* out=NULL; nfdresult_t ret=NFD_CANCEL; - errorOutput=false; + (*errorOutput)=false; if (state.isSave) { ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback); From 6f90124d82f8cb8ef20724e27998367d23cbea41 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 14 Jul 2022 22:15:14 -0500 Subject: [PATCH 011/194] issue #588, part 1 --- CMakeLists.txt | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0c46698..41818e96d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,8 @@ set(USE_SDL2_DEFAULT ON) set(USE_SNDFILE_DEFAULT ON) set(SYSTEM_SDL2_DEFAULT OFF) +include(CheckIncludeFile) + if (ANDROID) set(USE_RTMIDI_DEFAULT OFF) set(USE_BACKWARD_DEFAULT OFF) @@ -31,7 +33,17 @@ if (ANDROID) endif() else() set(USE_RTMIDI_DEFAULT ON) - set(USE_BACKWARD_DEFAULT ON) + CHECK_INCLUDE_FILE(execinfo.h EXECINFO_FOUND) + if (EXECINFO_FOUND) + set(USE_BACKWARD_DEFAULT ON) + else() + find_library(EXECINFO_IS_LIBRARY execinfo) + if (EXECINFO_IS_LIBRARY) + set(USE_BACKWARD_DEFAULT ON) + else() + set(USE_BACKWARD_DEFAULT OFF) + endif() + endif() endif() find_package(PkgConfig) @@ -548,8 +560,6 @@ endif() if (NOT WIN32 AND NOT APPLE) list(APPEND GUI_SOURCES src/gui/icon.c) - include(CheckIncludeFile) - CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND) @@ -574,6 +584,10 @@ if (USE_BACKWARD) if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") list(APPEND DEPENDENCIES_LIBRARIES dbghelp psapi) endif() + find_library(EXECINFO_IS_LIBRARY execinfo) + if (EXECINFO_IS_LIBRARY) + list(APPEND DEPENDENCIES_LIBRARIES execinfo) + endif() message(STATUS "Using backward-cpp") else() message(STATUS "Not using backward-cpp") From 3df5a6e2b6a0f6b4171280d4b172f2633f6c5f21 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 14 Jul 2022 22:17:05 -0500 Subject: [PATCH 012/194] issue #588, part 2 --- extern/backward/backward.hpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/extern/backward/backward.hpp b/extern/backward/backward.hpp index 5edda65ad..04032a4dc 100644 --- a/extern/backward/backward.hpp +++ b/extern/backward/backward.hpp @@ -221,6 +221,14 @@ #include #include #include +// https://github.com/tildearrow/furnace/issues/588 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#include +#undef _GNU_SOURCE +#else +#include +#endif #if BACKWARD_HAS_BFD == 1 // NOTE: defining PACKAGE{,_VERSION} is required before including @@ -233,13 +241,6 @@ #define PACKAGE_VERSION #endif #include -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#else -#include -#endif #endif #if BACKWARD_HAS_DW == 1 @@ -254,13 +255,6 @@ #include #include #include -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#else -#include -#endif #endif #if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) From d085f76c7f97da826712b62fe0b13025f42b957b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 14 Jul 2022 22:29:04 -0500 Subject: [PATCH 013/194] issue #588, part 3 add check for the existence of inb() and outb() --- CMakeLists.txt | 9 +++++++-- src/check/check_sysIO.c | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/check/check_sysIO.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 41818e96d..977f194aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,8 +564,13 @@ if (NOT WIN32 AND NOT APPLE) CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND) if (SYS_IO_FOUND) - list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO) - message(STATUS "PC speaker output: outb()") + try_compile(HAVE_INOUTB ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_sysIO.c) + if (HAVE_INOUTB) + list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO) + message(STATUS "PC speaker output: outb()") + else() + message(STATUS "sys/io.h found but inb()/outb() not present") + endif() endif() if (LINUX_INPUT_FOUND) list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_INPUT) diff --git a/src/check/check_sysIO.c b/src/check/check_sysIO.c new file mode 100644 index 000000000..653721e6d --- /dev/null +++ b/src/check/check_sysIO.c @@ -0,0 +1,6 @@ +#include + +int main(int, char**) { + inb(0x61); + outb(0x00,0x61); +} From 666b0d581a274856157366aa7f377b6a2c541737 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 15 Jul 2022 02:23:16 -0500 Subject: [PATCH 014/194] GUI: add multi-selection capability to file dialog --- src/gui/debugWindow.cpp | 14 +++++++++ src/gui/fileDialog.cpp | 65 +++++++++++++++++++++++++---------------- src/gui/fileDialog.h | 6 ++-- src/gui/gui.cpp | 64 +++++++++++++++++++++++++++++++++++++++- src/gui/gui.h | 8 +++-- 5 files changed, 126 insertions(+), 31 deletions(-) diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 22527a7b8..d9739f68d 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -260,6 +260,20 @@ void FurnaceGUI::drawDebug() { ImGui::Unindent(); ImGui::TreePop(); } + if (ImGui::TreeNode("File Selection Test")) { + if (ImGui::Button("Test Open")) { + openFileDialog(GUI_FILE_TEST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Test Open Multi")) { + openFileDialog(GUI_FILE_TEST_OPEN_MULTI); + } + ImGui::SameLine(); + if (ImGui::Button("Test Save")) { + openFileDialog(GUI_FILE_TEST_SAVE); + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Playground")) { if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 0bcc00a4b..d59a3cd03 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -10,13 +10,14 @@ #ifdef USE_NFD struct NFDState { - bool isSave; + bool isSave, allowMultiple; String header; std::vector filter; String path; FileDialogSelectCallback clickCallback; - NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc): + NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc, bool multi): isSave(save), + allowMultiple(multi), header(h), filter(filt), path(pa), @@ -61,7 +62,7 @@ void _nfdThread(const NFDState state, std::atomic* ok, String* result, boo } #endif -bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback) { +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback, bool allowMultiple) { if (opened) return false; saving=false; curPath=path; @@ -70,18 +71,18 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult,&hasError); + _nfdThread(NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); #else - dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult,&hasError); + dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); #endif #else - dialogO=new pfd::open_file(header,path,filter); + dialogO=new pfd::open_file(header,path,filter,allowMultiple?(pfd::opt::multiselect):(pfd::opt::none)); hasError=!pfd::settings::available(); #endif } else { hasError=false; ImGuiFileDialog::Instance()->DpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,0,clickCallback); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,allowMultiple?999:1,nullptr,0,clickCallback); } opened=true; return true; @@ -96,9 +97,9 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult,&hasError); + _nfdThread(NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); #else - dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult,&hasError); + dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); #endif #else dialogS=new pfd::save_file(header,path,filter); @@ -115,7 +116,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c bool FurnaceGUIFileDialog::accepted() { if (sysDialog) { - return (fileName!=""); + return (!fileName.empty()); } else { return ImGuiFileDialog::Instance()->IsOk(); } @@ -153,10 +154,15 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (sysDialog) { #ifdef USE_NFD if (dialogOK) { - fileName=nfdResult; - size_t dsPos=fileName.rfind(DIR_SEPARATOR); - if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); - logD("returning %s",fileName.c_str()); + fileName.clear(); + fileName.push_back(nfdResult); + if (!fileName.empty()) { + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + } + for (String& i: fileName) { + logD("- returning %s",i); + } dialogOK=false; return true; } @@ -165,10 +171,11 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (saving) { if (dialogS!=NULL) { if (dialogS->ready(0)) { - fileName=dialogS->result(); - size_t dsPos=fileName.rfind(DIR_SEPARATOR); - if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); - logD("returning %s",fileName.c_str()); + fileName.clear(); + fileName.push_back(dialogS->result()); + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + logD("returning %s",fileName[0]); return true; } } @@ -176,13 +183,19 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogO!=NULL) { if (dialogO->ready(0)) { if (dialogO->result().empty()) { - fileName=""; + fileName.clear(); logD("returning nothing"); } else { - fileName=dialogO->result()[0]; - size_t dsPos=fileName.rfind(DIR_SEPARATOR); - if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); - logD("returning %s",fileName.c_str()); + fileName=dialogO->result(); + if (fileName.empty()) { + // don't touch + } else { + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + for (String& i: fileName) { + logD("- returning %s",i); + } + } } return true; } @@ -217,10 +230,12 @@ String FurnaceGUIFileDialog::getPath() { } } -String FurnaceGUIFileDialog::getFileName() { +std::vector& FurnaceGUIFileDialog::getFileName() { if (sysDialog) { return fileName; } else { - return ImGuiFileDialog::Instance()->GetFilePathName(); + fileName.clear(); + fileName.push_back(ImGuiFileDialog::Instance()->GetFilePathName()); + return fileName; } } diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index f3edd2884..54b83c28a 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -30,7 +30,7 @@ class FurnaceGUIFileDialog { bool saving; bool hasError; String curPath; - String fileName; + std::vector fileName; #ifdef USE_NFD std::thread* dialogO; std::thread* dialogS; @@ -41,7 +41,7 @@ class FurnaceGUIFileDialog { pfd::save_file* dialogS; #endif public: - bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL); + bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL, bool allowMultiple=false); bool openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); bool accepted(); void close(); @@ -49,7 +49,7 @@ class FurnaceGUIFileDialog { bool isOpen(); bool isError(); String getPath(); - String getFileName(); + std::vector& getFileName(); explicit FurnaceGUIFileDialog(bool system): sysDialog(system), opened(false), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c6a58c083..32e698073 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1495,6 +1495,43 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_TEST_OPEN: + if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Open Test", + {"compatible files", "*.fur *.dmf *.mod", + "another option", "*.wav *.ttf", + "all files", ".*"}, + "compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*", + workingDirTest, + dpiScale + ); + break; + case GUI_FILE_TEST_OPEN_MULTI: + if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Open Test (Multi)", + {"compatible files", "*.fur *.dmf *.mod", + "another option", "*.wav *.ttf", + "all files", ".*"}, + "compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*", + workingDirTest, + dpiScale, + NULL, + true + ); + break; + case GUI_FILE_TEST_SAVE: + if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Test", + {"Furnace song", "*.fur", + "DefleMask module", "*.dmf"}, + "Furnace song{.fur},DefleMask module{.dmf}", + workingDirTest, + dpiScale + ); + break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -3220,6 +3257,11 @@ bool FurnaceGUI::loop() { case GUI_FILE_MU5_ROM_OPEN: workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_TEST_OPEN: + case GUI_FILE_TEST_OPEN_MULTI: + case GUI_FILE_TEST_SAVE: + workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; } if (fileDialog->isError()) { #if defined(_WIN32) || defined(__APPLE__) @@ -3229,7 +3271,11 @@ bool FurnaceGUI::loop() { #endif } if (fileDialog->accepted()) { - fileName=fileDialog->getFileName(); + if (fileDialog->getFileName().empty()) { + fileName=""; + } else { + fileName=fileDialog->getFileName()[0]; + } if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { // we can't tell whether the user chose .dmf or .fur in the system file picker @@ -3468,6 +3514,20 @@ bool FurnaceGUI::loop() { case GUI_FILE_MU5_ROM_OPEN: settings.mu5Path=copyOfName; break; + case GUI_FILE_TEST_OPEN: + showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC); + break; + case GUI_FILE_TEST_OPEN_MULTI: { + String msg="You opened:"; + for (String i: fileDialog->getFileName()) { + msg+=fmt::sprintf("\n- %s",i); + } + showWarning(msg,GUI_WARN_GENERIC); + break; + } + case GUI_FILE_TEST_SAVE: + showWarning(fmt::sprintf("You saved: %s",copyOfName),GUI_WARN_GENERIC); + break; } curFileDialog=GUI_FILE_OPEN; } @@ -4018,6 +4078,7 @@ bool FurnaceGUI::init() { workingDirColors=e->getConfString("lastDirColors",workingDir); workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir); workingDirLayout=e->getConfString("lastDirLayout",workingDir); + workingDirTest=e->getConfString("lastDirTest",workingDir); editControlsOpen=e->getConfBool("editControlsOpen",true); ordersOpen=e->getConfBool("ordersOpen",true); @@ -4255,6 +4316,7 @@ bool FurnaceGUI::finish() { e->setConf("lastDirColors",workingDirColors); e->setConf("lastDirKeybinds",workingDirKeybinds); e->setConf("lastDirLayout",workingDirLayout); + e->setConf("lastDirTest",workingDirTest); // commit last open windows e->setConf("editControlsOpen",editControlsOpen); diff --git a/src/gui/gui.h b/src/gui/gui.h index b3393a5a9..2966d594e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -277,7 +277,11 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_LAYOUT, GUI_FILE_YRW801_ROM_OPEN, GUI_FILE_TG100_ROM_OPEN, - GUI_FILE_MU5_ROM_OPEN + GUI_FILE_MU5_ROM_OPEN, + + GUI_FILE_TEST_OPEN, + GUI_FILE_TEST_OPEN_MULTI, + GUI_FILE_TEST_SAVE }; enum FurnaceGUIWarnings { @@ -943,7 +947,7 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM, workingDirTest; String mmlString[32]; String mmlStringW; From 96b7e5d3538c11c7876f56244b229fc3fbfa51df Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 16 Jul 2022 01:52:15 -0500 Subject: [PATCH 015/194] GUI: implement multi sel on NFD and IGFD --- src/gui/fileDialog.cpp | 39 +++++++++++++++++++++++++++++---------- src/gui/fileDialog.h | 2 +- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index d59a3cd03..8d524ab46 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -26,36 +26,48 @@ struct NFDState { }; // TODO: filter -void _nfdThread(const NFDState state, std::atomic* ok, String* result, bool* errorOutput) { +void _nfdThread(const NFDState state, std::atomic* ok, std::vector* result, bool* errorOutput) { nfdchar_t* out=NULL; nfdresult_t ret=NFD_CANCEL; (*errorOutput)=false; + nfdpathset_t paths; + + result->clear(); if (state.isSave) { ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback); } else { - ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + if (state.allowMultiple) { + ret=NFD_OpenDialogMultiple(state.filter,state.path.c_str(),&paths,state.clickCallback); + } else { + ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + } } switch (ret) { case NFD_OKAY: - if (out!=NULL) { - (*result)=out; + if (state.allowMultiple) { + logD("pushing multi path"); + for (size_t i=0; ipush_back(String(NFD_PathSet_GetPath(&paths,i))); + } + NFD_PathSet_Free(&paths); } else { - (*result)=""; + logD("pushing single path"); + if (out!=NULL) { + logD("we have it"); + result->push_back(String(out)); + } } break; case NFD_CANCEL: - (*result)=""; break; case NFD_ERROR: - (*result)=""; logE("NFD error! %s\n",NFD_GetError()); (*errorOutput)=true; break; default: logE("NFD unknown return code %d!\n",ret); - (*result)=""; break; } (*ok)=true; @@ -155,7 +167,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { #ifdef USE_NFD if (dialogOK) { fileName.clear(); - fileName.push_back(nfdResult); + fileName=nfdResult; if (!fileName.empty()) { size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); @@ -235,7 +247,14 @@ std::vector& FurnaceGUIFileDialog::getFileName() { return fileName; } else { fileName.clear(); - fileName.push_back(ImGuiFileDialog::Instance()->GetFilePathName()); + if (saving) { + fileName.push_back(ImGuiFileDialog::Instance()->GetFilePathName()); + } else { + for (auto& i: ImGuiFileDialog::Instance()->GetSelection()) { + fileName.push_back(i.second); + } + } + // return fileName; } } diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 54b83c28a..7990b0370 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -35,7 +35,7 @@ class FurnaceGUIFileDialog { std::thread* dialogO; std::thread* dialogS; std::atomic dialogOK; - String nfdResult; + std::vector nfdResult; #else pfd::open_file* dialogO; pfd::save_file* dialogS; From 707dc30f1558c1cd61741b94a5e75d34df3062e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Jul 2022 00:05:56 -0500 Subject: [PATCH 016/194] Revert "Fix issue #567: LFO disable/enable behavior for YM2151." --- src/engine/platform/arcade.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 29a2f67d0..41042cd5c 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -172,7 +172,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si w.addrOrVal=true; } } - + OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); @@ -182,13 +182,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si for (int i=0; i<8; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]; } - + if (o[0]<-32768) o[0]=-32768; if (o[0]>32767) o[0]=32767; if (o[1]<-32768) o[1]=-32768; if (o[1]>32767) o[1]=32767; - + bufL[h]=o[0]; bufR[h]=o[1]; } @@ -211,7 +211,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz delay=1; } } - + fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { @@ -225,7 +225,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz os[1]=out_ymfm.data[1]; if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; - + bufL[h]=os[0]; bufR[h]=os[1]; } @@ -616,12 +616,6 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - if(c.value==0) { - rWrite(0x01,0x02); - } - else { - rWrite(0x01,0x00); - } rWrite(0x18,c.value); break; } @@ -945,8 +939,6 @@ void DivPlatformArcade::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); - immWrite(0x01,0x02); // LFO Off - immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); From a4741861cef7e8162b0398a5fe46d781b2ecc8cf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 19 Jul 2022 15:57:06 -0500 Subject: [PATCH 017/194] fix audio output being reset on cmd line export --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e68208ea8..d49764cc3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3081,7 +3081,7 @@ bool DivEngine::deinitAudioBackend() { output->quit(); delete output; output=NULL; - audioEngine=DIV_AUDIO_NULL; + //audioEngine=DIV_AUDIO_NULL; } return true; } From cd7b333b2df8ae6727e500a4f059fadfdff5e04d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 19 Jul 2022 17:01:19 -0500 Subject: [PATCH 018/194] introduce a benchmark mode --- src/asm/6502/macroInt.s | 32 ++++++++++++++++++++++ src/engine/engine.cpp | 60 +++++++++++++++++++++++++++++++++++++++-- src/engine/engine.h | 4 +++ src/main.cpp | 25 +++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/asm/6502/macroInt.s diff --git a/src/asm/6502/macroInt.s b/src/asm/6502/macroInt.s new file mode 100644 index 000000000..c115a807e --- /dev/null +++ b/src/asm/6502/macroInt.s @@ -0,0 +1,32 @@ +macroState=$50 ; pointer to state +macroAddr=$52 ; pointer to address + +; macro state takes 4 bytes +; macroPos bits: +; 7: had +; 6: will + +; x: macro +macroIntRun: + lda macroAddr,x + ora macroAddr+1,x + beq :+ + + ; do macro +: rts + +; set the macro address, then call +; x: macro +macroIntInit: + lda #0 + sta macroState,x + sta macroPos,x + txa + rol + tax + lda macroAddr,x + ora macroAddr+1,x + beq :+ + lda #$40 + sta macroState,x +: rts diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index d49764cc3..9b3fa06fd 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -36,6 +36,7 @@ #include "../audio/jack.h" #endif #include +#include #ifdef HAVE_SNDFILE #include "sfWrapper.h" #endif @@ -181,6 +182,63 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { } } +#define EXPORT_BUFSIZE 2048 + +double DivEngine::benchmarkPlayback() { + float* outBuf[2]; + outBuf[0]=new float[EXPORT_BUFSIZE]; + outBuf[1]=new float[EXPORT_BUFSIZE]; + + curOrder=0; + prevOrder=0; + remainingLoops=1; + playSub(false); + + std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); + + // benchmark + while (playing) { + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + } + + std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); + + delete[] outBuf[0]; + delete[] outBuf[1]; + + double t=(double)(std::chrono::duration_cast(timeEnd-timeStart).count())/1000000.0; + printf("[RESULT] %fs\n",t); + return t; +} + +double DivEngine::benchmarkSeek() { + double t[20]; + curOrder=curSubSong->ordersLen-1; + prevOrder=curSubSong->ordersLen-1; + + // benchmark + for (int i=0; i<20; i++) { + std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); + playSub(false); + std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); + t[i]=(double)(std::chrono::duration_cast(timeEnd-timeStart).count())/1000000.0; + printf("[#%d] %fs\n",i+1,t[i]); + } + + double tMin=DBL_MAX; + double tMax=0.0; + double tAvg=0.0; + for (int i=0; i<20; i++) { + if (t[i]tMax) tMax=t[i]; + tAvg+=t[i]; + } + tAvg/=20.0; + + printf("[RESULT] min %fs max %fs average %fs\n",tMin,tMax,tAvg); + return tAvg; +} + void _runExportThread(DivEngine* caller) { caller->runExportThread(); } @@ -189,8 +247,6 @@ bool DivEngine::isExporting() { return exporting; } -#define EXPORT_BUFSIZE 2048 - #ifdef HAVE_SNDFILE void DivEngine::runExportThread() { size_t fadeOutSamples=got.rate*exportFadeOut; diff --git a/src/engine/engine.h b/src/engine/engine.h index 5f5ab1a35..4bf4cb65d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -477,6 +477,10 @@ class DivEngine { // notify wavetable change void notifyWaveChange(int wave); + // benchmark (returns time in seconds) + double benchmarkPlayback(); + double benchmarkSeek(); + // returns the minimum VGM version which may carry the specified system, or 0 if none. int minVGMVersion(DivSystem which); diff --git a/src/main.cpp b/src/main.cpp index 1f5f9560a..970bc784f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,6 +49,7 @@ FurnaceGUI g; String outName; String vgmOutName; int loops=1; +int benchMode=0; DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE; #ifdef HAVE_GUI @@ -220,6 +221,19 @@ TAParamResult pOutMode(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pBenchmark(String val) { + if (val=="render") { + benchMode=1; + } else if (val=="seek") { + benchMode=2; + } else { + logE("invalid value for benchmark! valid values are: render and seek."); + return TA_PARAM_ERROR; + } + e.setAudio(DIV_AUDIO_DUMMY); + return TA_PARAM_SUCCESS; +} + TAParamResult pOutput(String val) { outName=val; e.setAudio(DIV_AUDIO_DUMMY); @@ -254,6 +268,8 @@ void initParams() { params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); + params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek","run performance test")); + params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace.")); params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer.")); } @@ -414,6 +430,15 @@ int main(int argc, char** argv) { displayEngineFailError=true; } } + if (benchMode) { + logI("starting benchmark!"); + if (benchMode==2) { + e.benchmarkSeek(); + } else { + e.benchmarkPlayback(); + } + return 0; + } if (outName!="" || vgmOutName!="") { if (vgmOutName!="") { SafeWriter* w=e.saveVGM(); From dff7c61b79213b8f33f0d3c7f22f161ce738f83d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 20 Jul 2022 00:32:06 -0500 Subject: [PATCH 019/194] GUI: add option to disable threaded input --- src/gui/gui.cpp | 9 ++++++++- src/gui/gui.h | 2 ++ src/gui/settings.cpp | 11 +++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 32e698073..14b1b99e3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2479,7 +2479,13 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { } bool FurnaceGUI::loop() { - SDL_SetEventFilter(_processEvent,this); + bool doThreadedInput=!settings.noThreadedInput; + if (doThreadedInput) { + logD("key input: event filter"); + SDL_SetEventFilter(_processEvent,this); + } else { + logD("key input: main thread"); + } while (!quit) { SDL_Event ev; @@ -2495,6 +2501,7 @@ bool FurnaceGUI::loop() { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); processPoint(ev); + if (!doThreadedInput) processEvent(&ev); switch (ev.type) { case SDL_MOUSEMOTION: { int motionX=ev.motion.x; diff --git a/src/gui/gui.h b/src/gui/gui.h index 2966d594e..21bb0cf7d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1091,6 +1091,7 @@ class FurnaceGUI { int blankIns; int dragMovesSelection; int unsignedDetune; + int noThreadedInput; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1193,6 +1194,7 @@ class FurnaceGUI { blankIns(0), dragMovesSelection(1), unsignedDetune(0), + noThreadedInput(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index cb2064e62..9984e4996 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -460,6 +460,14 @@ void FurnaceGUI::drawSettings() { ImGui::SetTooltip("saves power by lowering the frame rate to 2fps when idle.\nmay cause issues under Mesa drivers!"); } + bool noThreadedInputB=settings.noThreadedInput; + if (ImGui::Checkbox("Disable threaded input (restart after changing!)",&noThreadedInputB)) { + settings.noThreadedInput=noThreadedInputB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("threaded input processes key presses for note preview on a separate thread (on supported platforms), which reduces latency.\nhowever, crashes have been reported when threaded input is on. enable this option if that is the case."); + } + bool blankInsB=settings.blankIns; if (ImGui::Checkbox("New instruments are blank",&blankInsB)) { settings.blankIns=blankInsB; @@ -2055,6 +2063,7 @@ void FurnaceGUI::syncSettings() { settings.blankIns=e->getConfInt("blankIns",0); settings.dragMovesSelection=e->getConfInt("dragMovesSelection",2); settings.unsignedDetune=e->getConfInt("unsignedDetune",0); + settings.noThreadedInput=e->getConfInt("noThreadedInput",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2141,6 +2150,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.blankIns,0,1); clampSetting(settings.dragMovesSelection,0,2); clampSetting(settings.unsignedDetune,0,1); + clampSetting(settings.noThreadedInput,0,1); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2276,6 +2286,7 @@ void FurnaceGUI::commitSettings() { e->setConf("blankIns",settings.blankIns); e->setConf("dragMovesSelection",settings.dragMovesSelection); e->setConf("unsignedDetune",settings.unsignedDetune); + e->setConf("noThreadedInput",settings.noThreadedInput); // colors for (int i=0; i Date: Wed, 20 Jul 2022 23:01:06 +0900 Subject: [PATCH 020/194] Struct-ize sample map variable --- src/engine/fileOps.cpp | 4 ++-- src/engine/instrument.cpp | 16 ++++++++++++---- src/engine/instrument.h | 19 +++++++++++++------ src/gui/insEdit.cpp | 25 +++++++++++++------------ 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index ddc5edf6b..3aa432fdb 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2445,8 +2445,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { const int dpcmNotes=(blockVersion>=2)?96:72; for (int j=0; jamiga.noteMap[j]=(short)((unsigned char)reader.readC())-1; - ins->amiga.noteFreq[j]=(unsigned char)reader.readC(); + ins->amiga.noteMap[j].map=(short)((unsigned char)reader.readC())-1; + ins->amiga.noteMap[j].freq=(unsigned char)reader.readC(); if (blockVersion>=6) { reader.readC(); // DMC value } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 196764d0f..a76588561 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -387,8 +387,12 @@ void DivInstrument::putInsData(SafeWriter* w) { // sample map w->writeC(amiga.useNoteMap); if (amiga.useNoteMap) { - w->write(amiga.noteFreq,120*sizeof(unsigned int)); - w->write(amiga.noteMap,120*sizeof(short)); + for (int note=0; note<120; note++) { + w->writeI(amiga.noteMap[note].freq); + } + for (int note=0; note<120; note++) { + w->writeS(amiga.noteMap[note].map); + } } // N163 @@ -932,8 +936,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { if (version>=67) { amiga.useNoteMap=reader.readC(); if (amiga.useNoteMap) { - reader.read(amiga.noteFreq,120*sizeof(unsigned int)); - reader.read(amiga.noteMap,120*sizeof(short)); + for (int note=0; note<120; note++) { + amiga.noteMap[note].freq=reader.readI(); + } + for (int note=0; note<120; note++) { + amiga.noteMap[note].map=reader.readS(); + } } } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index c4af6f042..18bf2c5f7 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -306,12 +306,18 @@ struct DivInstrumentC64 { }; struct DivInstrumentAmiga { + struct SampleMap { + int freq; + short map; + SampleMap(int f=0, short m=-1): + freq(f), + map(m) {} + }; short initSample; bool useNoteMap; bool useWave; unsigned char waveLen; - int noteFreq[120]; - short noteMap[120]; + SampleMap noteMap[120]; /** * get the sample at specified note. @@ -321,7 +327,7 @@ struct DivInstrumentAmiga { if (useNoteMap) { if (note<0) note=0; if (note>119) note=119; - return noteMap[note]; + return noteMap[note].map; } return initSample; } @@ -334,7 +340,7 @@ struct DivInstrumentAmiga { if (useNoteMap) { if (note<0) note=0; if (note>119) note=119; - return noteFreq[note]; + return noteMap[note].freq; } return -1; } @@ -344,8 +350,9 @@ struct DivInstrumentAmiga { useNoteMap(false), useWave(false), waveLen(31) { - memset(noteMap,-1,120*sizeof(short)); - memset(noteFreq,0,120*sizeof(int)); + for (SampleMap& elem: noteMap) { + elem=SampleMap(); + } } }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 45f7c6757..5633bb895 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3011,7 +3011,7 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::BeginTable("NoteMap",2,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + //ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0,1); @@ -3022,37 +3022,38 @@ void FurnaceGUI::drawInsEdit() { /*ImGui::TableNextColumn(); ImGui::Text("Frequency");*/ for (int i=0; i<120; i++) { + DivInstrumentAmiga::SampleMap sampleMap=ins->amiga.noteMap[i]; ImGui::TableNextRow(); ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); ImGui::TableNextColumn(); ImGui::Text("%s",noteNames[60+i]); ImGui::TableNextColumn(); - if (ins->amiga.noteMap[i]<0 || ins->amiga.noteMap[i]>=e->song.sampleLen) { + if (sampleMap.map<0 || sampleMap.map>=e->song.sampleLen) { sName="-- empty --"; - ins->amiga.noteMap[i]=-1; + sampleMap.map=-1; } else { - sName=e->song.sample[ins->amiga.noteMap[i]]->name; + sName=e->song.sample[sampleMap.map]->name; } ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SM",sName.c_str())) { String id; - if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i]==-1)) { PARAMETER - ins->amiga.noteMap[i]=-1; + if (ImGui::Selectable("-- empty --",sampleMap.map==-1)) { PARAMETER + sampleMap.map=-1; } for (int j=0; jsong.sampleLen; j++) { id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); - if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i]==j)) { PARAMETER - ins->amiga.noteMap[i]=j; - if (ins->amiga.noteFreq[i]<=0) ins->amiga.noteFreq[i]=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0)); + if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER + sampleMap.map=j; + if (sampleMap.freq<=0) sampleMap.freq=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0)); } } ImGui::EndCombo(); } /*ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SF",&ins->amiga.noteFreq[i],50,500)) { PARAMETER - if (ins->amiga.noteFreq[i]<0) ins->amiga.noteFreq[i]=0; - if (ins->amiga.noteFreq[i]>262144) ins->amiga.noteFreq[i]=262144; + if (ImGui::InputInt("##SF",&sampleMap.freq,50,500)) { PARAMETER + if (sampleMap.freq<0) sampleMap.freq=0; + if (sampleMap.freq>262144) sampleMap.freq=262144; }*/ ImGui::PopID(); } From 4e8d71fc22b95bd0cc4f7fde846d191f74d86868 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 21 Jul 2022 13:42:20 +0900 Subject: [PATCH 021/194] Fix sample map struct Structize sample map variable is for easily extend features. --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 5633bb895..18e286802 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3022,7 +3022,7 @@ void FurnaceGUI::drawInsEdit() { /*ImGui::TableNextColumn(); ImGui::Text("Frequency");*/ for (int i=0; i<120; i++) { - DivInstrumentAmiga::SampleMap sampleMap=ins->amiga.noteMap[i]; + DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i]; ImGui::TableNextRow(); ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); ImGui::TableNextColumn(); From d8dff3c20794ccc8850e8481168dffd97d731942 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 20 Jul 2022 23:52:35 -0500 Subject: [PATCH 022/194] add demo song requested by LovelyA72 --- demos/Phoenix_cover.fur | Bin 0 -> 1299 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/Phoenix_cover.fur diff --git a/demos/Phoenix_cover.fur b/demos/Phoenix_cover.fur new file mode 100644 index 0000000000000000000000000000000000000000..1a031443ada73d84d39666d43f60319881eb190b GIT binary patch literal 1299 zcmV+u1?>8Gob6j*ZyQArpF7*}zoZvMf)+}fMIbdniWFNF1tf&sC_#}z5~ZO$U+r~b zVf%s|qviprujK>q03QKfctiq;F906_z5=g^2bewI#p~1?dmJCxt9R{5#<#OGznz=8 z+1)s6Titfct#H=#s@(=(;{d>*-?n$R_MY3wWHJ`O$-@#PAi0pO_~A`}U)~1z{T}we1Mt@xjwk>u-3NH<0l@ta05(4acw7QF+5mXE z1@L?u;M*O5@Am+HJ^=XbQ(?yLy)D0;Lq0sNcNo6i9$Mzr*y%B6;Y{3i#jSF7+1;+>RT1@H~N_D&F5>xiu!culv3(TD)c zO|QKQ7;XSRqKE%MTE`EM_I5vhD8`)!q>TY7lr0O?zM%jr);R)N73(r@Bg{K(H!t-MHU>oNighfZ*WraJf{1`e- z5sI%Z>e#rqY5S6@-`E*tXLgFvXXVG63qnIFxFDYFLVgiRln-%`{wmTfq}P#dBfWui z2kGvFbSKaubiIajGqAH1*cC-U9Iu5(X%~;q!Xq*3iX?O-0^RArrkMMF`S2hs-S(t! zwSTYV`!2v%lNL2UPxMdJ^1Atl8b7K{zG`!>+T^QFN6G%_ zCq(~mJkxfP%%?QFfwpFNbPI6Z@K7HZWWJve{co;N6zEn_@|xj^I-scYrQRkveSH~h8n@{-9UgyCnNmn%fLXc8US;A^|r4)7dLf`rH5H~1&QFDFF! z9lxG}9MJ)3qP)>>^lPX;ObGd(LB=&=c*DPRd_N)npJP|U8(wR^pAeHByBgl`FBLB@ znanAZ;b)$gmrN!h3_tU{Tp{FRS4XpBGlzKWXYd>R6XBN=LOynNh{swE@z~7hH~O{I J{~u)b{&=X>hxh;h literal 0 HcmV?d00001 From 962dab012cf86eed89bb4334674f4d56cde30ca4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 02:49:19 -0500 Subject: [PATCH 023/194] GUI: improve wavetable editor, part 1 --- src/gui/gui.cpp | 3 + src/gui/gui.h | 1 + src/gui/waveEdit.cpp | 129 +++++++++++++++++++------------------------ 3 files changed, 62 insertions(+), 71 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 14b1b99e3..012925323 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4116,6 +4116,7 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + waveEditStyle=e->getConfInt("waveEditStyle",0); lockLayout=e->getConfBool("lockLayout",false); #ifdef IS_MOBILE fullScreen=true; @@ -4359,6 +4360,7 @@ bool FurnaceGUI::finish() { e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); + e->setConf("waveEditStyle",waveEditStyle); e->setConf("lockLayout",lockLayout); e->setConf("fullScreen",fullScreen); e->setConf("mobileUI",mobileUI); @@ -4438,6 +4440,7 @@ FurnaceGUI::FurnaceGUI(): vgmExportVersion(0x171), drawHalt(10), macroPointSize(16), + waveEditStyle(0), globalWinFlags(0), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), diff --git a/src/gui/gui.h b/src/gui/gui.h index 21bb0cf7d..e87b24ba9 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -958,6 +958,7 @@ class FurnaceGUI { int vgmExportVersion; int drawHalt; int macroPointSize; + int waveEditStyle; ImGuiWindowFlags globalWinFlags; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 4ff2182e2..549bddb12 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -36,31 +36,45 @@ void FurnaceGUI::drawWaveEdit() { if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); } else { - ImGui::SetNextItemWidth(80.0f*dpiScale); - if (ImGui::InputInt("##CurWave",&curWave,1,1)) { - if (curWave<0) curWave=0; - if (curWave>=(int)e->song.wave.size()) curWave=e->song.wave.size()-1; - } - ImGui::SameLine(); - // TODO: load replace - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WELoad")) { - doAction(GUI_ACTION_WAVE_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { - doAction(GUI_ACTION_WAVE_LIST_SAVE); - } - ImGui::SameLine(); - DivWavetable* wave=e->song.wave[curWave]; - - if (!settings.waveLayout){ + + if (ImGui::BeginTable("WEProps",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(80.0f*dpiScale); + if (ImGui::InputInt("##CurWave",&curWave,1,1)) { + if (curWave<0) curWave=0; + if (curWave>=(int)e->song.wave.size()) curWave=e->song.wave.size()-1; + } + ImGui::SameLine(); + // TODO: load replace + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WELoad")) { + doAction(GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE); + } + ImGui::SameLine(); + + if (ImGui::RadioButton("Steps",waveEditStyle==0)) { + waveEditStyle=0; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Lines",waveEditStyle==1)) { + waveEditStyle=1; + } + + ImGui::TableNextColumn(); ImGui::Text("Width"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); } ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); + ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { if (wave->len>256) wave->len=256; if (wave->len<1) wave->len=1; @@ -74,56 +88,15 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); } ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); + ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { if (wave->max>255) wave->max=255; if (wave->max<1) wave->max=1; e->notifyWaveChange(curWave); MARK_MODIFIED; } - } - ImGui::SameLine(); - if (ImGui::RadioButton("Dec",!waveHex)) { - waveHex=false; - } - ImGui::SameLine(); - if (ImGui::RadioButton("Hex",waveHex)) { - waveHex=true; - } - if (settings.waveLayout){ - if (ImGui::BeginTable("WaveProps",2,ImGuiTableFlags_SizingStretchSame)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Width"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { - if (wave->len>256) wave->len=256; - if (wave->len<1) wave->len=1; - e->notifyWaveChange(curWave); - if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); - MARK_MODIFIED; - } - ImGui::TableNextColumn(); - ImGui::SameLine(); - ImGui::Text("Height"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { - if (wave->max>255) wave->max=255; - if (wave->max<1) wave->max=1; - e->notifyWaveChange(curWave); - MARK_MODIFIED; - } - ImGui::EndTable(); - } + ImGui::EndTable(); } for (int i=0; ilen; i++) { @@ -131,21 +104,19 @@ void FurnaceGUI::drawWaveEdit() { wavePreview[i]=wave->data[i]; } if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here - if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex); - } - if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex); - } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here - if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { + contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y; + /*if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); + }*/ + if (waveEditStyle) { + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); + } else { + PlotCustom("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); } - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { waveDragStart=ImGui::GetItemRectMin(); waveDragAreaSize=contentRegion; @@ -159,6 +130,22 @@ void FurnaceGUI::drawWaveEdit() { modified=true; } ImGui::PopStyleVar(); + + if (ImGui::RadioButton("Dec",!waveHex)) { + waveHex=false; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Hex",waveHex)) { + waveHex=true; + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here + if (ImGui::InputText("##MMLWave",&mmlStringW)) { + decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex); + } + if (!ImGui::IsItemActive()) { + encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex); + } } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_EDIT; From 191a0dedf90635b934657d497566a433ae6f165f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 03:14:52 -0500 Subject: [PATCH 024/194] GUI: improve wavetable editor, part 2 --- src/gui/gui.cpp | 3 ++ src/gui/gui.h | 2 +- src/gui/waveEdit.cpp | 74 ++++++++++++++++++++++++++++++-------------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 012925323..90925b934 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4116,6 +4116,7 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + waveGenVisible=e->getConfBool("waveGenVisible",false); waveEditStyle=e->getConfInt("waveEditStyle",0); lockLayout=e->getConfBool("lockLayout",false); #ifdef IS_MOBILE @@ -4360,6 +4361,7 @@ bool FurnaceGUI::finish() { e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); + e->setConf("waveGenVisible",waveGenVisible); e->setConf("waveEditStyle",waveEditStyle); e->setConf("lockLayout",lockLayout); e->setConf("fullScreen",fullScreen); @@ -4535,6 +4537,7 @@ FurnaceGUI::FurnaceGUI(): firstFrame(true), tempoView(true), waveHex(false), + waveGenVisible(false), lockLayout(false), editOptsVisible(false), latchNibble(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index e87b24ba9..656b358e5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1222,7 +1222,7 @@ class FurnaceGUI { SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 549bddb12..f8c27b1fa 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -96,6 +96,11 @@ void FurnaceGUI::drawWaveEdit() { MARK_MODIFIED; } + ImGui::SameLine(); + if (ImGui::Button(waveGenVisible?(ICON_FA_CHEVRON_RIGHT "##WEWaveGen"):(ICON_FA_CHEVRON_LEFT "##WEWaveGen"))) { + waveGenVisible=!waveGenVisible; + } + ImGui::EndTable(); } @@ -105,31 +110,54 @@ void FurnaceGUI::drawWaveEdit() { } if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + if (ImGui::BeginTable("WEWaveSection",waveGenVisible?2:1)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + if (waveGenVisible) ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,250.0f*dpiScale); + ImGui::TableNextRow(); - ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here - contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y; - /*if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { - contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); - }*/ - if (waveEditStyle) { - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); - } else { - PlotCustom("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + + ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here + contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y; + if (waveEditStyle) { + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); + } else { + PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + waveDragStart=ImGui::GetItemRectMin(); + waveDragAreaSize=contentRegion; + waveDragMin=0; + waveDragMax=wave->max; + waveDragLen=wave->len; + waveDragActive=true; + waveDragTarget=wave->data; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + e->notifyWaveChange(curWave); + modified=true; + } + ImGui::PopStyleVar(); + + if (waveGenVisible) { + ImGui::TableNextColumn(); + + if (ImGui::BeginTabBar("WaveGenOpt")) { + if (ImGui::BeginTabItem("Shapes")) { + ImGui::Button("Square"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("FM")) { + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Mangle")) { + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + ImGui::EndTable(); } - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - waveDragStart=ImGui::GetItemRectMin(); - waveDragAreaSize=contentRegion; - waveDragMin=0; - waveDragMax=wave->max; - waveDragLen=wave->len; - waveDragActive=true; - waveDragTarget=wave->data; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - e->notifyWaveChange(curWave); - modified=true; - } - ImGui::PopStyleVar(); if (ImGui::RadioButton("Dec",!waveHex)) { waveHex=false; From 09b47fafe3e6d8f242b6467f0183f68f3ba7245b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 14:49:42 -0500 Subject: [PATCH 025/194] update demo songs --- demos/Melody_of_Certain_Feelings.fur | Bin 17076 -> 0 bytes demos/UNATCOPCM.fur | Bin 205943 -> 0 bytes demos/ecolove.fur | Bin 57810 -> 0 bytes demos/neon_night_riders_TFMX.fur | Bin 160437 -> 0 bytes demos/wolf3d.fur | Bin 29910 -> 0 bytes src/gui/about.cpp | 1 - 6 files changed, 1 deletion(-) delete mode 100644 demos/Melody_of_Certain_Feelings.fur delete mode 100644 demos/UNATCOPCM.fur delete mode 100644 demos/ecolove.fur delete mode 100644 demos/neon_night_riders_TFMX.fur delete mode 100644 demos/wolf3d.fur diff --git a/demos/Melody_of_Certain_Feelings.fur b/demos/Melody_of_Certain_Feelings.fur deleted file mode 100644 index 6ccf4e13cb18a3104f26a94ef12f2314b34d2046..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17076 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%0DiuQr6TUsnZmi*avrsHpyLAXuy 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 diff --git a/demos/ecolove.fur b/demos/ecolove.fur deleted file mode 100644 index 744b0c9f6d563860bcf687ba08cea23176473a0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57810 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%0Dn#%v8oaAO=Y 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�UycrE!j& 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 Ph;?zo9Dx)Sn*{tXqkx=4 diff --git a/demos/neon_night_riders_TFMX.fur b/demos/neon_night_riders_TFMX.fur deleted file mode 100644 index a15f448922687b0c04126fa55d40ddbd4930aee1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160437 zcmZ^qQ;;w^(5A<>ZQHi(Z*1GPZQHhO+qP|c_UvBn?q8Lv+;uu#=~Q>#C+^wTJ#OAO zXD$?6UCdqFTV4ZnXzI_q(31Wn>sca-jpdeA^GGxUr4k{ey`FyCX~(J2(4OFEJ=S+UX>Pq6qJ*Sc2DQF#ynGJ;++Ex!+yLr9-O+`@~F^-$EnTaahFo?H}n*%yuODw)O0z2N0Y{4 zuO{8h=!WYzb})E@!o;*jB#L@Qh8ghThhn7|uPxz1|6MX%Yn5Q60)@J<8K}>p!Bp)9 z7}(tm)kh9tycUfGh|d|WtLYBcH`M8`=kLqW!S{)eb;>wbtH&DgTMRYe#vQD)_XQa3tT|X8lEr3t+0!h>8C@ITD90VB zuaLyra&I9;|Dr;(VEb$MzhJHeB)HV8b7#QwACxaKt89;7k=D ztCI%xZ%P7rrAzD2K|)*yr}zKbVFvV6kksQ>KwjT?19hp&=o_Jem{yb4D4kOhq%s)LtN*_CH2>F#!=``)vva*z~2(Kx!{l! zj^7|sPkOoqjyuOHw;^t^DQultZVlONRoarhs&r6zdod!PJ=gUTemj{xB3MXs0hf2l zSyDNi z$K&4d_@-{*f-^gdVDI(4j?n~|T^NePS;6&Ia(lk6U>@U^zv`4bk9F-?t*Bb?(Pp)B zw(69uNWY!3v!3TppR~#`A7vO?(y7bgjXiZaU6H#a+LqG8NF2 zGbZoX@l;axFUXI>N9^shBB{B)J zVR~SVOMdM#c2H6SW-Jz;|Knw8rPHV`2c%FwSCEGDiHc$0nYB#P?fN9GYt}i32hj9! zv1&{%8{m7Tv#xLRWw8pSHpy(ipPFIT0_skhT+i$0*aAa;>2f2yJxzWPRRBl-s9(bl zpiAB@=d$_)09+U78Di%m`lXin+IbD~K5$Hz%L$D^0f*D)^<_;ym#gTC%cl^gwXVk( z$DGrFOxR`B#&P$YN4bg{!b>jig6FmIw#qC(x*u~Vqb_fm*AvDa2KmYp*W5oMd%nk& zZR+uqxvqBR{aCGBr;J;V3$mKqqfmLQLrduD828Sz2HM~|vi1t*e9t0&szPq3V%BJ^ z>p!!e7Do~g4Yyb1b9jCkPbSBE^4Zf!5wjQH^k)66tTl`pZZM%9Kkp6f@p8GGAD?$w zClks#bP@71%MHcF!)^%?j;(Z7aOzV>Vp;jad?RA$ z;D>s_`8BFMj~|N8e@J1Xd#_6B0<4CZbSh#cKehDe z?lqnsb;tbt&zA1bG2)o$mdXJcaex0Xf%q|h^9P_pGS=>bVgOGckP%*QqacL65Df74 zJHfTzJa2yB@>uEbT)x5G`C#YHm-Cbh%$wlFFRvdpsYPr`|-f2k?h8z}s&D*Lrll`i9JAro3?f0CVMs zo;YKeb;xh4ID~n^ng0i4W^!&h?kDbN5lLoTcX{eV?g&=@gM+I$9Ij$*U+)u3Id9_W z27D0zgGp@D*zR4A!(IrcYH-0*z*?{V|2t6Ld_mI}v$e>27neM~e#uP7K@?8_YrOlN zYgTt|@6{e2bw~XDPZsWcd5Wj*XANJ({q^`D>Hpsye!wzxHr4I5F^2wqG5(Yp!uYO5 zxvWJ!;r9!F?Lo-=ak75?xmCaOM_lRF_3Rxqou2f>{sGLD9ej9?mazYqjoIIy5wptY zb%!`hxcnZ4Pw&{BYTR)-Tuyq!RI@>z=l_5Ik7g6-uleqUrhE_$+u?<#fwkZK|KAlt zF`TDr9(uzWzQGayhd>ZdQ=4_o;&6-pT-;9Y3G~qy#_S6=d-z}B+3#Gnym5Q4c6YBm z;O~F1@Z`;z3%4M2dp};ijd^CtTN1qkr~fJ|lEHnk_YS)C0AcIi!q&+T{p2_7|Bn1W zA@?m@ISRw~!RRUSbA1v&@8dN7fy=3r!M<7oit#Z#S9Ga z-@%3hIRY6lYzP)YleC=7w6t{o7gK`zqSa=5xBhURFcioCJB=(J*YiG%-uJw+CfEDf zqFg4&|MnQc&*$}+GTY?$t=Idy!teR>Qd@hJLyynxdi`m*e%f*7rLz_j`i>cYyzUYuEd;nq}vERQG#G z|GW10j_>!o_qUZt_>K(q`#kr1-S_j6_xJJlt>^ck=J#{T-usYq_j?2H_a5Z;K9=|Q zeb)DV(%19z3Vz1d?R~QOa=W`5kgzZj@$m6+a$*uSbL;5k{VPF}AyeXzJwf8(T_8b| zLV4^M!zN#}f{KcYj;@xRruJ`Tb(M{Ug^k79(q^OC!f9*OMyt)$+4}B$z0Gd#cIOKo zkL!7!SR8@pVK_;i@8NlA&if&1j`!^wdXE2Z5Sku$sy|K7`(OlJ&-?e|EZ6IH!z|C| zF`_J&!}ECjU?>d!daujt?e(?w^mH`yGICK#r&p{10s38rj5a4W7)UN`#5_GO3lkgr z`mW34XtP8b-}nA^>+g3<@8|N2|0@mu?_J#Q?~VU^V9)38YwzpUtzyQl4;&z?@8iF* z)%P_3kNCaB|MMj8`$^CLwNdByV`ta*^b3yP{WlPnMyGSv>+qbEbnyrtIH16S2HZRE0G2dy@c8Z>pg@u!feK|J7b{kzSmpHe2n`Jl1sx?V z1voiLMNMsGb#-}(g@tu_g^it+nTe&P)z;i%XS3I6BJHK6<@MDX8ykzu-D0RmJTF3gze z+?fluBT|J4EhboaK!5=S=JWekfMEa0lfu5MA_m>(-lK=EZS5|M*_%&~0N_lND8U|s zO|hL1CwBD6;TZf`4bW(Nm*Gr2{{Qsd1;ARBV9+`pE@;FHCilb!nk4Gnk@`kcJb=v^Lqec zKY9cSph5*p_^&Kh&I|zrP5|NRZdv@Ywy9w-~k0z1baF`b4@(vhQIK39MO05NI=sY0d{K%r8FN|p|&Ml~xftxg!Aa6Vv~ zl0rZa;IS#bY$m(9K%iz;k{1EZ zuxBT>8TWbT2v)%m4lIF+0CxP59x@lh0plTOz}TOX0fQ!S2rOR$uxbAx-sb)zoNrhN z;GF`gz)mOuAlBGbQT+Yf7{Hk{Zpu_STfAt1TrQMahO7m&Vu=1lZ%LqjA6?lZ9S+GX z&=9Glp+Es*nGZwA^6p7dDs#m>4Y_$ThOd1doIU)tEej)2TF%FUfR$E(yXY;15@JJAR!-Ds~ADn5ZRhP z7Md=mvS=$*-M?=E7qOG3)J!1{2-c++Sa*M8q6A66eJxkN*G?2527}4_i>kk6Bt9ev zSOOuC!((~%*^JMjgjy32d4-S?+DRx%F8O%Rq;Qw` z;>%_*l+@MHP2=klsTl|V#L>kOqQ=xTQWj{6p%{QY!I(5fdX+p$`i150J8wisMtzolCBsC~Z%Bk+%p6ZXe|DR{Y>mwHI}R3#kzdkwh9u&@*8Pn$YFLBzKV^fX8Q4V zWmaR9U_caC3HS(-LERBi;T4%rmtlxJz^ed{7uSImfQTZK)(VgvJlPck&TNF3#z_DO zHF8EQr9d`e-hV0}r4^7K-7#JEnwn<-blv_?lKXEnPrfjCu1-_n%MLAW07!NKY+Ck6 zX^^`hiWSuXrBE9;B1@%K35GKv-sbclnK`9aQxS+xlncL3{~IYp2_BjqWl=?AdKIaV zQq`HSW_AbYEBM~+ z>Lm8UXdcfC07%&$0@z4LE?x{Bw^WVWhwDSugGWuaXZl4G&gFQDv$Duw$Z|P&j;1pu zoJw99j_F7pQoO5TGBB0b?27If*k(I^d;TXnemJapfQecZQfGP1=lSa!<2Z>35p!`OnDq_IPT=mSPmpQ~x54 z@(wpLd*V@5Ua!w{XPtffQoHFlW}Inld%~Tnsz7cU&fzR&J+rHy4et-*_63odyoBF% zKdG+Usc}80tb{$g)ps%noSmx~mU;W$BxePCY|G#L=8~oLAsP1lHhW)eXN(y8kLt zHlY>qWi+A|*|Uss8?mIZtWN%gjF48E+DIT$!^s^$)mf-)9Zk z&+%&L>1@~UE$7YYuXU&{@qp1IYYs)a@3Ypcy_ z=TZl}^=`exX)d=%%c_mi^-05dD5m1VjV|x+d=hlZ=4;#7nbA$5*2%Q78WpJtke2%rD#otM6gq=5`nQuIG)fbLso{BMY5=R`W(smEEg+ zJ+0ii@q9-#tL8e{XA&uD60D5;~6R^1t02bvB7_dZ<%*@i~D`|zUJUIgEVUBE*w0d4XUG3 zBu$HemYQN}0W~FM6)l-9&92I_wxYV$Vn@4xwvwu@nueN^qGGCwx@ucxZ?Utav|4~b zs>9rN?0dL>=69fDAa~&RLMzQHHFbK*R(Kgby7))ZGgzOYL?P=Sn0*?Gyok;$JZ40q zf19mCu3D}63c*h8N$*IY9tZ)qP@If6$ zw^4wAVti(8a$PASUsJ9u1QJVFqBz@uCxF~REc-?dL?SVlzPL#PD_6bWR3zNrz*c6D z+q0nWZS*kaswn98<2^Ded?6&0g%Guz(~ymF3B>{htA<6q=7chWAm^6t5t&JoK~kS2 zieOp0BAR>&O+(ZL~Bifrv%Y6{wl3R-$9D^h9&ssX~t9)bq(t-XNnX?E+|wP}OT zAdj7PPBuOI^`S~%46L*ODF%cXBd9`uzL+R+Y&OM$kufaZQ3VK& zS6(7>9=Ot{d z)9+z7CF-ZB#|Zfq^XYmyeTM1Eo;HNW0n2oNu~VTxA)Jcp(XmR)suIgG)0K_2rPao2 zXQ!dZRZm!1QZ_^?xe9gvo27Fsd?`byK!r%ZUPC*z{|0I^8?BrLeas7NH0Lv8pn&n{ zBmjGeSM9Io>$`uw*lpI<N_Z&gyNv8ON=t28GNps%a9b|Bw|>jou!waRzC9^DiDFVEZ$*E^V&+3_rAbNV5+qjwB+^4c&Bzh6;t!q*1tK$v6cTaaM|B=LYQ*sO z#DnymqZm5w)U?o0vu9*w_R`bQ(`mLYY6JL>B1Rg-XV5}CwRPy);lpTh;0JvQR2WEi zCAKKqL<+}dB^}8Y7V)ZNsSRg3yyA^&zO12KsrU(&pxJ&*XE^Axrgub5CR`{mG}zOO zjg*4hgNA~Fgx}r9O&(Ke5-i9;b$2ejpsMo-i~&MSq*l%1 z)5+zMkWQX|x`Mv8wy`QSYLDu{5M$;_KxPLlV(}=AK9H&mIZPhWPh@|A1N990Oy_2@ zpAi`wA-X^J&(q7>=jVgP0|$<^9N&QODr>%Nu2i>?|p0YX2;wte_%UQ?x8CouD6~ zS~$Jol8g@{NX{KOY@Oy81H6l-pAVevxNs!(D?}JFPMt6% z0~Pj}0#tO1W|Pv=)>Ktd&09LZfoQcdlnYl*$62Xrv@*!VY4;Omo}So}1~6ekIwS+B4M{tQN|fkQ?oXsJDM3z5!pzkvM&ubA z7$CSOj*<-Vq)7^zLX3q3kPbkWM@6C~h8-X$kmfgtQi8|SCPEt+rV*d^#hN?mpeBbOGj6Ayx#D)d#ilxwq|FvuqLr4qcSI~>mg#%fPvf3{j(SXSCrQ!P`x5l>yU zo|%z*f_{8@etLd>+B`>1#lgr&!^*_MK0`;#&Pv71!c5W7$V+kI+@|ef^;tTI)Jo=4 z(pK42*_LlCEw0RMceRyM)e^M|QzwjxIyC8K<`(`XtDvB>v!t#j3nHwdMR*`#5~IRy z=t!T2G+az1B5OS~sYq zYH2UeYc4G+Z7A)vRy0;q=oO_%wmZY+;l(0B`ckFegBhu>V>)^Q>z0iiK4(@(Q9DH` ze@-+s{4NY^v4fk6zc?99-Rd);N!m+>`^Q65vN{H+*UX?oI<{ou%u}a`RV5x%H*?TLm6mjjluUGlgoqY7?3%ZDeAD^$HuOk%fsRkCfXO{xM~lc z7)ruGi+<1AFYD`O=();bpQ5AJL_NX9jhMi92#GMD7LpgbXAYDg3*m*$pi7M?RjAOc zN}hBOAs$x}Vd_z!MY?NKW+g|hYx9NCszSLN#!3w8JGnbkGULXI0|VNNlif>*6)iM3 zGIZ59TeHw@!Ni&mqQj6Hx)TY=7h8w~j>;T8g9r++0T&>$sMJg+RXi0VHPlknQMBbt zl&Dm26#L`GkQc&1gzldqxf7+pl^Rxnz(y?+1&9paF>=w6HLd7tw$jpJ4vf%cslh;t zb&cFKegrlE@QQ{dA{8OxWCu$+C4*YlXhQw6;sY65c-V?U7F%{8g-Fq%GH~K#%gTcS z^M(0rSTb{Mmui%|KIbbhiB*~Sj=-H>0gOC!D*gdP&f?h-;6#fVs|Zn>%FSPR03p7W8aZq*Uc`qkhETx6ilH@jBt^2v_waDcP$E3@ zN17UrSB3Z>VhfWei!evQ0JDf=KX?`Yd3B#gj_l+$?PET)-^|v3RITIPe*#H@Oq3~I zgk(c!vPtwT6tO?CuFW1me2_qkBQ{P#2u=3R6%nA~fWebIV)(FDpp~NvC_{d5=Rkx7 z7edsdLVzd}sxYQVh%R+N6O;~QhX}c`%^Ny_bN2?+BMB5u$4Q()b##bTk^1i{Z&d(b z3<73hB-{vJ2v}#CFmB`o37b2WFed7iph26E%uq-#9U+b|CqQxD$N|hE$9ohTh&Q6_ z7b_Z)vEaxe$)F*MkNzY^#9vR_KnH;n|K9g7Tu{L>a=_$~s}-V}bbTEkF`T!73>(IT zF^v{1g@{HIr!|v|ubn%4Q{N^Go4SJ!`To@>2_8Wja9_ud{5*U>F=vg5bzo#0Efy}6 zm_HF=OT0p>0adp%=%y z4<*vz4y2@_nHdq}CdFKjku-WD(HO|7jW)Yq-yy?Ryk|`laN7||Tu@F8;|c^R zGmd{hTKHi6-_!bMA!I$583!IbS<*QqTvm+nC{WwP@I{3L0R}{;&#(PB`fEs1ggc6( z6kxkh0tg3jqSSye;wX|v0hN+Wh|FIC99}yz-7?M9L-UE2yfdUPvhac7SJ|hT7&aD1|Bv;Jr=!Xzw zUNSW*Q;T+L(eb#6He7-ssqlFs14*+W_%Kl1+|_;PHeMX6lwKWlV8Y%B)Os~x8~-(Y z>5+f~4X$*_k03*~2m}VEls*j;>_W6;<2yn#DN7C)r(_qF=p@%#Dp^>p=)tutU(P<$I1Ml?s1Bx`d+2*<<;B2BM9EdcQp zV$j+tq$pHI`WSiy3CY9}W5~5L)FfW{74d;P5gaLndw>c@C}{u_d^$Q<1MLCyJVA#n zh(P{rJwMbZdL((`voVxWq^LrPBnefB6mbxV4pCCLNV%E-2q#1k`05}MWWNW41X~_C z2$Uq)1dwD&hNJ{Mk#60dMGH8uZQIlpFf_avMBpeG=!gW6G*W~F7v6ISk_A()963aT z0!>0iKG^(mq?3~hRe}sz3N?r!1&W6doKAl+IU|_491Uc&e=t`FB2Qr4fiqiF>L?+Z z_|mUA5mrz|5xBfF&#X+Sp`<4X8uUmK#{gNdb%9VV8XyrNHh2kPBI0EN0&vh_xW00n z&q3Ol8DogdvhJvVX5vf;4hdqE`;h(uT-1zB8+U;xyl3F93RP@Dm^P2Ss53?Wx;J3h zalHXVHZ)xJtVskX$-rTKKWgL#4k0BH5)4{`99jOjlP0cf*~t0hJ7JikPl`aa5)fC6d5HL5NJ41Kt#fv7iTa z22A;)vSE84E=))-;0AXhG^tSF@stf6xuzAH1`Qk6teKlA5MqO5+LAf_*6u9y$&Mx^!T%c?}$W*!r*uf2fp! zevSBgNQD`~6iN{wiFl<4cP^wht!%h{bM@AA8CQz9Y*)Mfvsdtbj}d>r{UHamOUa{q zOlvgo!?@%*gvk=a$}B;;U>MMC6l_Z#rQjrzAmA1*L6QcD#~$0VnM;wF=wAf}%-B$i z27MAN*w7%sIP^vSbx;3Jdk`S7Bsm6&=jyx(a6cN9NfD(O?UF@{h0b(@!4SR&L zB?-C-!z2pZglKiF!ff>J&?!LWwv8LOwyB$jE?hkH@QpLrcS1*j8;CSg&U}9oSdyWB zfr&+Duy)vtcv|#H6VZ-J?HD3(Dv5;POQnNB!E9g?N@V$^rG3hUA{hk!bs;LCQv9be zBgk;x{RCmeVvGe4-jFf}nD-e$@6&ruA;57V8l{0c2u>8b7rUVk5=*X%al?c_j-ju-R}q z1RAz1NURZ5Xm@Q4ICs*6hjj`@!gGY*paBVn{Wt=@tA~OvNZof)R&69_&dgwxfXfLW z;0N%*2MLnMLP!~8-awND3z+CxiLwG1dFJzdvcz&=WZ<8{^JbJ;i4p*C4kWICfLdW| zV0zrxFaoZRpaXLfs9K^NkU8)OWXlQveQ2H%V03>i3oe+jPN4ox`*+Z(vVvSyEM&7F zzIyv{4T7=ouOLD{gv#9iw9xx6BVYV;vUOk#;7gc(eRqk0@?62?8`Ln8@Kia$&Ix_} z@h^z}8mD$|ysU|QU?3PIY?v4HLbNP*lxV0zSoP$w6EN^VEUWHb3Ujz&BX>{*aPZ5m zaN)vm#hVO{q)R>nh6D#j$v|18b3i^wsiBiZ?PqN)jyPrl6Oo;U1$Q1Kn9xANWrRbA zLO6ALb4qIg{(+*RbNrb6Q>k%6e{(i?BwIQ?0)zqhNT7Jf2m;n1l|=e|=mYddA+|6P z#@JZAJ2GT-7#vIp1|&hBZ~}jQ4wwm&!F|_G!ks4|A6-Rh`BDdke&oVfMKs_t&ztfvVM!R5^pL>)R$$xm@NK;1!>b?>as;Rkg;8XX z9#9itHBcZagZ&hNO&K)!mq7=Oa^XmF5!z^kbdr5Rf;Q2d%Kg%%{SQTYBunzpZ7$GN zwAX@BkMx!`Tc`YQVC$_LU@eHkS^Z|7K*4@^@+}(ZZ*oqO2&{U5AOQ%6e>A}?A_V(@ zjtKCvnT-9t3TcKkNWgObOrQnP_ksJo=iugmVuV(}5k;wT%VtnipqQ|7bI)S}yMiud zNjQ9IBY=VPuskZ7`!E>ztPM)IVGamlASGM{0G3r#uiyEnRT!4%LZ%79+YE@pxEL8= zD3xPOOArBO+>Yg793q&=&(W3~AT4sG{UqgdQulKP>!xDGn#<+3MwNNgxgt42ER~Y5 zknxuTBMY%I@=&lo#hpA&Ho68zU(Tum0s z(}+-D>JUMKF1H6N^w=-Uj0rZa`I&SA%aWB5h~MggpTU{bifhC zBc+?HjXHDXD`b+83<(LuD^3zOQ?FXeHOv{e3D$EkSjH_DU^|gwOykaB`Endwq}rdF zZrmk8nE4{Q06T$luLz8tGU8OU|Lr*1_+oJ+ba%1t|r_H<+0o}Ioem^LJ*3~ zvP^h$g__uY!fTV7dUl~4Tq}oxb#Fx`o_@?UQ#|`}Cio7-Ci&_~(?}97el@KmW?jEb zF(wkAsFo75G;!^WisV&~ZqND0`@s@dxtwn=mkUfdLTe7x zZ@zTw*uH{oo36Q2Evht#skgqGe((8(2QPb_Lz9<1-L}Mp*btJtu@A_fA$I_)cEmE! zM4^tj@;{U`6MCIq``VnSTDZnoS~p#!G<3P5(7T!2$;365)3D`U*rm`>>u*|Iwej4XRbAiAv9$Q^usl3|y3XO_DpwXiK?y5k(ak-aMT zYGL}+tg)8~dNu`|3!jc5cOq)lba{)oSN;!2ydGtgH!qPq+P?AYJG1GJXo-?A`K}+tUd~Iu%3%jbOJBnST9PF4N>$y0CK>qBMTvxK3l* zv|%V3hgShwXzp_qxnSK=;8G~ZuJU&9AGH)pgh-_jpf0vhI6a|AoWH6~lzFTaAt5jk zI3y+tN)%QjN%q0@#pF%hqf=7&D9*W{hsu*c4TBBJ~f> zABz?<#4SY%a1oLC1PMx&G;rQPg2=o9Qeykc)kfX_K(LAqh`@CD4`u>DoYnzB zAuuQf zkD~cVL7Z8F?L0|6a#>Q2HZ26bM8?n(Fhin!k`>OAC{6{Sg0|(BvdNO!-%_zyNO7JZ z*_Ux1X>d9dCIEt#eu{=E$fJmmxmC<2e-a3|h+GITZ_8ABIS%gm`FXqZaiPNr-}jx+ zGpEn7smx8^+}hG~0>gaMF3L{CoooD;p032Z#OBcL)5OUbp?jfAg$Px9ijCjFwMO(Gv6& zwP_eV#+ZHJL;}T%hdHjZfrhAwiHVDgtd5VckF2bV%Rf;K$pA8Pamutvf!*;>#mi{N z7!ejBT60+iP&cfaE5NZZZNwI5Nt&5zg~Y&-23+Y@Z@zi&N)fBXgCualYmL=L`DFHn5k>-gm^A4o|LgHCJi$KF$x9Gr3u7wL9myc>ghR^K}yST?m}YFT;;+M3z`Ow=GNij?s!K-~U!_hbd#WkRv*^1LX@g?IZA%^n^dRD8Lk z5J1q}(ait7@HQd)tkr3Y+J6j+pVc|*^|`)m&g>Q}BqTT#_tM$>-ZF$T4+M4?rjV)S z6wS_1Q_xXPi~3Ojh9*VEH(Bul+G6+u$$M#V)*$V7lu z8l7&h=gDHn-v8z_(dlyj?uVe+Kl^RPN6XS`@c7&PIS56lsHY~VDQGDdQBhJ;)soT7 zS2DHr>p)~=g3+cY5x5uh!YQb#%5BUoZ7b=fgD@$BtJB9Ct(maT#=<^7KRvx_^Zxgl z(`)y>80>iC2RgaE>Db|BVQT{d9J-AQN&>AT1-|)Dzz6WCZ9F9MKP(i=L8Dl?fQpKg zMxc#JPAjLQ^_L9iY$o8yMh!A>@N~3xHg+$+bA4;Mg6QlLwD9u!`V1Q*D>XeugHd&3 z$P{oZ6SOm!lrYaP!XoKBG_(ruDDsPb>e1fV7kfyf6#@5ng zZ?oC!F#8(_`g(P4W?%c4dai;5mmBSAV|sKry*zBc7jv~nr^E5L5Qd)(yw&Fld+Jx@ zW^U~4EW~dnFb6TzKmx0Qd!7h%3U^3RQC?|fy|LZvvzGf428YYlZYS<#<>MlnAT~&N zC>S?opsSk>mdEanU42f}e)x1ouifo=bQ*Je#cl_zM`nf=&4$8+gb_p?UwEDk?WwJ( zv)Aac6^q;ThUJ%-@XGe z^?L1pze}umi~d6`KS`U|Q%jLa^k|d^(oa@gVzOFq)}Q8y<9HuN&hq>YGR9o(Y^&?) z=q3!vgU(Ouhq_t#xj9*RnYrQbhJJj6k3KIh7PQpr-eTtHXlCW)=V-Kc3Q#)lMo%Z8 zCi@B3*zI;7<`>HHd|n^MVsSPbTHvw`a~Wm;>T22Tc=>kTAT!VbAjkWpN`C09YzD)6l^9Opf(&%;Xa2Kw~#RtG}4&0%V!-yy9 zgseCHjw|YVe^=7<+%FG&x?Ee>PnLEB77BgeH$S4%2z7Vbci=7IMl12}dRBa_?7dAb zy$xrr-8=_b(BxB#Xf4-&#?fQ{Q(qT|#qqf8_5W5Qy?rxBjZ1i^kcU1$P@4=~3;Xar znnErTuGDN{QPy{1cOkA?u?zo@N(ewOXJLvg5_1|h=Qym3~&Ydz% z!@9Z%^a~I_0rZu9my?2cnr{}P5=wPUe985h8Mh;xO0wJ+-29%Hg*=_dR z9hAlOJeQck-*Mlc4|P?AoTobIdps98rP;y5>h`(UnAtKQkJp*=yB}x#M`b^r4{^WoJcjVfD#09?2UD)Czw}Os4qdMUOzZ0#8kqRrKd$_8 zR_zqyXJ|Ty%>Vo-1X3!UrLo!W{M;{$!QpTA-OVS2wWa+hphpCw=Jq=DxM}mVF8iE# z&V~EhI_UDRvzxeb-b6J`%}Vf`x`{7>$QMx8TJb**GsD~2*lajGM}19gNM=^5%rwTl z%GF||osEWpf7-&!&eF-hX}7Z(KX##ohMDCws4U;fC-4-gXe>9{oaG9CxmjqDKb#M4o*%^CVI$GDa`E6RaRnydR6kZt9?8_nvjaNZcZNl;X z8jI_kl#1v63L(=cASI(r@dmAVpINHmaym-a({7ehe(rU$ee`Ek-#VY7(R26TLlc(LRo=g;It=YZCPR~Za z>h$vQ7&T#=zMN77C~aeC)vlneSo6Jyl)FUJOQ_N-65=_8mjEr;-s^ zKEuSuzUg$?Kdde zE^LiB($Iz}iP*n>U*=ht-kTpb_IqpDrTlqcAFm$5f9?2@td%KIy_jTkvEJF;E&hkr ze7&bg9_JXt7vnT z8wO{yirUKmx?)Cs4CC?hTJsn`HH1=U0FB8HT@$h1KqOa5>+&)WBgC zYfmttaGKU;z2|gZK)X^>6PD-GDmad?cD=5V(b|!OM0t^s$IeOPG!#@+^uu1ai?tS~ z-M8Jg53hOlELSQ*+Mh9a_^Pwj%CeTMX0!1#eqb3QqTA_Wl|peC!L;FA=4)>3Y2n)a zHd%7j<^`_s>FqOk%{V7>$;+WPJ;>QP`nuD#iFI|U`NsGAo(mOHZ9nyj28$NC&%+#Azn5+gJ`IiR&RZ(TN-EH+ZbVF(xQR^Vx zLv`_7tlsYCn(wE=&+C5lI8wF4>+|EoyR-I5CL5cwAt{_li_Q$;>#;SPlT}4+rPYeh zvA&@2-`9bIeXV*O}-G>fY=p;5QJyIe`i8E32(m+X`yRDw~}SH}M#;1m&IW>ZAXV4>ojYtyIOn zp~2_%c?NgMZ~@_>Bqz4Am1g5-QgtG{q?!uboz9BZ?rK}&u6NaH(uDDAr;qgzqlgZRfXpDoX(oVy%g4{x-`CG1)4mhtA@g?drROw>)C#Eo zR@PS4l9Sh1Y;5hQ6eu34uPTq5)dMNf8VXxco4EIxW@F{#=rf~3GbS=RP_woySTl0~ zv7#gyL^aDjFE6Vip`@)`t*ca>JpSuFgiOb8#yqZr9ZYx6UDeS>#m30Y(9zLN$;Qag zrsc{k97fQ{+IiT-ORH>b0+X8>IxB6TYIS0;5-93wTZ8doh9wc1Pc?KM+Ht3tkA{#v ze^^NJiH8(0YG`I>rZc=H15;&~R8Cx26`cO1sI9cPHm6vSL{7@ifJRFM7cGPbg@P(G z;5nXuiJ7YAQ{dm^`HQ%C7~m-xsG(G9XD6SeuC1-Csh&SlX>z+R-(%vq=p?0{I*o!j z3NE*HpU0AYQ!5iIORH9yXuyB!J8@NuC9UL<5V{yXqN2jKtgO7uyriW3i_yxQVllL^ zRV>Js2tJfaMEg&U*4&0$Qt_MHpWdE+ud(7qI7c@f6*EiQju2tNF@kuao}8lAYGX@I zS-$0JuM;DRSGBgP+ZQ`>j!49A%gPyuH}kMGbo2#$99$@PaQL|%T)dtR96*#jx*!>y zqOL2iuC%M9pP{d`wz6A3gvqEah=)lBwhm!1$kw$NABycL_@z|nDRH3Sh<43dX*YHZ zxgbFG4e=fzeNb}VDRG&q73S8gvf6y*>+BL3R#UaJos{k3aX^dsI4!Ua zel~TscQrQAh5RYBFc~q2ABG^4d^GNy>L-O%Z3O#W=!IH8<6Fp0NF|d)(#j`tRo+2Mb@Dp_@hwn;nN}{W?#B6tu3f(^YCt zSl*^y0kt`YyRjN3cSbS_+C&g^k@Bhk7K>-m*K0pP`@R-0Zx1i9z|`<|C0w^6>>cJv$mKp4BYd7C5Mc>P>$VO z!PrfF)YGwtM<$M}!Of`+uGZG3E6?|Fl2?>yH=akw)dX7%h1=5rFY4_9 zC3F@(llJe49shX-M)$*R-QLgr~^UylGA>qg=_lY%0h_Y9KZJ4-@JRN zXJU4GKvGtZhfR^@*ltPPqS&#dHJV*`;+&3h9~B%R`bxs z4|^vXD|f-R4}_~kEs>G>x%t5{@S zTXRvO=iK?5lS?!G@BHM)|MKq_Zuj<2%+5@Yjx7gRVy17&oDk;7n2AsnXR*0FfmkdQ zLh@ChgQB^xq;dCw!$+Tc<>fmV`>f_5 zKetfJbuL?Cd@&CTcmhcV6Y_I(pG6^8<`oqc=4&+hb-NBf{@k&bzWVyfCpU{sh5$}l zFMa>7{`#-q{N|f)e)I2s@%Ha7^vwntdF6R>E|Vk85u=+U%Mc+ah9~CjfjFHMNz}!a z)eS8zP4zWZHI-EjTlPHm^s~>r@a3wYD?zrrWXs+?JKDDH*tL68O}>z%r=rO; zsVJ;&Xm8)K_vo?XFYGJ`n}+)bhxs2*+S00qy4u?MmMvRaHnnZvT$Gsj#W%n6?Vp^xf{Ma8gNdaW z97SboOJk{qHW(+L;A&)WynKZ9=pSz5*AiemKT+jmgK3#Jd%Yc0_ZW43#!N@ zjU`d#pu2JTRZXp}n_4%wwbWMDwQSPH7k=^9cfS4IpS*Xe-xOiwmE32F z^#@~KZcb?fx@px~ox@Di*(zyM!{UrFxO}M`(*4@EL{X$Js@k;o@uN@eRW1Mc+u!~6-~47^1utqn^!S&a+M$hE4IXAeZGELIYM!`p zqi@3CPVuz0&F%GC+`l-iU-pwqrHC1ITFe%U+3Jqc1sYwx9J9~PuK1!!pbum6Wx2&wwN0BKFV`49`oXu}dh=WVJVsMi?LPA4i%)fwlGZVZLl{rQ z^i1?$zcn>)Pl;7UZ4Wdwkvzq6;+@ExYy|+)POy>CXgHglj)D^kq*#zE z#{+Jc+aK^a9G+m36o^Dz0(GMiK&L71EKaD@S$H}c ziqmLx96X8NVKExbHoL?>q8^vcWOn$%7`ii@o0qE;a2T`{dXyT+NYu0`m7@n0LdZFa zYg%{KYg4^HhY5V=+u!?HkDJ}_@E4zd>R`Q&ur%lU8E_fs3_5?glq;D^}Fq^ zaEcVmm0|%Ha9&=HOdycv6je30G-$%}=f3;qH{W{myFYk)o?E*6#g|@vc2@x>JUojyS?|x^o8OzBh5`!8-MX9-s&Grv-Cb_xb$9m{`_Myotm`xR2qXuN=HM1FdXevI*lW6qUDy3A&WzmS4Xwc`65>b}}mChFM zIW&A0PiC-%lANNNnhLRd_Pg`%UU>JyCzl7kij4=JdF6#iS~b+LX%RNUh~B{Mi=;B? zcoNT6l{Ph0=F52uQq;b(Fh9Snw+9JyKKwALh(jZ1LN2G%;}6D?I6RTY73CBZ73Io> zd_J3)4uNUH+hqwfzDNQdj!vf{?MW6-q*QB5>RM_GIhJ3}ojZH>?1i5O%+#6#&%XNV zQ=0$|Syt8zz^8yzIvptQ?Eq`%=2z6!mE?+OnXpqoJ32fxId6!8?-a@83Yn0a4myzR zb|8`@P>|P_iiI2|IQ10x^e|Rb9E_z=Zi&ei2>Cn~m4MRETs}Ceg3_k-8>@JR-#$8X z?(De>Uvx|c<=aj?`|9yM)q<#LdSDdD5m4>LRj^N^(dtTZRaK2OfNa3rVjjRWdaEmh zWAd;pGnd1Luo4YMQaC&TsALL9W^mXnB#MigG-Ba^HxNnSAVfZpGv@KROb7-%nNlS~ zDId9}sJ5YA8}Iz|o%8R&uW#A0m`h{oqZ`LMt(<6Puz*?8|W={eKkBg@WEM;M3 zT|;eU0pMAD#ARI58EhduM=F;~_-qChpA5U5t^g{)jYi@L#LpQhU&x>mGYO>Doh48> zvK&pGCPyU)<6)5SI3kV3;c+-zJ~(kL_^#>-Wq9Np1nvMM+?*p;Zhh>zrypvRX51?y zgTpfhU-W^*ZVHv36WO_y_06?qd2#_O8?w(%&FS1gifLT2OsNoZDA`0Dg`-$3p334W zG&++5VUB~8$caRJHl0XncmM3LZJ-UYiAuYzyL9 zB4t5od0CNK%%xL^|67ZfLD^si7uJmd0Gdg|XOgi5GGG!~HI$4e<56z_9!w14NO5Up zbv@Y5ruB8Xc;j#9-#UHn^t+$_vBE6g@$8GIoC8OamD)j)+MW%CjEI4k- z2&!Iz-@=nRh!_$Ry#? z2~;5v4*7k-a13?g(z$>aMej$dNJ$ zVTXQdXlQa3Or8oqR3;U0=wz(W8x`dM7w`u{aU6vSJQ*yQO`{O9$q0tvQ2jHUaw;l- zq99O;ItKU>xe_U?t8?>2xP|MV{o@=!s}HXY*hCH69((T5_9{+5H+JvF^?SWzvujRY zDCBn;S62+aI9py=Szf5*k>U=+RM)+pk>wzmrC6!S1+XUOv9WFhK9@nvCegt{KpFaK3wW(1-+*iX`bd6;0cA@7i2n zq!!^kD^tT0tM(`f=Hh$OHhtg1sG z;4&!$ctMnxl`Ay`g@yUK+QNKA+Hm#VGiT16K70PsSX|M3;+4O?ezHxT)lZ;3nO}8B zGXx3^po%}7qALoj8d@4E3S`uPb#bt#yMJsIkPMl@pb&A{Ogs|Caq z;5?{EVgcgMQAjvcR6d913&3iKM0_@cGJpYYcOZdU9+0qfA{z1`4V*NI4d^FVTU6b= zp;_kX{qXGhx8J__-Q5Lx&7K!teEHR9*71_IrCF3rx4VO}u-CS{w4`^Xg*oMo>szYx z7@44XacXpQ#hPLXgd935Zb?F{$dc)NiIC5x6LFZ&14|JI1$-7IlSpK;saPl!&k~s& zHi9FlT=9XLyD+!7dj00MO0Mzh=Wm~f|9sVF#Fw?7eBtT#Dn-gPKeuT1g~0zud@JL_ zlPiv3l9|7L>+aTKLBh5)hpzFK!G_6nl(vGX?ev5bL|A(!1(%A4LXkw4#1zO?N*SMq zPsU@gKk&n;Bmf>VF_VlZF;R6FU#ux>Ue_qLcD;T2^x1PCTpmeq8;(5j(#c0Q@I1>? zla3^g$>q>eUj5?O=;)*_A}Bfd%=6D3+o+;vJZm#Ei#CX25D61*!>ZBdk7cNQk$^)< zc#I1ZGdee(DN^KWazsqj#);}wk#h$7C6XyD_!Dh$dDF%n`&)}s-JhMgc}*jW)lyBrm(%@&3j4{&{0?!4M#b zRHbFPqO99u@g|8BVkTtKEzL~MuGsuZvZM@-Mu~tFaQXuAkk^Xr!|QQ6-GMYmQBYZ3 zRUoEjA}*6bH@~n50n7)+2GoF`lV4F=-%wjoQmD;Ut7L!?6jW>f?;m~s!<|`YiXUe{3&O;?px*A&ZG zWE`vrhlvlHC-43C#TJv2iokE$9qw?7 zCaY}Q_0VG{pE%xLrHXaEclO+wcfY*75N0Y%N{ZE7GKom#$dqaYD{GwSxPIkY7p!2G zDVFev>7dseNRl{UmTXeeYnY#&*E^#OSzcj*Ru0%U6Z1Qq{uo&xRccD=THCg5+qrXF zeG$(;clVEfe)ZiiHwV}JI4U4O;7V~EmC5GPNom(YZ^y0M_xpi-29sITw(A2AN);*8 z8o7|33A-#Nt2;{KNt7B?iUp1WU@in+u$AU*`wt%8(^@VkI}P)r1Kr)-y?p~iW8+iv zdYdna`By4A8xNqeP>ap!!-^mQPM}x_=1#{TIfuzWb+*7RX%Lh6Xgm#z2ObANF+#Eo z7GEq;C>3%kpFxPYfb_duPOAac%goI!t%8$jwTZD4PLJ=6w45p z5aC38Ci3z?p=m6LE<7H3v`m=`fT^scFi$1nAz^cBHelAxFX;?cAa^$46b{qc(z4EI zv)RlBy>3~zX7vJoMiHwbi(~yu7SiHJD8B!YEC*umDdq zS)6E|iOZ&+9Uq-svd8g!bwO!ad2t>v2NqAREvu}puc@l2C@U_6z+YTd35StMOozQL zw+FV&??&kz-OAG9{M_8aicYsUGd?;t0}n>G0B~H9e@H%P#DYLhK(`-xMddYhjjbDj z%oiyDXUOtOs%k3A3)LcO#%EkXpBV^+eJ_D(ty_ z^~&wefhnEY?)ErshL!o5>DlEqyFZ2r<0GHJR~J{;x2$VzZmg?sXx^}XT}x9#eRX+Z zu9QPVP!jMHz$c>t4Z<;)ZD?SQ#cH!yOlB*Xhs)vcph|WCckxt)K&CP{BAHSt171O< zafHgE`t>^w96ZpzrJ*cW3Xq0ONXPw7u%$JF$p(MjGe=|f1gCmfta*N8VY8%&W+Olout{t0OtILY=HA;jQfGC9mfb&om)?r*2 z?(MvD`_}!wvFSx4oVS3-W?G(}nx0u)GdX=hI1p4OM<5i-a)5?!+Ol=a#+GJuJ=IiK zSyonFUZmvU!(N9ORk@)$Nux=>x&)gJR;5R^-gX;U78)32g{=ui6X2qW$n+5?Le+lt zHL!YW_}`UH>$hy#x_!&0x&k31<5hoQiZ0lw6=NuM)>C4d-v|%v3b+FrkVmFlMoFCd~iDTYX+lnZ4sV4w=g#~ zJ_dY!d1Ym3ZhROk{2UlxFbDB;ktV;e08E_&a8#9F)w+4tzJm`P-nXTtsi_4aL@Ad} zh`LQn3v+W*lM`dZLqntE<72}E{rv;*52~;j8J~g0FgpE_6vE9SRMnSTP*GQ3-_X*w zeb0dd``dTzKvf4h@NTJCED{6&V>2!d-umOxuYcYJ9ykYQQolSq2!8f%SO3t|qQ!+u z;nOrg$Up+}%WIoj)~|2dvUAT~_|UfXt?OEVNo$n?1{oK#>sJ6V%*;$pjEzr>4fb?( z4-5?r^mcXk508!x4~TbtB!^uU@@*_uk$6UERIiU0wI@-0kQcnw(uSnk)`4c!x39nV{@vTRZ{NG$+1+u! zvv&}cGa+CGVMC2; z08r;Ahq^nvdwTo(x;yW8bar-j^$(7Wj*gB`FIYU`6k^~)xk{zhXtWhgn|46_dFar= z-P_tWwKmsbcMl9IG2sV_0Upt6M2>rUbf~ARv#X<{qqA>xatcgsSr0DJ?X+1;76^G1 zzN)Bo=YfZhKk?+#Pdt2h@3xJbx9!-ruC83Gkcv4}JdhTw@>91sKRZ4Mj^+A|+o)O< z-fMUejAU$P$!PZmFx)Iv=4i|7TiU>4_Z>O*=+OhackSA_d0l-)5u8>LFpG37=&`NM zPmT@^!cKO=QgwFRy?OoC-HwiXH?I8t+aLD^=S{v0L!vIPYie#p)eP%4?>uz;*_U2? z=82P!KX#~nBakemRLEnIfm>P);Edt(x_Xeg4)pc)blyi5q@CS;L*sJ>cPLGyW9J=c z6dcfo^;_Ey9yxOOz^<)u_BX7nt3Y>lbg&4!*=#f;6)}T;b#Z!Pbhy9!?zKzbegFNX zU$5TmnOJd0(sflL;93n8%1Fw1M9= z0abNaj4RUyCYgk34q#_|XG9 zVdYyP)>M}ir~ogpAb2HW0grKh3Ql+Dy}PKn1N91Y^bQVL&uIEKYr|?gNF~cw>4GfA@x{t%&VUn?Cb08AD&t=*&Q~+;&}JX%fJ8p z%b)O41E{fOYJSCN^#U1YigNM_OG-*gE5ZG&+qiA_o&$#u9{>-uYjab1zMMlxSA}Rg zn%ik!Tb`R3?(4jFucPDsy*qbq-|4v5)d?PGdf8}m2cn5A0ez@aTMBEoYyY96aGIWa z^6_IwA3D6Jt*J;xNG1|lq)9=eQt%0%X>O?N)*rwB_WPBax9{Eq-_vpb{{61r;qgTa z;A1?IOlR=`G=SHrY}vl|=*bt~ICbjf=N>lyYAh(cJ=b*-~PODyK7(serp8v1Fh&So?w~^oKsy? zQ3dd3)3!bPkq0__Xzz|K8(SM{%QPZ3oleDLC>kK$%KY^B2v#f(d)m|8+0_G{vah$N ze{_1)5sYR?Ob#DwrV|Opa!pZ1^R8pZpLzC~CmwzHaQmhTF(Zp)5~A2>uaMWgFf%bc z*xNfWG(6PbaqGs_KYqLPjD>o!@0x@r6Fy$22*I@G>> z>())J4Hfwi1i1`KHVpI{Al=gB=orM4u6sAGBeC(T*YEWX4TE{i0XYTGW3)JfNfJ|9 zSi5Q8!{8d90>1L_L;Krz?%LbFb8|~=X|5EgaylMIpK1e`Fg4tH^YYI>|NPtKt2b`k zx!=_VzPr1BWNyXmiD!unJ_K}CULl0K*0!Aoj-3GeIQiJIBm1|v))%WKzyiqVt}zmX zP&_*^HZs(Ue1A_5I;K56_iz37!;imQx!u2D4wFTN$DNz-W*uk`u#gU6(;o`QQe=)2 z-NOMSTE7V)tUdepZr{9R+m`k96 z8Q6kpWgJf2)UqRzCWCX-=AnLz@}|u@4ALhYsxnh_Gwh z#-?fr!o;}WVl-NvfFs>VqRcQm)`MEGZr;3p?Iz$1)a%nVI}}uHQl2M+%@F|?1R5OiyDWyaRo&9;%rwM--kZOH>$rL6_RWr- zfr$m3!Q%9W5(F9#5KvJ?O>JFmO?6Fu^Op7lM~^@A+%rdatuGNHArnlGBAW`B7A8<+ z8XlXTUs+k48Mt@*HtL(`=h2}+USnq83GZ|NeFhD(z*@1 z4n2yZ<(73D+O}?MTh~|%tVYJjCPN;G`~d6MmKSEhUHA6(_COfu?CnDa(%CyUHNUXD zW^sj51UesLIgsd`haWk9;;}<}!SMI*+tFMF1}5PWBOYLF0Usc5cr*Rt>?pXP`*&{L zyxR?r>>C^&pI^1u9AIC-8L3n{M-F6TUE8kx2M-v=F%*KvCLm@)w7hu}bzWyDyq0 zb7k7{DujX`I{NTK?d$4mDyr-1Dhg1eA%~s~Sq&Bk@Em|z=C%3pfjhUrpN`BdTb&Mv z(*v=V#1SYpDiMpA#?eG2TOWSm)a$37KTt@<1$|zBBtxW96Xx-|V1a$BQI0gPNWlv0 z#`=2h_Y6<#{qZn-=fbijl%h+trFAW^47II04!`*Nsn_0US4O&i{OF_4f9aVt#M5!> z+=9+S&2QXx?4doI^0IUNcW&J8A08eW8Sd{JoLuv#IokTBy0TmWgT__Y?0V|8*WY;k z$!gZ!x8HyJ<6rxSW{rNgVPtT`sMz-EU;pFfhc@sQfBo#j*-tKYF98>H2ctNatf+F` z*3FH%l(5H}$!$6C@++^t_G&A!?}Lxt`SiDbW1Lx7qGmP&(PQCfkk=Fdc{Vy+^ zJNM0vX}!*13#M5>D>TK`O|^w0LLx=WZP|O|$roOI_0@xl=>5;$|FQ!-fB#GCX2TFJ652!Z)`k0yKdHh?`c%A(Pkwk$|2} zlT|GTjvagC_|s24tTA7E=iPTd{POzXh=o&JTVJkL6m59q>Eqjr1<{53KYaAw#gG43 z%Fvj60Y}Iuc!1btg*hsbL{|U!ORu~D*mVzI_ubp)-+J%M+w&0$H>bF&P$p26Z{4x2 zN$Kslbm8K~ci#Q{)?8LukSAl13CTbp8q2VSA|8vul9z7SzVF!MCr{MHdp|#W=G^;N zr_DimW81+)O?kORb-VWOXjYR91HXU#&Ie!rIT;Za)|KZ-Bz$t(@9@M4BpQ**uh{eC z-%kC<)4RFdU;X{e|9$JrZcny!Tl;~1+X`hOaq;H;JKA!6Qv-i|`T3V$-Ll4VwjA2q z)>_dJ#b+OXa%<8~%dM)Ytt!sp5~8szOI=#ux@mLU_TBrBJ+e-0`RUx*i(lWJF}N~( zN#*9ITyn%47ne8Gi1jzW|M=oZUww6{b2ZG?XjBqf*za-raWrXpby-%&I#iX+P|SYIY%MS>(*O=FWXusYg%zrX9&t-+bK1YfC;P$NFKBTC}r z6qc6Otw(dCckgMyOFLePns4Q6+STU?UEiF#6Go{Eci zjCI`Yn_Qfoomp|DARcN2R6HI}LDfOIg;lLvcOH1;(Kd;%|Jx70zdd93*v#;al3b;j zj>8KJ%gQvFIRGWSs1#N=uXCknA|5dr3Z{q@4v)i^NR^sgl}xHstHfE?(t^%~qcQ0T zo7w11lURHaUnmuG$-bH1uHG4IC=ri_qge`_k_ovz{s_8I0W+267L+w^*|o7Cv3%#- z-|mf=1Hj4*_9#Oj;PZHSfKkdd8QuK&ygeFChHYyme}*Fw(Mj1Pj>=%svhgIGcy&R3 zNpX>k;-Bs59n%FOQJ;Bn(cr}rd?Xr+MM~S2<`+#t91$Dbfa5B26mm>3fz4vDMatp^ z)El{>j%ORbb9>kxOr>m-qy3{gXE+jz#3(YAk{vcok4-Px{4VR-{EEe&p`%u0)Yl6f zMWigO-F)o%r%&w6Pb?4I9a!~qRXLiHnoXOlafaxZCKGQoBT1@WSNRj$7gUvmKd>we4sCdbnD&Q11oWgef0Zt=ia?C>5eBU zz`N=yv=VAM77IBorsajzHG?yVuE%9kfkaca_0X|nj~;*WrPp6*WG?^k>GxgptK)aS z`{DMG9Y^JB%Iepzt57jgKGdRj_s{Qt`1$Ic;k6V;qRG=LM9T8!_QU&j@7e$8lP|w= zxIW$c(=T_8A=_fdy-`Dm%o9qLxw$zac=x5b`Psf}zkT!JSC?)M&l&v$u1LTj5Scl3 z8+UBq_sGkC`P-|zl=^`=H-Tun^}&T-#>1lAg8WK&xAx}pqSA7R8VenN{&^D!&aJ*> zYbfOPd5P+(9Y>BHJNDEI&pll3?)m23Te_g(`n&&l?}{--=PR`pz%DCFD_gehtd>M) zd#>N@=^q>!oA;#BnPi5hYTXSOvVGsf&%O5CUfJ4>Pp{6J*RH>R_QD6hkL!Y*g7Q*S z7N{&NFOnq;Gu@pd^G3VXV(}z#G_EA4pmE#YhhF&WfBgG@zTTd1=^izv(`z^1K6Cc$ zj~(;Q5T4Fr^JJCVj~s2yqd2EWX6B{`J8$3W9GNx-Gs2Q}I}bee^eb=t?ce|N)Namv z|7=#m^Ip05)`fre&aV0y+KN&cgQ{xWw5c#XJJ8)bxv(%jHqhHMHof9XkhxNkSXtBl z?7#iT|MTjJ7JA4_l;$u@9bbL;;m=(&27fx64ceR;VO4Xr!nZiT=uA_x{948TtzrTO+>&``|$6+;_tQH?$Rb5v|@!35Yj*!O$ zf9dnaiTvDLm53+Rls4@=uxID`3U$`$iVHP@%b=l2SAywyTH4Kb#}XHNoNhx zMG85Ol1Zj00*N{|N2M-q+5PCNZ#=m}YFzPfE9(lPBiFwDrGIf%ubb|@eQzNvuWZPt z1lJ5%wn|C}>gK@x#Blhe%VzbFing~O+}>JOs$dZ^oZJ!x&NwwV>CF<-VXxg{bwtQK z5j$a=U-1!{%$Q!McP6PUlxsBTttq;qq`JOw-IlhdLPiG9Vx&A%J^kbGnr4$>b#|Z%vBebXk|1ULtVRN@4>yBTUy&TR!SKRiI5Unnw(rTuFVeK z>lmDz8lPTtXEQO6Vbx%Dm=sVORUsT8&CuR?NeDZ4nZk znsk~B7K>qVUgz-HS7)Y|d;~(uy|xU?jnd8dWH>{Sl+`sgHa0Z3R7=Q${Muqx(rNU@ zNjS^g_=3Y_F$GCfa@1wEC1`vBF5n9$lR>-P7Yez(VP;-sU3mc_Lj}C7J1drpQ--A# zODGw2tS;$MvcQ+d<3bjbE0RvdU3!ZbClGKrLYaU`r2+UYEG{gpXx_X|g^S{qrE0Qi ze$APp$4v8s)62T0#Wjo5vN$!m;7!7=TJ`oQj>*J9G-hx`xy8kW1(kKJ+xG5g%}wI? zdHL*^Wz`m^3HhWXL=4l)!rI#E%wSh9I{5CTnVA)LmO>1g^tLdO#pZGtOtH4Cs-_As zA3d+Wr9s6erAa*Stt1?hO~jE%8NYdX*_t8YW44tAqbH3|1>APKC!ES)!+402qT(EO zCgP1y5&Mcb3bfJh%27E73iCIyr!Q{(OY2-}66Nus& zECw#(bwqIN95Fe#yyS>dSg=+GPlUi?(ed#RZ~*umMm7>jF~o8yBbz|ib4)rJmx@JG zcmkCTT$4l~;LcynA8s*mrg<~#e@kH5HP3|Vm6&2F{MhS1WB&Y zNHLGi>Q6&ZMCzERl8F=x0P$vWnbdSN62(Tfhl1f~GMh<+{9Z4n6&ntQA}LIx5(#u7 zg(N(lNM&%i59UbINjMydE8sEk@kok5rIM0C;9Lo$Ng9oYP;w^`4SUhp45!P1Qo>mp zUxrFJIY|EmjSXhFxkK4|yF{l+E+FU1$n~&*MbOMp#}ok--zO zsmw?&2VRE7Ll;XT0iR7rqC;c~m4=k{6Q~jqNeP9c(MS-`r45zL!2<$bw-br>xO_qM z{XDTuDi&~AOd6`yWAj9qFcK^eCZ32Y&rsDZm;tGr3If!N06aB0eBmU4ib_4%RD3exf1sNb3P&R$ zv<2Qk7?q^J-={nfvn10PY#yc?!a)gRBpySdvyn;`157so-;qYIp5&@4ib_hr<3cJ8h$D^SD zrX3c+WT4R^p_(XIOW55=C=i0xBceM{0fytbNRvmv<1k5?L=+osjrKc($ups1N;Gmg zm57Gmkx`VDkA_3ooNT01PJSScoJnHGn@B>PRSZjlvZQ`Amp4CY^507D|)3n6wy=k0qFeJPtVO zYz9w5wVZi*IYN5YXE9h@;W*M8N1g*-7W?8<%nxT^6&{`yaz^Xz}u?$Bb z771Cnh})!FHM@fG42cC>%Z8tatdBt>XJY}6-Tt8S0cp>(nN$L71QJdHA1#ooH0m6= zn31&U7UuPcEea$u5fA06qH!FYMk+4uwdmn2d&3#vk0Ks}Oi0IoZKjdfJ5swv6`4FZ zF|<_By0&I=hhdLpYK>aP2Zw_dInXFbz{ll={igCHQW2Yik9!?9J8FSJ4jIlb8-fNY z>9qo=wz@;Gp<=mA%%#Eu@KjXNhfe`K_J?9XCluQJJcW=!f+a}d!9|d;(~K$fF&MHlOrBUOhf6yjILqRyQSpw#yAnGuA@Z~f@5}{kiWiqo#3RKxh^p`s z=!r;z(B*WYLLvts47c5CHrvr1Z5BtOqirK+60sP%*kGe82;2ktGb9^;G}s_sA}u!X zMO^UQ;H2Oe=BAJf7J=VmQ&JJf+UlCc6OI6=0!IVT!R~PS5wc1~f&o;coJ93e2&=_m zgRK^uJCwu`$qb~62G0=kkjLW)BoMZ^3^H0N2C5Z-pUhrbTd5P8E0tb~$TBAq9eYqWV9l~S%$D!|3CAB0Fk1_E`ONau?r zuxlh-Dqzt~jt-4X&aPTrF1y8KG+AtRryEJJBUwckFh+3KxJ)dB9vzJ1Nhp=9#B>A8 zDk`gMn_4$*Uf)n6Lv0U4OeGwxV+J6A+iF-_S)84onwptkTwX@AUv#UhI+PBWT{POm zM2fPnl?o5TMg6)%#AIpAWFu^0*(WCmZNE~#r- zx4yZtrmVQ6tf8%a-@zUAxkSHZvH#AWf8H9LUom@NO$o_}*R(P{KGfIKJBYQ8$1||H z@u1scFxrDzhD4R4QYyd!r2Q5>IAXmufMbZ%dD^@JSe2T(<_)_ZdGXZ0{pByGUTnj! z{_^(Q=Ptba^$&k`Pg??b7Mq&%=vRz>995{!D=aL`*Gd@)%kt#d=;+wQ^xUc$)*qnG z198bjmdaPcqNo)zDJ*+IX?aCe{ieN7ocimVe|hzd$7(Y0@Qd*LcQ4&rap4pVyB>Ms zkt6%}96a{uvrin|UMa~7R> z6At6*^1`Cd8Rz7+9(n1_zx?IRH{N)zjx%-P!i9I<{^-)J!MOmDU$%MYwv9Dal?|Ks zAAM|BnbbXY^UCkvfArSH^Y49nxp!v8ih_}c8JLv=~UU zG_{-eKlRF+r%t{3=BYPcY+#L^zi|HTbLY>0bZsU?QaA3{xurH=qpjM!Z{NNeIoUqm z@yBOp&Ypkw!gqrvPHBC8ZE;0K?S`HE4)0xGSClIiGonj9S1#S@ne}9tlG4qOz3{ic z{`GHv{mUD#KC^{v`0~Hczjgln#S8C$+Z$Eyf9>@*UVG)}?k#QGc5kf}2bR08{`vd& zAD_E$@%?KCQvSLfJGX6Zf9TPZ5AUuPgba&QQzIjtH?Q0n(x;e3O|1=$8xNj%;V-Yh z^2~{2j~w2k4Bz-aXaDEiTj$^Tvdfjb;px|3eeI=}4y-RKDl4xo7si%ue*gKW@4t2S z+&f>5Wr`ku?bPcp9NoIHsZ_;DxR$3zM+UoYUcU6(?S%k4x9Pw`#||IZwS7x-RZYY0 zC*M5v>JfGF*MFS(pVNOoeWBBx-~Q~IufF-pn@4K7S(ZXuUP$+i-~9aj_dmXP_TneE zW=U<&zy9X4Tg&nwPEeE9IaI9F-*x?$?|!^8PReV4_03byJ+^n}x-tnlm0%P$wjVii zbaz8`^1J`{zy8nZGgmCEZBPH_U;lRM&BqG~^F4htCJ&jB_AC$H{Oz~zzWeFMG^PHb zH(q)Dx!q;Vv}>v7#`QbXhByRj>*Cz9Gb7*n@UyR;JiM_~PKg?3CKr8@^^cr-{gtQo z=Q-}Z|6l+0UuV8w+=I`Q(G zPqxRxuHMLBsI1q2KML~t@gRk?2O;YSYbYAPTOefr=3_wRpy|IcNd zyrS*M6DN;tVENW2=1~ru5Ydg^zjFEVZ{PoXW1LvncJjGr4mYYe1ayZxqqBGtWFYrz zlOv<^mMD%PkmZ&&)Rt;^q!iG6d3jY`U44CBb-BcN>-~S6KJ(7cOIcCHmV-x+9o&8zy>f3zmz1qPa^lGYjk%-{D!G^k9GN7sIHXj-YPAQl43Q!? zzqq6%PtL`mrZPN*DMFntxp{?o%E;JHZ@qo?oqzTkqr!^qdk-Jmu}({IEiSCsLWzKF zd2(WA2~edwE2!H2``1VR}G8jrGL(bXGTX(w#CMG5p zYza=;=Js9d3VGyIz^0oW9vGU|dqP2<9cZ~XmL>v`WTM7F20jkKjK)_MXjQ18vZkq_ zxv^Fdoc#I23m4x1?t14kw|?ux&p&Z&vz8LDF3*h*Uj6=yFD~8g9UPf9N4Sl9AAV%t z#!_+CXIh%*>*^huGow*2{y0@yTwYP65ObJRQa0=Z$W3OVVZ2NZs#44=DXp$kkygIG z@XmYheSKrdC1}}q^4TXIZe;q6Q}YhL&otI^>)O4}{-HTbM6mAgiI<-}yfL2&sC;^8 zV0hZ#4FxK-$3W4kwYLo5dKSRwLZh!O9#jk$(@Z?H0CM24NR5sO`lB)W;TEf8B=RbVslPk0Cw77Enp(h^S)hcpZ+-aFs z=dwxBA#k>4t!F1OhF-+AY~FK+3j>kqu}(#tPCyHl8OdvHu{aQNo8AARxt zpPhD+eA|haUwq~9T{ZlKVNGwcS}j(a4Ukqa!^|nEtkej}QDEqvU@)2jl!Q+KU8YEi zs~Z|BVAF(((x&a(H*-6`c2cq+6MrLOP98t3h6I!{*kSQZ<`|1Ew4RTA9u!!zrq;xNX;tZR_hw z^E3brO7cYu@1H(%=Hiv%aM`v;pE>oy$s_9+2E9AM*JS2?JbU)^+0U*n(`t7<@yZL& z9BS4`C>fxGfEFe@JA3-4Tp2?&) zzQXYNxr=ZAI-#eQwrtvU;NYR7J7stZO{CQXyFNR8{>;}mCW2gTS?k8OjT;-v3l(e< zA>lU8ci+5zqhr*WVCI(=Yvo)zgTtUw@#uyD7)m6RB8qA@v~6svEY8!YH42rwxU?XO zI)g4v+qre?T5D@->bCCGQfL&0h->=e;<*bKzP@6>3v+S{%PY&8)^BL3$>mbxwzZ|n z{(+Iv zTv}bEkZTH?cW)^Y%OrAfcJ8NlF1-Emt$AOToK6t=>gu+wTQ}4x8ELO|d7`hktAAp} z;ZJ6%JefkF5b~G|GVm3b6)C7AH>WABEGyQ)AIU@#MQ&+r0gR5fmDsLEk5_-b%D>}<&90N!X{ThtkC z9)B=Fk!ZA9IiHrr8hbcmiC7GDo5m0oHny~FY-vU+CJpN}i=Uo7|LH)GnOXefe?IEN zKlIXT&ulC#Yu$ZdOD%nEVr-

-OcJe!kv4X-v@A0I5J$zg#ARSFrWyV-Ihx(6Fg2 zIwi6;ap(H2j&WNkmIMk$=Zb-4@MuIlgD;hdg(4A$MJLmFQngkk63g=|np!qCm%2W9 z`>iXf!Xk$Gmyf<2p>BNs`P~X4t6=^1ovpX z+M>#ee{Sn~pprbPbLW>JPtgyoj+ls1He0d}drX z*f;JZvdG?vTQ@o^jGX#vIg^NIi;Ej-$|S^eDup9t;vVDD;)*LvW)M?hm(35H9ZzJ4 zRNA64twJJ}L$qwHD-ltEo>P&yxmd#TcYgADcQorB`S{G~^H-fTvh~`x-}a^(jva2H zx~3MXii#>h!aUS7Z>LB(H2=)t@N$&JBP`v$a(l*0=4(_UdX_F!=4piVWB_$VM7_ZH z4HjRT!r^nM#59g2FQ_U}Nf>NJVOe>;go!6{rFj}TA0jK&Kk@15Z|p@?l(A3#`@-*D zGAq#W?T44`qJjdJXR%vHkheA!3Q|U$KFpPfNgnSkIO`z;}mq`$`DBNs+wC%>4w1` zt5`Wox zvvcw!8Ha9h)fGvlLLR3loTaiL82X&PIF2RY(orS?-N6b4Jk(jspyI(X27w?l*i0IM z#1d#5TU*L=sPVP2g@AzL9=h@Ky*WFR799QY+iTM%91Ul`^V^jXN18~8o0fI9w6eTh z%S<@j)ST+pQUN|3p(qMVL@DcV|Jc0MWiu`=n!IUZHW6?+y@W@ocF=mJ6_g(VUx zax}RLAuSya$Kk-UXy_`B!Dch5bdFG|R`N53DJxsSh)(?e%OCgWLkazzAHM&+bJk?h zk6ruy#<(krC#U?@Rab(QmzN`AaV3RKZJX+I*bD|wEacHrfYn?+pVwtK8&S&;a+$$E zAP|aY2{aD6bCt-Dl(m$HZf?138Znd35~(x_2~VW4MRJ9NM`!V*xdjDz;UF-bypFiCiU3OUJ2LHU)H(`!tGcmWt>Z7Vlsw$K`4o_WD-B_6`W^-f! zJz>)le&EEBNYHK7udW$QW~K{E5!L6?gTCwajZ;^PRtryPRr{2%;fBnZf#|DdUo0D^mv_Civ`KK zI0KOsnS;u==uB)-1rkozz}KN%0UBG$<+8CW77~F-$fB7UXz(G0M554Xq)f&@=`p0=J8Zi|OM-n(ZJ`)f7U3Qxl zyQcAbTy{)V#D|1ITrP**YBrdx4kQ_iE|c+OI=Z5hp!X&{=mj$ugvLG#rj$atd@B8R41`5_I;3Vt5A9DaHf8XI zqmc|nsFDg;3?`kJOh9Z&_)JLf6)CD4omiqLjT$$S846ejk41>sj3z5e%LZZ)=Gi=v zLZiu1ViQ>eEK<_%Seu;~o19y-dLmgWU#e8eg-kLblT4+v>4?j`rnfmgPK(82_l437 zkxVXT!os3FDH_^{LqHstiiDFmSWToHra(OdVyOU}DuY2ud-X`Oe8ChVh}4B;W#uJW zF*D^btgILvZkKtsujBUpp|RNooyir!F_3i>^sV3sYVMZ1dEgj7)HBp+@CQ7;}M@}rnkF) zVs?7)`j7v-bZgS&bOTq}JvCW|joX>#Nuq&vlv$V6;98Tk+v zB?1H?#&#kD$t)A40WO^zDL?JzSzi`g20rG#LZj{2MqXCR!Ri`Cjf ztxC!yW??-j@LZu-Do1iU+y{9!iBciqB=sYAZVgQ7OslhlcP?MP)jvBwb@#`wKmO+S zVjxLj(wSUEL4j7rq)<4@g36Zaq8u)ZCz7jV0%|&yrV2G$m5`KhLu~Me!@%v$;FukL zcv&Q01UN~E#_b3s@MDpz5MKa6Jri<3G=Xmj0kt4f*ixm8M@*n`@+qVVk;S8QGfJUy zr8xzK0HWcAQ3gc7LDG5lwVBb8kcw1ED9mkWFp`q)1Y?LOABT(n( z$(YfVvAefA`et+n-PpZ9fBp4Z|IDh@X3OP-@GsjRLjE6h`g zSrmLS1OYdJBf*DB?bwP1XB$6P}7!DJ>QpoSLqvl?u@?=4y zmXKo#K5%*zDg(R!P-=4X^YhT)j2w+x$|Xd7E_X1IA+p5^b&f(v%|tv7FgZLS8;^t& zLuM@l≫4aAoh0*>xoU+7UI9(Kke~rXS`8o^ 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

8oPtwaPQWwi4{DL+(V5#D@e}p)XmwMif`pxQ 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%0Dk$t<&$LQT6%0t}G#;&~ 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 M4Zc9H`O1j?AI$LVc>n+a diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 70e09e727..d3d1d9e48 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -83,7 +83,6 @@ const char* aboutLine[]={ "Miker", "nicco1690", "NikonTeen", - "SnugglyValeria", "SuperJet Spade", "TheDuccinator", "theloredev", From 5aa287eeceee9fe361038ba2749764e1dc8383bf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 14:51:26 -0500 Subject: [PATCH 026/194] update format.md - CSM for all OPN chips soon --- papers/format.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/papers/format.md b/papers/format.md index 3e3583463..6281293ab 100644 --- a/papers/format.md +++ b/papers/format.md @@ -238,6 +238,10 @@ size | description | - 0xbe: YM2612 extra features - 7 channels | - 0xbf: T6W28 - 4 channels | - 0xc0: PCM DAC - 1 channel + | - 0xc1: YM2612 CSM - 10 channels + | - 0xc2: Neo Geo CSM (YM2610) - 18 channels + | - 0xc3: OPN CSM - 10 channels + | - 0xc4: PC-98 CSM - 20 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels From f6b45d3d9bee96d661fcfc5a19f63d21379bcbee Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 15:21:29 -0500 Subject: [PATCH 027/194] GUI: add Namco C163 chip name option --- CONTRIBUTING.md | 5 +++++ src/engine/engine.h | 1 + src/engine/sysDef.cpp | 17 ++++++++++++++--- src/gui/gui.h | 4 +++- src/gui/insEdit.cpp | 2 +- src/gui/settings.cpp | 15 ++++++++++++++- 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97efdf8cd..51903d8b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,11 @@ the coding style is described here: - don't use `auto` unless needed. - use `String` for `std::string` (this is typedef'd in ta-utils.h). - prefer using operator for String (std::string) comparisons (a==""). +- if you have to work with C strings, only use safe C string operations: + - snprintf + - strncpy + - strncat + - any other operation which specifies a limit some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style. diff --git a/src/engine/engine.h b/src/engine/engine.h index 4bf4cb65d..896c083a2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -355,6 +355,7 @@ class DivEngine { short vibTable[64]; int reversePitchTable[4096]; int pitchTable[4096]; + char c163NameCS[1024]; int midiBaseChan; bool midiPoly; size_t midiAgeCounter; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index a5b689b2b..25e35227f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -54,7 +54,7 @@ std::vector& DivEngine::getPossibleInsTypes() { return possibleInsTypes; } -// TODO: rewrite this function (again). it's an unreliable mess. +// TODO: deprecate when I add "system name" field in the file. String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { switch (song.systemLen) { case 0: @@ -177,7 +177,9 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { return "Famicom Disk System"; } if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_N163) { - return "Famicom + Namco C163"; + String ret="Famicom + "; + ret+=getConfString("c163Name","Namco C163"); + return ret; } if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_MMC5) { return "Famicom + MMC5"; @@ -205,7 +207,11 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { String ret=""; for (int i=0; i0) ret+=" + "; - ret+=getSystemName(song.system[i]); + if (song.system[i]==DIV_SYSTEM_N163) { + ret+=getConfString("c163Name","Namco C163"); + } else { + ret+=getSystemName(song.system[i]); + } } return ret; @@ -213,6 +219,11 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { const char* DivEngine::getSystemName(DivSystem sys) { if (sysDefs[sys]==NULL) return "Unknown"; + if (sys==DIV_SYSTEM_N163) { + String c1=getConfString("c163Name","Namco C163"); + strncpy(c163NameCS,c1.c_str(),1023); + return c163NameCS; + } return sysDefs[sys]->name; } diff --git a/src/gui/gui.h b/src/gui/gui.h index 656b358e5..851e10aa1 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1099,6 +1099,7 @@ class FurnaceGUI { String audioDevice; String midiInDevice; String midiOutDevice; + String c163Name; std::vector initialSys; Settings(): @@ -1201,7 +1202,8 @@ class FurnaceGUI { patFontPath(""), audioDevice(""), midiInDevice(""), - midiOutDevice("") {} + midiOutDevice(""), + c163Name("") {} } settings; char finalLayoutPath[4096]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 18e286802..81626b777 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3063,7 +3063,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndDisabled(); ImGui::EndTabItem(); } - if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem(settings.c163Name.c_str())) { if (ImGui::InputInt("Waveform##WAVE",&ins->n163.wave,1,10)) { PARAMETER if (ins->n163.wave<0) ins->n163.wave=0; if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9984e4996..2d05ab5ac 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1156,6 +1156,12 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + ImGui::Text("N163/C163 chip name"); + ImGui::SameLine(); + ImGui::InputTextWithHint("##C163Name","Namco C163",&settings.c163Name); + + ImGui::Separator(); + bool insEditColorizeB=settings.insEditColorize; if (ImGui::Checkbox("Colorize instrument editor using instrument type",&insEditColorizeB)) { settings.insEditColorize=insEditColorizeB; @@ -1453,7 +1459,11 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL,"FM (OPL)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_FDS,"FDS"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_VBOY,"Virtual Boy"); - UI_COLOR_CONFIG(GUI_COLOR_INSTR_N163,"Namco 163"); + // special case + String c163Label=fmt::sprintf("%s##CC_GUI_COLOR_INSTR_N163",settings.c163Name); + if (ImGui::ColorEdit4(c163Label.c_str(),(float*)&uiColors[GUI_COLOR_INSTR_N163])) { + applyUISettings(false); + } UI_COLOR_CONFIG(GUI_COLOR_INSTR_SCC,"Konami SCC"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPZ,"FM (OPZ)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEY,"POKEY"); @@ -1972,6 +1982,8 @@ void FurnaceGUI::syncSettings() { settings.audioDevice=e->getConfString("audioDevice",""); settings.midiInDevice=e->getConfString("midiInDevice",""); settings.midiOutDevice=e->getConfString("midiOutDevice",""); + // I'm sorry, but the C163 education program has failed... + settings.c163Name=e->getConfString("c163Name","Namco C163"); settings.audioQuality=e->getConfInt("audioQuality",0); settings.audioBufSize=e->getConfInt("audioBufSize",1024); settings.audioRate=e->getConfInt("audioRate",44100); @@ -2194,6 +2206,7 @@ void FurnaceGUI::commitSettings() { e->setConf("audioDevice",settings.audioDevice); e->setConf("midiInDevice",settings.midiInDevice); e->setConf("midiOutDevice",settings.midiOutDevice); + e->setConf("c163Name",settings.c163Name); e->setConf("audioQuality",settings.audioQuality); e->setConf("audioBufSize",settings.audioBufSize); e->setConf("audioRate",settings.audioRate); From e295cea2384ec7e5c0b389d1e5d13b25d0617144 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 15:28:24 -0500 Subject: [PATCH 028/194] whoops --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b75db0d05..1a2d88a7d 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - SID (6581/8580) used in Commodore 64 - Mikey used in Atari Lynx - ZX Spectrum beeper (SFX-like engine) + - Commodore PET - TIA used in Atari 2600 - Game Boy - modern/fantasy: From a137eefd209add604bf57986dfb8808b3d6ec6d1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Jul 2022 19:00:32 -0500 Subject: [PATCH 029/194] GUI: refine the Namco [C]163 chip name option --- src/gui/dataList.cpp | 1 + src/gui/insEdit.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 62b93479b..3a536951f 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -184,6 +184,7 @@ void FurnaceGUI::drawInsList() { if (i>=0) { DivInstrument* ins=e->song.ins[i]; insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]; + if (ins->type==DIV_INS_N163) insType=settings.c163Name.c_str(); switch (ins->type) { case DIV_INS_FM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 81626b777..25ee330b6 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1416,7 +1416,7 @@ void FurnaceGUI::drawInsEdit() { ins->type=(DivInstrumentType)insType; } */ - if (ImGui::BeginCombo("##Type",insTypes[insType])) { + if (ImGui::BeginCombo("##Type",insType==DIV_INS_N163?settings.c163Name.c_str():insTypes[insType])) { std::vector insTypeList; if (settings.displayAllInsTypes) { for (int i=0; insTypes[i]; i++) { @@ -1426,7 +1426,7 @@ void FurnaceGUI::drawInsEdit() { insTypeList=e->getPossibleInsTypes(); } for (DivInstrumentType i: insTypeList) { - if (ImGui::Selectable(insTypes[i],insType==i)) { + if (ImGui::Selectable(i==DIV_INS_N163?settings.c163Name.c_str():insTypes[i],insType==i)) { ins->type=i; // reset macro zoom From 5127d5ef180aa53dc33df6fac9a7b6dba0c91c25 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 22 Jul 2022 13:36:42 +0900 Subject: [PATCH 030/194] Implement sample loop end position, enum-ise sample depth (#557) TODO: new sample format --- src/engine/engine.cpp | 7 +- src/engine/fileOps.cpp | 34 ++-- src/engine/platform/amiga.cpp | 10 +- src/engine/platform/genesis.cpp | 30 ++-- src/engine/platform/lynx.cpp | 10 +- src/engine/platform/mmc5.cpp | 11 +- src/engine/platform/nes.cpp | 11 +- src/engine/platform/opl.cpp | 4 +- src/engine/platform/pce.cpp | 10 +- src/engine/platform/qsound.cpp | 2 +- src/engine/platform/rf5c68.cpp | 4 +- src/engine/platform/segapcm.cpp | 14 +- src/engine/platform/su.cpp | 4 +- src/engine/platform/swan.cpp | 10 +- src/engine/platform/vera.cpp | 16 +- src/engine/platform/vrc6.cpp | 12 +- src/engine/platform/ym2608.cpp | 4 +- src/engine/platform/ym2610.cpp | 4 +- src/engine/platform/ym2610b.cpp | 4 +- src/engine/platform/ymz280b.cpp | 44 ++--- src/engine/playback.cpp | 10 +- src/engine/sample.cpp | 287 ++++++++++++++++++++------------ src/engine/sample.h | 57 ++++++- src/engine/vgmOps.cpp | 14 +- src/gui/debugWindow.cpp | 13 +- src/gui/doAction.cpp | 37 ++-- src/gui/guiConst.cpp | 2 +- src/gui/guiConst.h | 2 +- src/gui/sampleEdit.cpp | 100 +++++++---- 29 files changed, 461 insertions(+), 306 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9b3fa06fd..659ea6b9a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2226,7 +2226,7 @@ int DivEngine::addSampleFromFile(const char* path) { sample->rate=33144; sample->centerRate=33144; - sample->depth=1; + sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM; sample->init(len*8); if (fread(sample->dataDPCM,1,len,f)==0) { @@ -2301,9 +2301,9 @@ int DivEngine::addSampleFromFile(const char* path) { int index=0; if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { - sample->depth=8; + sample->depth=DIV_SAMPLE_DEPTH_8BIT; } else { - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } sample->init(si.frames); if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { @@ -2358,6 +2358,7 @@ int DivEngine::addSampleFromFile(const char* path) { if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD) { sample->loopStart=inst.loops[0].start; + sample->loopEnd=inst.loops[0].end; if(inst.loops[0].end < (unsigned int)sampleCount) sampleCount=inst.loops[0].end; } diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 3aa432fdb..40932a378 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -799,17 +799,17 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { - sample->depth=reader.readC(); - if (sample->depth!=8 && sample->depth!=16) { + sample->depth=(DivSampleDepth)reader.readC(); + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { logW("%d: sample depth is wrong! (%d)",i,sample->depth); - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } } else { if (ds.version>0x08) { - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } else { // it appears samples were stored as ADPCM back then - sample->depth=3; + sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; } } if (length>0) { @@ -838,7 +838,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (k>=sample->samples) { break; } - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { float next=(float)(data[(unsigned int)j]-0x80)*mult; sample->data8[k++]=fmin(fmax(next,-128),127); } else { @@ -1631,7 +1631,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { logD("reading sample %d at %x...",i,samplePtr[i]); sample->name=reader.readString(); - sample->samples=reader.readI(); + sample->samples=sample->loopEnd=reader.readI(); sample->rate=reader.readI(); if (ds.version<58) { vol=reader.readS(); @@ -1639,7 +1639,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readI(); } - sample->depth=reader.readC(); + sample->depth=(DivSampleDepth)reader.readC(); // reserved reader.readC(); @@ -1657,6 +1657,13 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readI(); } +/* + if (ds.version>=100) { + sample->loopEnd=reader.readI(); + } else { + reader.readI(); + } +*/ if (ds.version>=58) { // modern sample sample->init(sample->samples); reader.read(sample->getCurBuf(),sample->getCurBufLen()); @@ -1670,9 +1677,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } // render data - if (sample->depth!=8 && sample->depth!=16) { + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { logW("%d: sample depth is wrong! (%d)",i,sample->depth); - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } sample->samples=(double)sample->samples/samplePitches[pitch]; sample->init(sample->samples); @@ -1683,7 +1690,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (k>=sample->samples) { break; } - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { float next=(float)(data[(unsigned int)j]-0x80)*mult; sample->data8[k++]=fmin(fmax(next,-128),127); } else { @@ -1877,7 +1884,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { logD("reading samples... (%d)",insCount); for (int i=0; idepth=8; + sample->depth=DIV_SAMPLE_DEPTH_8BIT; sample->name=reader.readString(22); logD("%d: %s",i+1,sample->name); int slen=((unsigned short)reader.readS_BE())*2; @@ -1897,8 +1904,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { loopLen=0; } if (loopLen>=2) { - if (loopEndloopStart=loopStart; + sample->loopEnd=loopEnd; } sample->init(slen); ds.sample.push_back(sample); @@ -3043,6 +3050,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(0); w->writeS(sample->centerRate); w->writeI(sample->loopStart); + //w->writeI(sample->loopEnd); w->write(sample->getCurBuf(),sample->getCurBufLen()); diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 65b44136f..1b0075307 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -114,12 +114,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le if (chan[i].audPossamples) { writeAudDat(s->data8[chan[i].audPos++]); } - if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].audPos=s->loopStart; - } else { - chan[i].sample=-1; - } + if (s->isLoopable() && chan[i].audPos>=MIN(131071,s->getEndPosition())) { + chan[i].audPos=s->loopStart; + } else if (chan[i].audPos>=MIN(131071,s->samples)) { + chan[i].sample=-1; } } else { chan[i].sample=-1; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index afd59b336..b6dbb8d12 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -153,14 +153,13 @@ void DivPlatformGenesis::processDAC() { if (chan[i].dacPeriod>=(chipClock/576)) { if (s->samples>0) { while (chan[i].dacPeriod>=(chipClock/576)) { - if (++chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[i].dacDirection) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - chan[i].dacPeriod=0; - break; - } + ++chan[i].dacPos; + if (!chan[i].dacDirection && (s->isLoopable() && chan[i].dacPos>=s->getEndPosition())) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + chan[i].dacPeriod=0; + break; } chan[i].dacPeriod-=(chipClock/576); } @@ -200,14 +199,13 @@ void DivPlatformGenesis::processDAC() { chan[5].dacReady=false; } } - if (++chan[5].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[5].dacDirection) { - chan[5].dacPos=s->loopStart; - } else { - chan[5].dacSample=-1; - if (parent->song.brokenDACMode) { - rWrite(0x2b,0); - } + chan[5].dacPos++; + if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=s->getEndPosition())) { + chan[5].dacPos=s->loopStart; + } else if (chan[5].dacPos>=s->samples) { + chan[5].dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); } } while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate; diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 3f5e92c03..7c349e5a5 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -158,12 +158,10 @@ void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len WRITE_OUTPUT(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7); } - if (chan[i].samplePos>=(int)s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].samplePos=s->loopStart; - } else { - chan[i].sample=-1; - } + if (s->isLoopable() && chan[i].samplePos>=(int)s->getEndPosition()) { + chan[i].samplePos=s->loopStart; + } else if (chan[i].samplePos>=(int)s->samples) { + chan[i].sample=-1; } } } diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index dc9e5abad..bafbda3fb 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -62,12 +62,11 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len if (!isMuted[2]) { rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80)); } - if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } + dacPos++; + if (s->isLoopable() && dacPos>=s->getEndPosition()) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; } dacPeriod-=rate; } else { diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index d801fe664..bd1be94a8 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -108,12 +108,11 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { rWrite(0x4011,next); \ } \ } \ - if (++dacPos>=s->samples) { \ - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \ - dacPos=s->loopStart; \ - } else { \ - dacSample=-1; \ - } \ + dacPos++; \ + if (s->isLoopable() && dacPos>=s->getEndPosition()) { \ + dacPos=s->loopStart; \ + } else if (dacPos>=s->samples) { \ + dacSample=-1; \ } \ dacPeriod-=rate; \ } else { \ diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 823e764bc..36dd2c66b 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -771,7 +771,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -807,7 +807,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat int freq=(65536.0*(double)s->rate)/(double)chipRateBase; immWrite(16,freq&0xff); immWrite(17,(freq>>8)&0xff); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 9e1302a0f..f1eb5a108 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -90,12 +90,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) chWrite(i,0x04,0xdf); chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3)); chan[i].dacPos++; - if (chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - } + if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; } chan[i].dacPeriod-=rate; } diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 6eb14be7f..faeaf6a9d 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -301,7 +301,7 @@ void DivPlatformQSound::tick(bool sysTick) { qsound_bank = 0x8000 | (s->offQSound >> 16); qsound_addr = s->offQSound & 0xffff; - int length = s->samples; + int length = s->getEndPosition(); if (length > 65536 - 16) { length = 65536 - 16; } diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 176b6e7c7..e4a39d44e 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -142,7 +142,7 @@ void DivPlatformRF5C68::tick(bool sysTick) { if (chan[i].audPos>0) { start=start+MIN(chan[i].audPos,s->length8); } - if (s->loopStart>=0) { + if (s->isLoopable()) { loop=start+s->loopStart; } start=MIN(start,getSampleMemCapacity()-31); @@ -393,7 +393,7 @@ void DivPlatformRF5C68::renderSamples() { size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; - int length=s->length8; + int length=s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT); int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length); if (actualLength>0) { s->offRF5C68=memPos; diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 71d45298a..d66fcce0b 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -56,12 +56,10 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR); } chan[i].pcm.pos+=chan[i].pcm.freq; - if (chan[i].pcm.pos>=(s->samples<<8)) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].pcm.pos=s->loopStart<<8; - } else { - chan[i].pcm.sample=-1; - } + if (s->isLoopable() && chan[i].pcm.pos>=(s->getEndPosition()<<8)) { + chan[i].pcm.pos=s->loopStart<<8; + } else if (chan[i].pcm.pos>=(s->samples<<8)) { + chan[i].pcm.sample=-1; } } else { oscBuf[i]->data[oscBuf[i]->needle++]=0; @@ -202,7 +200,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].macroInit(ins); if (dumpWrites) { // Sega PCM writes DivSample* s=parent->getSample(chan[c.chan].pcm.sample); - int actualLength=(int)s->length8; + int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)); if (actualLength>0xfeff) actualLength=0xfeff; addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); @@ -235,7 +233,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].furnacePCM=false; if (dumpWrites) { // Sega PCM writes DivSample* s=parent->getSample(chan[c.chan].pcm.sample); - int actualLength=(int)s->length8; + int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)); if (actualLength>65536) actualLength=65536; addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index f3be01a78..48d0ba9a1 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -213,7 +213,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); if (sample!=NULL) { - unsigned int sampleEnd=sample->offSU+sample->samples; + unsigned int sampleEnd=sample->offSU+(sample->getEndPosition()); unsigned int off=sample->offSU+chan[i].hasOffset; chan[i].hasOffset=0; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; @@ -221,7 +221,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chWrite(i,0x0b,off>>8); chWrite(i,0x0c,sampleEnd&0xff); chWrite(i,0x0d,sampleEnd>>8); - if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) { + if (sample->isLoopable()) { unsigned int sampleLoop=sample->offSU+sample->loopStart; if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1; chWrite(i,0x0e,sampleLoop&0xff); diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index b3e833198..b6da23271 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -83,12 +83,10 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len continue; } rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); - if (dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } + if (s->isLoopable() && dacPos>=s->getEndPosition()) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; } dacPeriod-=rate; } diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 092bbb6ac..6376cc19e 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -96,13 +96,11 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len rWritePCMData(tmp_r&0xff); } chan[16].pcm.pos++; - if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[16].pcm.pos=s->loopStart; - } else { - chan[16].pcm.sample=-1; - break; - } + if (s->isLoopable() && chan[16].pcm.pos>=s->getEndPosition()) { + chan[16].pcm.pos=s->loopStart; + } else if (chan[16].pcm.pos>=s->samples) { + chan[16].pcm.sample=-1; + break; } } } else { @@ -267,12 +265,12 @@ int DivPlatformVERA::dispatch(DivCommand c) { chan[16].pcm.pos=0; DivSample* s=parent->getSample(chan[16].pcm.sample); unsigned char ctrl=0x90|chan[16].vol; // always stereo - if (s->depth==16) { + if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { chan[16].pcm.depth16=true; ctrl|=0x20; } else { chan[16].pcm.depth16=false; - if (s->depth!=8) chan[16].pcm.sample=-1; + if (s->depth!=DIV_SAMPLE_DEPTH_8BIT) chan[16].pcm.sample=-1; } rWritePCMCtrl(ctrl); } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 88fcb37b4..8a34d9252 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -77,13 +77,11 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len chWrite(i,0,0x80|chan[i].dacOut); } chan[i].dacPos++; - if (chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - chWrite(i,0,0); - } + if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + chWrite(i,0,0); } chan[i].dacPeriod-=rate; } diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index b70efaf03..c3cf52a06 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -761,7 +761,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); - immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -796,7 +796,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); - immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); immWrite(0x109,freq&0xff); immWrite(0x10a,(freq>>8)&0xff); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 1a67c4288..4e01b005c 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -793,7 +793,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -828,7 +828,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); immWrite(0x19,freq&0xff); immWrite(0x1a,(freq>>8)&0xff); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 600c89fd3..1a1f7f0ae 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -775,7 +775,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -810,7 +810,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); immWrite(0x19,freq&0xff); immWrite(0x1a,(freq>>8)&0xff); diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index d8d98478d..87193da50 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -136,9 +136,9 @@ void DivPlatformYMZ280B::tick(bool sysTick) { DivSample* s=parent->getSample(chan[i].sample); unsigned char ctrl; switch (s->depth) { - case 3: ctrl=0x20; break; - case 8: ctrl=0x40; break; - case 16: ctrl=0x60; break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: ctrl=0x20; break; + case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x40; break; + case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x60; break; default: ctrl=0; } double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; @@ -146,40 +146,44 @@ void DivPlatformYMZ280B::tick(bool sysTick) { if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>511) chan[i].freq=511; // ADPCM has half the range - if (s->depth==3 && chan[i].freq>255) chan[i].freq=255; - ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8); + if (s->depth==DIV_SAMPLE_DEPTH_YMZ_ADPCM && chan[i].freq>255) chan[i].freq=255; + ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x10:0)|(chan[i].freq>>8); if (chan[i].keyOn) { unsigned int start=s->offYMZ280B; - unsigned int loop=0; + unsigned int loopStart=0; + unsigned int loopEnd=0; unsigned int end=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1); if (chan[i].audPos>0) { switch (s->depth) { - case 3: start+=chan[i].audPos/2; break; - case 8: start+=chan[i].audPos; break; - case 16: start+=chan[i].audPos*2; break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: start+=chan[i].audPos/2; break; + case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; break; + case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; break; + default: break; } start=MIN(start,end); } - if (s->loopStart>=0) { + if (s->isLoopable()) { switch (s->depth) { - case 3: loop=start+s->loopStart/2; break; - case 8: loop=start+s->loopStart; break; - case 16: loop=start+s->loopStart*2; break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=start+s->loopStart/2; loopEnd=start+s->loopEnd/2; break; + case DIV_SAMPLE_DEPTH_8BIT: loopStart=start+s->loopStart; loopEnd=start+s->loopEnd; break; + case DIV_SAMPLE_DEPTH_16BIT: loopStart=start+s->loopStart*2; loopEnd=start+s->loopEnd*2; break; + default: break; } - loop=MIN(loop,end); + loopEnd=MIN(loopEnd,end); + loopStart=MIN(loopStart,loopEnd); } rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first rWrite(0x20+i*4,(start>>16)&0xff); - rWrite(0x21+i*4,(loop>>16)&0xff); - rWrite(0x22+i*4,(end>>16)&0xff); + rWrite(0x21+i*4,(loopStart>>16)&0xff); + rWrite(0x22+i*4,(loopEnd>>16)&0xff); rWrite(0x23+i*4,(end>>16)&0xff); rWrite(0x40+i*4,(start>>8)&0xff); - rWrite(0x41+i*4,(loop>>8)&0xff); - rWrite(0x42+i*4,(end>>8)&0xff); + rWrite(0x41+i*4,(loopStart>>8)&0xff); + rWrite(0x42+i*4,(loopEnd>>8)&0xff); rWrite(0x43+i*4,(end>>8)&0xff); rWrite(0x60+i*4,start&0xff); - rWrite(0x61+i*4,loop&0xff); - rWrite(0x62+i*4,end&0xff); + rWrite(0x61+i*4,loopStart&0xff); + rWrite(0x62+i*4,loopEnd&0xff); rWrite(0x63+i*4,end&0xff); if (!chan[i].std.vol.had) { chan[i].outVol=chan[i].vol; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 329c5b54d..bff0c3c3f 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1204,17 +1204,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); samp_prevSample=samp_temp; - if (sPreview.pos>=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && (int)sPreview.pos>=s->loopStart) { + if (sPreview.pos>=s->getEndPosition() || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { + if (s->isLoopable() && (int)sPreview.pos>=s->loopStart) { sPreview.pos=s->loopStart; } } } - if (sPreview.pos>=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && (int)sPreview.pos>=s->loopStart) { + if (sPreview.pos>=s->getEndPosition() || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { + if (s->isLoopable() && (int)sPreview.pos>=s->loopStart) { sPreview.pos=s->loopStart; - } else { + } else if (sPreview.pos>=s->samples) { sPreview.sample=-1; } } diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 908b9a0d8..60eb35bb2 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -38,6 +38,65 @@ DivSampleHistory::~DivSampleHistory() { if (data!=NULL) delete[] data; } +bool DivSample::isLoopable() { + return (loopStart>=0 && loopStartloopStart && loopEnd<=(int)samples); +} + +unsigned int DivSample::getEndPosition(DivSampleDepth depth) { + int end=loopEnd; + unsigned int len=samples; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + end=(loopEnd+7)/8; + len=length1; + break; + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + end=(loopEnd+7)/8; + len=lengthDPCM; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + end=(loopEnd+1)/2; + len=lengthZ; + break; + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + end=(loopEnd+1)/2; + len=lengthQSoundA; + break; + case DIV_SAMPLE_DEPTH_ADPCM_A: + end=(loopEnd+1)/2; + len=lengthA; + break; + case DIV_SAMPLE_DEPTH_ADPCM_B: + end=(loopEnd+1)/2; + len=lengthB; + break; + case DIV_SAMPLE_DEPTH_8BIT: + end=loopEnd; + len=length8; + break; + case DIV_SAMPLE_DEPTH_BRR: + end=9*((loopEnd+15)/16); + len=lengthBRR; + break; + case DIV_SAMPLE_DEPTH_VOX: + end=(loopEnd+1)/2; + len=lengthVOX; + break; + case DIV_SAMPLE_DEPTH_16BIT: + end=loopEnd*2; + len=length16; + break; + default: + break; + } + return isLoopable()?end:len; +} + +void DivSample::setSampleCount(unsigned int count) { + samples=count; + if ((!isLoopable()) || loopEnd<0 || loopEnd>(int)samples) loopEnd=samples; +} + bool DivSample::save(const char* path) { #ifndef HAVE_SNDFILE logE("Furnace was not compiled with libsndfile!"); @@ -53,7 +112,7 @@ bool DivSample::save(const char* path) { si.channels=1; si.samplerate=rate; switch (depth) { - case 8: // 8-bit + case DIV_SAMPLE_DEPTH_8BIT: // 8-bit si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV; break; default: // 16-bit @@ -76,17 +135,17 @@ bool DivSample::save(const char* path) { inst.detune = 50 - (pitch % 100); inst.velocity_hi = 0x7f; inst.key_hi = 0x7f; - if(loopStart != -1) + if(isLoopable()) { inst.loop_count = 1; inst.loops[0].mode = SF_LOOP_FORWARD; inst.loops[0].start = loopStart; - inst.loops[0].end = samples; + inst.loops[0].end = loopEnd; } sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst)); switch (depth) { - case 8: { + case DIV_SAMPLE_DEPTH_8BIT: { // convert from signed to unsigned unsigned char* buf=new unsigned char[length8]; for (size_t i=0; isamples) end=samples; int count=samples-(end-begin); if (count<=0) return resize(0); - if (depth==8) { + if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; data8=NULL; - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); if (begin>0) { memcpy(data8,oldData8,begin); } @@ -234,13 +293,13 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; - } else if (depth==16) { + } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { short* oldData16=data16; data16=NULL; - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); if (begin>0) { memcpy(data16,oldData16,sizeof(short)*begin); } @@ -252,7 +311,7 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } return false; @@ -262,31 +321,31 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { int count=end-begin; if (count==0) return true; if (begin==0 && end==samples) return true; - if (depth==8) { + if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; data8=NULL; - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); memcpy(data8,oldData8+begin,count); delete[] oldData8; } else { // do nothing return true; } - samples=count; + setSampleCount(count); return true; - } else if (depth==16) { + } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { short* oldData16=data16; data16=NULL; - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); memcpy(data16,&(oldData16[begin]),sizeof(short)*count); delete[] oldData16; } else { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } return false; @@ -294,11 +353,11 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { bool DivSample::insert(unsigned int pos, unsigned int length) { unsigned int count=samples+length; - if (depth==8) { + if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; data8=NULL; - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); if (pos>0) { memcpy(data8,oldData8,pos); } @@ -307,15 +366,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } delete[] oldData8; } else { - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); } - samples=count; + setSampleCount(count); return true; - } else if (depth==16) { + } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { short* oldData16=data16; data16=NULL; - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); if (pos>0) { memcpy(data16,oldData16,sizeof(short)*pos); } @@ -324,9 +383,9 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } delete[] oldData16; } else { - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); } - samples=count; + setSampleCount(count); return true; } return false; @@ -337,15 +396,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { int finalCount=(double)samples*(r/(double)rate); \ signed char* oldData8=data8; \ short* oldData16=data16; \ - if (depth==16) { \ + if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ if (data16!=NULL) { \ data16=NULL; \ - initInternal(16,finalCount); \ + initInternal(DIV_SAMPLE_DEPTH_16BIT,finalCount); \ } \ - } else if (depth==8) { \ + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \ if (data8!=NULL) { \ data8=NULL; \ - initInternal(8,finalCount); \ + initInternal(DIV_SAMPLE_DEPTH_8BIT,finalCount); \ } \ } else { \ return false; \ @@ -353,19 +412,20 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { #define RESAMPLE_END \ if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ + if (loopEnd>=0) loopEnd=(double)loopEnd*(r/(double)rate); \ centerRate=(int)((double)centerRate*(r/(double)rate)); \ rate=r; \ samples=finalCount; \ - if (depth==16) { \ + if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ delete[] oldData16; \ - } else if (depth==8) { \ + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \ delete[] oldData8; \ } bool DivSample::resampleNone(double r) { RESAMPLE_BEGIN; - if (depth==16) { + if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples) { @@ -374,7 +434,7 @@ bool DivSample::resampleNone(double r) { data16[i]=oldData16[pos]; } } - } else if (depth==8) { + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i=samples) { @@ -396,7 +456,7 @@ bool DivSample::resampleLinear(double r) { unsigned int posInt=0; double factor=(double)rate/r; - if (depth==16) { + if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples)?0:oldData16[posInt]; short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1]; @@ -409,7 +469,7 @@ bool DivSample::resampleLinear(double r) { posInt++; } } - } else if (depth==8) { + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i=samples)?0:oldData8[posInt]; short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1]; @@ -436,7 +496,7 @@ bool DivSample::resampleCubic(double r) { double factor=(double)rate/r; float* cubicTable=DivFilterTables::getCubicTable(); - if (depth==16) { + if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples)?0:oldData16[posInt]; } } - } else if (depth==8) { + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i>3]>>(i&7))&1)?0x7fff:-0x7fff; } break; - case 1: { // DPCM + case DIV_SAMPLE_DEPTH_1BIT_DPCM: { // DPCM int accum=0; for (unsigned int i=0; i>3]>>(i&7))&1)?1:-1; @@ -687,27 +747,27 @@ void DivSample::render() { } break; } - case 3: // YMZ ADPCM + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM ymz_decode(dataZ,data16,samples); break; - case 4: // QSound ADPCM + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM bs_decode(dataQSoundA,data16,samples); break; - case 5: // ADPCM-A + case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A yma_decode(dataA,data16,samples); break; - case 6: // ADPCM-B + case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B ymb_decode(dataB,data16,samples); break; - case 8: // 8-bit PCM + case DIV_SAMPLE_DEPTH_8BIT: // 8-bit PCM for (unsigned int i=0; i0) { data1[i>>3]|=1<<(i&7); } } } - if (depth!=1) { // DPCM - if (!initInternal(1,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_1BIT_DPCM) { // DPCM + if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return; int accum=63; for (unsigned int i=0; i>9; @@ -739,84 +799,88 @@ void DivSample::render() { if (accum>127) accum=127; } } - if (depth!=3) { // YMZ ADPCM - if (!initInternal(3,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_YMZ_ADPCM) { // YMZ ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_YMZ_ADPCM,samples)) return; ymz_encode(data16,dataZ,(samples+7)&(~0x7)); } - if (depth!=4) { // QSound ADPCM - if (!initInternal(4,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return; bs_encode(data16,dataQSoundA,samples); } // TODO: pad to 256. - if (depth!=5) { // ADPCM-A - if (!initInternal(5,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_ADPCM_A) { // ADPCM-A + if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_A,samples)) return; yma_encode(data16,dataA,(samples+511)&(~0x1ff)); } - if (depth!=6) { // ADPCM-B - if (!initInternal(6,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_ADPCM_B) { // ADPCM-B + if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_B,samples)) return; ymb_encode(data16,dataB,(samples+511)&(~0x1ff)); } - if (depth!=8) { // 8-bit PCM - if (!initInternal(8,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_8BIT) { // 8-bit PCM + if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return; for (unsigned int i=0; i>8; } } // TODO: BRR! - if (depth!=10) { // VOX - if (!initInternal(10,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_VOX) { // VOX + if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return; oki_encode(data16,dataVOX,samples); } } void* DivSample::getCurBuf() { switch (depth) { - case 0: + case DIV_SAMPLE_DEPTH_1BIT: return data1; - case 1: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: return dataDPCM; - case 3: + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: return dataZ; - case 4: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: return dataQSoundA; - case 5: + case DIV_SAMPLE_DEPTH_ADPCM_A: return dataA; - case 6: + case DIV_SAMPLE_DEPTH_ADPCM_B: return dataB; - case 8: + case DIV_SAMPLE_DEPTH_8BIT: return data8; - case 9: + case DIV_SAMPLE_DEPTH_BRR: return dataBRR; - case 10: + case DIV_SAMPLE_DEPTH_VOX: return dataVOX; - case 16: + case DIV_SAMPLE_DEPTH_16BIT: return data16; + default: + return NULL; } return NULL; } unsigned int DivSample::getCurBufLen() { switch (depth) { - case 0: + case DIV_SAMPLE_DEPTH_1BIT: return length1; - case 1: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: return lengthDPCM; - case 3: + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: return lengthZ; - case 4: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: return lengthQSoundA; - case 5: + case DIV_SAMPLE_DEPTH_ADPCM_A: return lengthA; - case 6: + case DIV_SAMPLE_DEPTH_ADPCM_B: return lengthB; - case 8: + case DIV_SAMPLE_DEPTH_8BIT: return length8; - case 9: + case DIV_SAMPLE_DEPTH_BRR: return lengthBRR; - case 10: + case DIV_SAMPLE_DEPTH_VOX: return lengthVOX; - case 16: + case DIV_SAMPLE_DEPTH_16BIT: return length16; + default: + return 0; } return 0; } @@ -831,9 +895,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { duplicate=new unsigned char[getCurBufLen()]; memcpy(duplicate,getCurBuf(),getCurBufLen()); } - h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart); + h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd); } else { - h=new DivSampleHistory(depth,rate,centerRate,loopStart); + h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd); } if (!doNotPush) { while (!redoHist.empty()) { @@ -863,7 +927,8 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { } \ rate=h->rate; \ centerRate=h->centerRate; \ - loopStart=h->loopStart; + loopStart=h->loopStart; \ + loopEnd=h->loopEnd; int DivSample::undo() { diff --git a/src/engine/sample.h b/src/engine/sample.h index 70f8418cf..103bcaa27 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -17,9 +17,28 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#ifndef _SAMPLE_H +#define _SAMPLE_H + +#pragma once + #include "../ta-utils.h" #include +enum DivSampleDepth: unsigned char { + DIV_SAMPLE_DEPTH_1BIT=0, + DIV_SAMPLE_DEPTH_1BIT_DPCM=1, + DIV_SAMPLE_DEPTH_YMZ_ADPCM=3, + DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4, + DIV_SAMPLE_DEPTH_ADPCM_A=5, + DIV_SAMPLE_DEPTH_ADPCM_B=6, + DIV_SAMPLE_DEPTH_8BIT=8, + DIV_SAMPLE_DEPTH_BRR=9, + DIV_SAMPLE_DEPTH_VOX=10, + DIV_SAMPLE_DEPTH_16BIT=16, + DIV_SAMPLE_DEPTH_MAX // boundary for sample depth +}; + enum DivResampleFilters { DIV_RESAMPLE_NONE=0, DIV_RESAMPLE_LINEAR, @@ -32,10 +51,10 @@ enum DivResampleFilters { struct DivSampleHistory { unsigned char* data; unsigned int length, samples; - unsigned char depth; - int rate, centerRate, loopStart; + DivSampleDepth depth; + int rate, centerRate, loopStart, loopEnd; bool hasSample; - DivSampleHistory(void* d, unsigned int l, unsigned int s, unsigned char de, int r, int cr, int ls): + DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le): data((unsigned char*)d), length(l), samples(s), @@ -43,8 +62,9 @@ struct DivSampleHistory { rate(r), centerRate(cr), loopStart(ls), + loopEnd(le), hasSample(true) {} - DivSampleHistory(unsigned char de, int r, int cr, int ls): + DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le): data(NULL), length(0), samples(0), @@ -52,13 +72,14 @@ struct DivSampleHistory { rate(r), centerRate(cr), loopStart(ls), + loopEnd(le), hasSample(false) {} ~DivSampleHistory(); }; struct DivSample { String name; - int rate, centerRate, loopStart, loopOffP; + int rate, centerRate, loopStart, loopEnd, loopOffP; // valid values are: // - 0: ZX Spectrum overlay drum (1-bit) // - 1: 1-bit NES DPCM (1-bit) @@ -70,7 +91,7 @@ struct DivSample { // - 9: BRR (SNES) // - 10: VOX ADPCM // - 16: 16-bit PCM - unsigned char depth; + DivSampleDepth depth; // these are the new data structures. signed char* data8; // 8 @@ -93,6 +114,23 @@ struct DivSample { std::deque undoHist; std::deque redoHist; + /** + * check if sample is loopable. + * @return whether it is loopable. + */ + bool isLoopable(); + + /** + * get sample end position + * @return the samples end position. + */ + unsigned int getEndPosition(DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX); + + /** + * @warning DO NOT USE - internal functions + */ + void setSampleCount(unsigned int count); + /** * @warning DO NOT USE - internal functions */ @@ -116,7 +154,7 @@ struct DivSample { * @param count number of samples. * @return whether it was successful. */ - bool initInternal(unsigned char d, int count); + bool initInternal(DivSampleDepth d, int count); /** * initialize sample data. make sure you have set `depth` before doing so. @@ -212,8 +250,9 @@ struct DivSample { rate(32000), centerRate(8363), loopStart(-1), + loopEnd(-1), loopOffP(0), - depth(16), + depth(DIV_SAMPLE_DEPTH_16BIT), data8(NULL), data16(NULL), data1(NULL), @@ -253,3 +292,5 @@ struct DivSample { samples(0) {} ~DivSample(); }; + +#endif diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 0d1ad4f52..ef1c5dc37 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -512,7 +512,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(streamID); w->writeS(write.val); // sample number w->writeC((sample->loopStart==0)|(sampleDir[streamID]?0x10:0)); // flags - if (sample->loopStart>0 && !sampleDir[streamID]) { + if (sample->isLoopable() && !sampleDir[streamID]) { loopTimer[streamID]=sample->length8; loopSample[streamID]=write.val; } @@ -1549,7 +1549,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { size_t memPos=0; for (int i=0; ilength8+0xff)&(~0xff); + unsigned int alignedSize=(sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)+0xff)&(~0xff); if (alignedSize>65536) alignedSize=65536; if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) { memPos=(memPos+0xffff)&0xff0000; @@ -1559,8 +1559,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { sample->offSegaPCM=memPos; unsigned int readPos=0; for (unsigned int j=0; j=sample->length8) { - if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) { + if (readPos>=sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)) { + if (sample->isLoopable()) { readPos=sample->loopStart; pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80); } else { @@ -1663,7 +1663,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { memcpy(sampleMem,writeZ280[i]->getSampleMem(),sampleMemLen); for (int i=0; idepth==16) { + if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { unsigned int pos=s->offYMZ280B; for (unsigned int j=0; jsamples; j++) { unsigned char lo=sampleMem[pos+j*2]; @@ -1871,12 +1871,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { if (loopSample[nextToTouch]loopStart<(int)sample->length8) { + if (sample->loopStart<(int)sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)) { w->writeC(0x93); w->writeC(nextToTouch); w->writeI(sample->off8+sample->loopStart); w->writeC(0x81); - w->writeI(sample->length8-sample->loopStart); + w->writeI(sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->loopStart); } } loopSample[nextToTouch]=-1; diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index d9739f68d..12e775b53 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -18,6 +18,7 @@ */ #include "gui.h" +#include "guiConst.h" #include "debug.h" #include "IconsFontAwesome4.h" #include @@ -154,12 +155,19 @@ void FurnaceGUI::drawDebug() { ImGui::Text("rate: %d",sample->rate); ImGui::Text("centerRate: %d",sample->centerRate); ImGui::Text("loopStart: %d",sample->loopStart); + ImGui::Text("loopEnd: %d", sample->loopEnd); ImGui::Text("loopOffP: %d",sample->loopOffP); - ImGui::Text("depth: %d",sample->depth); + if (sampleDepths[sample->depth]!=NULL) { + ImGui::Text("depth: %d (%s)",(unsigned char)sample->depth,sampleDepths[sample->depth]); + } else { + ImGui::Text("depth: %d ()",(unsigned char)sample->depth); + } + ImGui::Text("length8: %d",sample->length8); ImGui::Text("length16: %d",sample->length16); ImGui::Text("length1: %d",sample->length1); ImGui::Text("lengthDPCM: %d",sample->lengthDPCM); + ImGui::Text("lengthZ: %d",sample->lengthZ); ImGui::Text("lengthQSoundA: %d",sample->lengthQSoundA); ImGui::Text("lengthA: %d",sample->lengthA); ImGui::Text("lengthB: %d",sample->lengthB); @@ -170,6 +178,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("off16: %x",sample->off16); ImGui::Text("off1: %x",sample->off1); ImGui::Text("offDPCM: %x",sample->offDPCM); + ImGui::Text("offZ: %x",sample->offZ); ImGui::Text("offQSoundA: %x",sample->offQSoundA); ImGui::Text("offA: %x",sample->offA); ImGui::Text("offB: %x",sample->offB); @@ -179,6 +188,8 @@ void FurnaceGUI::drawDebug() { ImGui::Text("offQSound: %x",sample->offQSound); ImGui::Text("offX1_010: %x",sample->offX1_010); ImGui::Text("offSU: %x",sample->offSU); + ImGui::Text("offYMZ280B: %x",sample->offYMZ280B); + ImGui::Text("offRF5C68: %x",sample->offRF5C68); ImGui::Text("samples: %d",sample->samples); ImGui::TreePop(); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 60c9d5c3a..ca640d627 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -709,6 +709,7 @@ void FurnaceGUI::doAction(int what) { sample->centerRate=prevSample->centerRate; sample->name=prevSample->name; sample->loopStart=prevSample->loopStart; + sample->loopEnd=prevSample->loopEnd; sample->depth=prevSample->depth; if (sample->init(prevSample->samples)) { if (prevSample->getCurBuf()!=NULL) { @@ -838,7 +839,7 @@ void FurnaceGUI::doAction(int what) { if (!sample->insert(pos,sampleClipboardLen)) { showError("couldn't paste! make sure your sample is 8 or 16-bit."); } else { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (size_t i=0; idata8[pos+i]=sampleClipboard[i]>>8; } @@ -864,7 +865,7 @@ void FurnaceGUI::doAction(int what) { if (pos<0) pos=0; e->lockEngine([this,sample,pos]() { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (size_t i=0; i=sample->samples) break; sample->data8[pos+i]=sampleClipboard[i]>>8; @@ -894,7 +895,7 @@ void FurnaceGUI::doAction(int what) { if (pos<0) pos=0; e->lockEngine([this,sample,pos]() { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (size_t i=0; i=sample->samples) break; int val=sample->data8[pos+i]+(sampleClipboard[i]>>8); @@ -948,7 +949,7 @@ void FurnaceGUI::doAction(int what) { SAMPLE_OP_BEGIN; float maxVal=0.0f; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]/32767.0f); if (val>maxVal) maxVal=val; @@ -963,7 +964,7 @@ void FurnaceGUI::doAction(int what) { sample->data16[i]=val; } } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]/127.0f); if (val>maxVal) maxVal=val; @@ -994,14 +995,14 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*float(i-start)/float(end-start); if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*float(i-start)/float(end-start); if (val<-128) val=-128; @@ -1024,14 +1025,14 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*float(end-i)/float(end-start); if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*float(end-i)/float(end-start); if (val<-128) val=-128; @@ -1058,11 +1059,11 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]=0; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]=0; } @@ -1116,7 +1117,7 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[ri]^=sample->data16[i]; sample->data16[i]^=sample->data16[ri]; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; ilockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]=-sample->data16[i]; if (sample->data16[i]==-32768) sample->data16[i]=32767; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]=-sample->data8[i]; if (sample->data16[i]==-128) sample->data16[i]=127; @@ -1174,11 +1175,11 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]^=0x8000; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]^=0x80; } @@ -1261,8 +1262,8 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - sample->trim(0,end); sample->loopStart=start; + sample->loopEnd=end; updateSampleTex=true; e->renderSamples(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 80f9b5d85..64e7b18db 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -116,7 +116,7 @@ const char* insTypes[DIV_INS_MAX+1]={ NULL }; -const char* sampleDepths[17]={ +const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "1-bit PCM", "1-bit DPCM", NULL, diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 69085c324..a6df68f62 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -40,7 +40,7 @@ extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; -extern const char* sampleDepths[17]; +extern const char* sampleDepths[]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const FurnaceGUIActionDef guiActions[]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index b684a0c77..e9b5cfb35 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -41,7 +41,7 @@ void FurnaceGUI::drawSampleEdit() { } else { DivSample* sample=e->song.sample[curSample]; String sampleType="Invalid"; - if (sample->depth<17) { + if (sample->depthdepth]!=NULL) { sampleType=sampleDepths[sample->depth]; } @@ -61,11 +61,11 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { - for (int i=0; i<17; i++) { + for (int i=0; iprepareUndo(true); - sample->depth=i; + sample->depth=(DivSampleDepth)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; @@ -93,22 +93,43 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - bool doLoop=(sample->loopStart>=0); + bool doLoop=(sample->isLoopable()); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loopStart=0; + sample->loopEnd=sample->samples; } else { sample->loopStart=-1; + sample->loopEnd=sample->samples; } updateSampleTex=true; } if (doLoop) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Loop Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0) { sample->loopStart=0; } + if (sample->loopStart>sample->loopEnd) { + sample->loopStart=sample->loopEnd; + } + updateSampleTex=true; + } + ImGui::TableNextColumn(); + ImGui::Text("Loop End"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (sample->loopEndloopStart) { + sample->loopEnd=sample->loopStart; + } + if (sample->loopEnd>=(int)sample->samples) { + sample->loopEnd=sample->samples; + } updateSampleTex=true; } } @@ -123,7 +144,7 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); - ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { @@ -275,14 +296,14 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*vol; if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*vol; if (val<-128) val=-128; @@ -466,7 +487,7 @@ void FurnaceGUI::drawSampleEdit() { double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; irate))*M_PI); @@ -482,7 +503,7 @@ void FurnaceGUI::drawSampleEdit() { if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; irate))*M_PI); @@ -574,11 +595,11 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { - for (int i=0; i<17; i++) { + for (int i=0; iprepareUndo(true); - sample->depth=i; + sample->depth=(DivSampleDepth)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; @@ -607,22 +628,43 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - bool doLoop=(sample->loopStart>=0); + bool doLoop=(sample->isLoopable()); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loopStart=0; + sample->loopEnd=sample->samples; } else { sample->loopStart=-1; + sample->loopEnd=sample->samples; } updateSampleTex=true; } if (doLoop) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Loop Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0) { sample->loopStart=0; } + if (sample->loopStart>sample->loopEnd) { + sample->loopStart=sample->loopEnd; + } + updateSampleTex=true; + } + ImGui::TableNextColumn(); + ImGui::Text("Loop End"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (sample->loopEndloopStart) { + sample->loopEnd=sample->loopStart; + } + if (sample->loopEnd>=(int)sample->samples) { + sample->loopEnd=sample->samples; + } updateSampleTex=true; } } @@ -637,7 +679,7 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); - ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { @@ -820,7 +862,7 @@ void FurnaceGUI::drawSampleEdit() { double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; irate))*M_PI); @@ -836,7 +878,7 @@ void FurnaceGUI::drawSampleEdit() { if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; irate))*M_PI); @@ -890,14 +932,14 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*vol; if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*vol; if (val<-128) val=-128; @@ -1138,7 +1180,7 @@ void FurnaceGUI::drawSampleEdit() { for (int i=0; iloopStart>=0 && sample->loopStart<(int)sample->samples && scaledPos>=sample->loopStart) { + if (sample->isLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { data[i*availX+j]=bgColorLoop; } else { data[i*availX+j]=bgColor; @@ -1158,7 +1200,7 @@ void FurnaceGUI::drawSampleEdit() { if (xCoarse>=sample->samples) break; int y1, y2; int totalAdvance=0; - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; } else { y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; @@ -1171,7 +1213,7 @@ void FurnaceGUI::drawSampleEdit() { totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; } else { y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; @@ -1209,11 +1251,11 @@ void FurnaceGUI::drawSampleEdit() { sampleSelStart=0; sampleSelEnd=sample->samples; } else { - if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { + if (sample->samples>0 && (sample->depth==DIV_SAMPLE_DEPTH_16BIT || sample->depth==DIV_SAMPLE_DEPTH_8BIT)) { sampleDragStart=rectMin; sampleDragAreaSize=rectSize; - sampleDrag16=(sample->depth==16); - sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); + sampleDrag16=(sample->depth==DIV_SAMPLE_DEPTH_16BIT); + sampleDragTarget=(sample->depth==DIV_SAMPLE_DEPTH_16BIT)?((void*)sample->data16):((void*)sample->data8); sampleDragLen=sample->samples; sampleDragActive=true; sampleSelStart=-1; @@ -1312,7 +1354,7 @@ void FurnaceGUI::drawSampleEdit() { posX=samplePos+pos.x*sampleZoom; if (posX>(int)sample->samples) posX=-1; } - posY=(0.5-pos.y/rectSize.y)*((sample->depth==8)?255:32767); + posY=(0.5-pos.y/rectSize.y)*((sample->depth==DIV_SAMPLE_DEPTH_8BIT)?255:32767); if (posX>=0) { statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); } @@ -1362,7 +1404,7 @@ void FurnaceGUI::drawSampleEdit() { } } - if (sample->depth!=8 && sample->depth!=16) { + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { statusBar="Non-8/16-bit samples cannot be edited without prior conversion."; } From 7bc3166ed5eac383364595541c966a6ca14fe337 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Jul 2022 00:01:29 -0500 Subject: [PATCH 031/194] YMZ280B: frequency precision improvement frequency is now multiplied by 256 and then fed to the chip divided by 256 to increase freq precision --- src/engine/platform/ymz280b.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index 87193da50..b690d30cb 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -23,7 +23,7 @@ #include #include -#define CHIP_FREQBASE 98304 +#define CHIP_FREQBASE 25165824 #define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }} @@ -142,7 +142,7 @@ void DivPlatformYMZ280B::tick(bool sysTick) { default: ctrl=0; } double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; - chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1; + chan[i].freq=(int)round(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)/256.0)-1; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>511) chan[i].freq=511; // ADPCM has half the range @@ -267,14 +267,15 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; + int multiplier=(parent->song.linearPitch==2)?1:256; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value; + chan[c.chan].baseFreq+=c.value*multiplier; if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value; + chan[c.chan].baseFreq-=c.value*multiplier; if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; From d004629a58d750015fddad09ce3ac415267f6a45 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Jul 2022 02:29:01 -0500 Subject: [PATCH 032/194] dev102 - new sample storage format --- papers/format.md | 44 +++++++++++++++++++++-- src/engine/engine.h | 4 +-- src/engine/fileOps.cpp | 81 ++++++++++++++++++++++++++---------------- 3 files changed, 95 insertions(+), 34 deletions(-) diff --git a/papers/format.md b/papers/format.md index 6281293ab..080914c1e 100644 --- a/papers/format.md +++ b/papers/format.md @@ -816,7 +816,47 @@ size | description 4?? | wavetable data ``` -# sample +# sample (>=102) + +this is the new sample storage format used in Furnace dev102 and higher. + +``` +size | description +-----|------------------------------------ + 4 | "SMP2" block ID + 4 | size of this block + STR | sample name + 4 | length + 4 | compatibility rate + 4 | C-4 rate + 1 | depth + | - 0: ZX Spectrum overlay drum (1-bit) + | - 1: 1-bit NES DPCM (1-bit) + | - 3: YMZ ADPCM + | - 4: QSound ADPCM + | - 5: ADPCM-A + | - 6: ADPCM-B + | - 8: 8-bit PCM + | - 9: BRR (SNES) + | - 10: VOX + | - 16: 16-bit PCM + 3 | reserved + 4 | loop start + | - -1 means no loop + 4 | loop end + | - -1 means no loop + 16 | sample presence bitfields + | - for future use. + | - indicates whether the sample should be present in the memory of a system. + | - read 4 32-bit numbers (for 4 memory banks per system, e.g. YM2610 + | does ADPCM-A and ADPCM-B on separate memory banks). + ??? | sample data + | - size is length +``` + +# old sample (<102) + +this format is present when saving using previous Furnace versions. ``` size | description @@ -825,7 +865,7 @@ size | description 4 | size of this block STR | sample name 4 | length - 4 | rate + 4 | compatibility rate 2 | volume (<58) or reserved 2 | pitch (<58) or reserved 1 | depth diff --git a/src/engine/engine.h b/src/engine/engine.h index 896c083a2..21fde0a70 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 "0.6pre1 (dev101)" -#define DIV_ENGINE_VERSION 101 +#define DIV_VERSION "0.6pre1 (dev102)" +#define DIV_ENGINE_VERSION 102 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 40932a378..110259050 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1619,51 +1619,67 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } reader.read(magic,4); - if (strcmp(magic,"SMPL")!=0) { + if (strcmp(magic,"SMPL")!=0 && strcmp(magic,"SMP2")!=0) { logE("%d: invalid sample header!",i); lastError="invalid sample header!"; ds.unload(); delete[] file; return false; } + bool isNewSample=(strcmp(magic,"SMP2")==0); reader.readI(); DivSample* sample=new DivSample; logD("reading sample %d at %x...",i,samplePtr[i]); + if (!isNewSample) logV("(old sample)"); sample->name=reader.readString(); - sample->samples=sample->loopEnd=reader.readI(); + sample->samples=reader.readI(); + if (!isNewSample) { + sample->loopEnd=sample->samples; + } sample->rate=reader.readI(); - if (ds.version<58) { - vol=reader.readS(); - pitch=reader.readS(); - } else { - reader.readI(); - } - sample->depth=(DivSampleDepth)reader.readC(); - // reserved - reader.readC(); + if (isNewSample) { + sample->centerRate=reader.readI(); + sample->depth=(DivSampleDepth)reader.readC(); - // while version 32 stored this value, it was unused. - if (ds.version>=38) { - sample->centerRate=(unsigned short) reader.readS(); - } else { - reader.readS(); - } + // reserved + reader.readC(); + reader.readC(); + reader.readC(); - if (ds.version>=19) { sample->loopStart=reader.readI(); + sample->loopEnd=reader.readI(); + + for (int i=0; i<4; i++) { + reader.readI(); + } } else { - reader.readI(); + if (ds.version<58) { + vol=reader.readS(); + pitch=reader.readS(); + } else { + reader.readI(); + } + sample->depth=(DivSampleDepth)reader.readC(); + + // reserved + reader.readC(); + + // while version 32 stored this value, it was unused. + if (ds.version>=38) { + sample->centerRate=(unsigned short)reader.readS(); + } else { + reader.readS(); + } + + if (ds.version>=19) { + sample->loopStart=reader.readI(); + } else { + reader.readI(); + } } -/* - if (ds.version>=100) { - sample->loopEnd=reader.readI(); - } else { - reader.readI(); - } -*/ if (ds.version>=58) { // modern sample sample->init(sample->samples); reader.read(sample->getCurBuf(),sample->getCurBufLen()); @@ -3038,19 +3054,24 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; itell()); - w->write("SMPL",4); + w->write("SMP2",4); blockStartSeek=w->tell(); w->writeI(0); w->writeString(sample->name,false); w->writeI(sample->samples); w->writeI(sample->rate); - w->writeI(0); // reserved (for now) + w->writeI(sample->centerRate); w->writeC(sample->depth); + w->writeC(0); // reserved + w->writeC(0); w->writeC(0); - w->writeS(sample->centerRate); w->writeI(sample->loopStart); - //w->writeI(sample->loopEnd); + w->writeI(sample->loopEnd); + + for (int i=0; i<4; i++) { + w->writeI(0xffffffff); + } w->write(sample->getCurBuf(),sample->getCurBufLen()); From 8d88ac766c109872114e7055988a819e9f62478e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 17:02:03 -0500 Subject: [PATCH 033/194] dev103 - store system name and other info in song --- TODO.md | 3 -- papers/format.md | 9 +++++ src/engine/engine.cpp | 13 ++++++- src/engine/engine.h | 8 ++-- src/engine/fileOps.cpp | 32 ++++++++++++++++ src/engine/song.h | 5 ++- src/engine/sysDef.cpp | 86 +++++++++++++++++++++--------------------- src/engine/vgmOps.cpp | 24 +++++------- src/gui/gui.cpp | 4 +- src/gui/gui.h | 1 + src/gui/newSong.cpp | 3 +- src/gui/songInfo.cpp | 27 +++++++++++++ 12 files changed, 146 insertions(+), 69 deletions(-) diff --git a/TODO.md b/TODO.md index 5032ea32e..46773705a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,5 @@ # to-do for 0.6pre1.5-0.6pre2 -- rewrite the system name detection function anyway - - this involves the addition of a new "system" field in the song (which solves the problem) - - songs made in older versions will go through old system name detection for compatibility - Game Boy envelope macro/sequence - volume commands should work on Game Boy - ability to customize `OFF`, `===` and `REL` diff --git a/papers/format.md b/papers/format.md index 080914c1e..537150215 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 103: Furnace dev103 +- 102: Furnace 0.6pre1 (dev102) - 101: Furnace 0.6pre1 (dev101) - 100: Furnace 0.6pre1 - 99: Furnace dev99 @@ -334,6 +336,13 @@ size | description 1 | number of additional subsongs 3 | reserved 4?? | pointers to subsong data + --- | **additional metadata** (>=103) + STR | system name + STR | album/category/game name + STR | song name (Japanese) + STR | song author (Japanese) + STR | system name (Japanese) + STR | album/category/game name (Japanese) ``` # subsong diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 659ea6b9a..f1e8dc043 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -842,7 +842,7 @@ void DivEngine::initSongWithDesc(const int* description) { } } -void DivEngine::createNew(const int* description) { +void DivEngine::createNew(const int* description, String sysName) { quitDispatch(); BUSY_BEGIN; saveLock.lock(); @@ -852,6 +852,11 @@ void DivEngine::createNew(const int* description) { if (description!=NULL) { initSongWithDesc(description); } + if (sysName=="") { + song.systemName=getSongSystemLegacyName(song,getConfInt("noMultiSystem",0)); + } else { + song.systemName=sysName; + } recalcChans(); saveLock.unlock(); BUSY_END; @@ -3197,6 +3202,12 @@ bool DivEngine::init() { preset.push_back(0); initSongWithDesc(preset.data()); } + String sysName=getConfString("initialSysName",""); + if (sysName=="") { + song.systemName=getSongSystemLegacyName(song,getConfInt("noMultiSystem",0)); + } else { + song.systemName=sysName; + } hasLoadedSomething=true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 21fde0a70..f6567dd3a 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 "0.6pre1 (dev102)" -#define DIV_ENGINE_VERSION 102 +#define DIV_VERSION "dev103" +#define DIV_ENGINE_VERSION 103 // for imports #define DIV_VERSION_MOD 0xff01 @@ -454,7 +454,7 @@ class DivEngine { String encodeSysDesc(std::vector& desc); std::vector decodeSysDesc(String desc); // start fresh - void createNew(const int* description); + void createNew(const int* description, String sysName); // load a file. bool load(unsigned char* f, size_t length); // save as .dmf. @@ -576,7 +576,7 @@ class DivEngine { DivInstrumentType getPreferInsSecondType(int ch); // get song system name - String getSongSystemName(bool isMultiSystemAcceptable=true); + String getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable=true); // get sys name const char* getSystemName(DivSystem sys); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 110259050..e66e2c8d9 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -907,6 +907,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.system[1]=DIV_SYSTEM_FDS; } + ds.systemName=getSongSystemLegacyName(ds,getConfInt("noMultiSystem",0)); + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); @@ -1482,7 +1484,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; i=103) { + ds.systemName=reader.readString(); + ds.category=reader.readString(); + ds.nameJ=reader.readString(); + ds.authorJ=reader.readString(); + ds.systemNameJ=reader.readString(); + ds.categoryJ=reader.readString(); + } else { + ds.systemName=getSongSystemLegacyName(ds,getConfInt("noMultiSystem",0)); + } + + // read subsongs + if (ds.version>=95) { for (int i=0; i='1' && magic[0]<='9') { logD("detected a FastTracker module"); + ds.systemName="PC"; chCount=magic[0]-'0'; } else if (memcmp(magic,"FLT",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a Fairlight module"); + ds.systemName="Amiga"; chCount=magic[3]-'0'; } else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a TakeTracker module"); + ds.systemName="PC"; chCount=magic[3]-'0'; } else if ((memcmp(magic+2,"CH",2)==0 || memcmp(magic+2,"CN",2)==0) && (magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) { logD("detected a Fast/TakeTracker module"); + ds.systemName="PC"; chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { insCount=15; logD("possibly a Soundtracker module"); + ds.systemName="Amiga"; chCount=4; } @@ -2976,6 +3000,14 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(0); } + // additional metadata + w->writeString(song.systemName,false); + w->writeString(song.category,false); + w->writeString(song.nameJ,false); + w->writeString(song.authorJ,false); + w->writeString(song.systemNameJ,false); + w->writeString(song.categoryJ,false); + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); diff --git a/src/engine/song.h b/src/engine/song.h index 26640fdd4..8375fc561 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -428,7 +428,7 @@ struct DivSong { unsigned int systemFlags[32]; // song information - String name, author; + String name, author, systemName; // legacy song information // those will be stored in .fur and mapped to VGM as: @@ -438,7 +438,7 @@ struct DivSong { String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate; // more VGM specific stuff - String nameJ, authorJ, categoryJ; + String nameJ, authorJ, categoryJ, systemNameJ; // other things String notes; @@ -541,6 +541,7 @@ struct DivSong { systemLen(2), name(""), author(""), + systemName(""), carrier(""), composer(""), vendor(""), diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 25e35227f..813e409e5 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -54,14 +54,14 @@ std::vector& DivEngine::getPossibleInsTypes() { return possibleInsTypes; } -// TODO: deprecate when I add "system name" field in the file. -String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { - switch (song.systemLen) { +// for pre-dev103 modules +String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable) { + switch (ds.systemLen) { case 0: return "help! what's going on!"; case 1: - if (song.system[0]==DIV_SYSTEM_AY8910) { - switch (song.systemFlags[0]&0x3f) { + if (ds.system[0]==DIV_SYSTEM_AY8910) { + switch (ds.systemFlags[0]&0x3f) { case 0: // AY-3-8910, 1.79MHz case 1: // AY-3-8910, 1.77MHz case 2: // AY-3-8910, 1.75MHz @@ -88,116 +88,116 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { return "Intellivision (PAL)"; default: - if ((song.systemFlags[0]&0x30)==0x00) { + if ((ds.systemFlags[0]&0x30)==0x00) { return "AY-3-8910"; - } else if ((song.systemFlags[0]&0x30)==0x10) { + } else if ((ds.systemFlags[0]&0x30)==0x10) { return "Yamaha YM2149"; - } else if ((song.systemFlags[0]&0x30)==0x20) { + } else if ((ds.systemFlags[0]&0x30)==0x20) { return "Overclocked Sunsoft 5B"; - } else if ((song.systemFlags[0]&0x30)==0x30) { + } else if ((ds.systemFlags[0]&0x30)==0x30) { return "Intellivision"; } } - } else if (song.system[0]==DIV_SYSTEM_SMS) { - switch (song.systemFlags[0]&0x0f) { + } else if (ds.system[0]==DIV_SYSTEM_SMS) { + switch (ds.systemFlags[0]&0x0f) { case 0: case 1: return "Sega Master System"; case 6: return "BBC Micro"; } - } else if (song.system[0]==DIV_SYSTEM_YM2612) { - switch (song.systemFlags[0]&3) { + } else if (ds.system[0]==DIV_SYSTEM_YM2612) { + switch (ds.systemFlags[0]&3) { case 2: return "FM Towns"; } - } else if (song.system[0]==DIV_SYSTEM_YM2151) { - switch (song.systemFlags[0]&3) { + } else if (ds.system[0]==DIV_SYSTEM_YM2151) { + switch (ds.systemFlags[0]&3) { case 2: return "Sharp X68000"; } - } else if (song.system[0]==DIV_SYSTEM_SAA1099) { - switch (song.systemFlags[0]&3) { + } else if (ds.system[0]==DIV_SYSTEM_SAA1099) { + switch (ds.systemFlags[0]&3) { case 0: return "SAM Coupé"; } } - return getSystemName(song.system[0]); + return getSystemName(ds.system[0]); case 2: - if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612 && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis/Mega Drive"; } - if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612_EXT && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis Extended Channel 3"; } - if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_OPLL && ds.system[1]==DIV_SYSTEM_SMS) { return "NTSC-J Sega Master System"; } - if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_OPLL_DRUMS && ds.system[1]==DIV_SYSTEM_SMS) { return "NTSC-J Sega Master System + drums"; } - if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_OPLL && ds.system[1]==DIV_SYSTEM_AY8910) { return "MSX-MUSIC"; } - if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_OPLL_DRUMS && ds.system[1]==DIV_SYSTEM_AY8910) { return "MSX-MUSIC + drums"; } - if (song.system[0]==DIV_SYSTEM_C64_6581 && song.system[1]==DIV_SYSTEM_C64_6581) { + if (ds.system[0]==DIV_SYSTEM_C64_6581 && ds.system[1]==DIV_SYSTEM_C64_6581) { return "Commodore 64 with dual 6581"; } - if (song.system[0]==DIV_SYSTEM_C64_8580 && song.system[1]==DIV_SYSTEM_C64_8580) { + if (ds.system[0]==DIV_SYSTEM_C64_8580 && ds.system[1]==DIV_SYSTEM_C64_8580) { return "Commodore 64 with dual 8580"; } - if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { + if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { return "YM2151 + SegaPCM Arcade (compatibility)"; } - if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM) { + if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_SEGAPCM) { return "YM2151 + SegaPCM Arcade"; } - if (song.system[0]==DIV_SYSTEM_SAA1099 && song.system[1]==DIV_SYSTEM_SAA1099) { + if (ds.system[0]==DIV_SYSTEM_SAA1099 && ds.system[1]==DIV_SYSTEM_SAA1099) { return "Creative Music System"; } - if (song.system[0]==DIV_SYSTEM_GB && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_GB && ds.system[1]==DIV_SYSTEM_AY8910) { return "Game Boy with AY expansion"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC6) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_VRC6) { return "Famicom + Konami VRC6"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_VRC7) { return "Famicom + Konami VRC7"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_OPLL) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_OPLL) { return "Family Noraebang"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_FDS) { return "Famicom Disk System"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_N163) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_N163) { String ret="Famicom + "; ret+=getConfString("c163Name","Namco C163"); return ret; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_MMC5) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_MMC5) { return "Famicom + MMC5"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_AY8910) { return "Famicom + Sunsoft 5B"; } - if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_AY8910 && ds.system[1]==DIV_SYSTEM_AY8910) { return "Bally Midway MCR"; } - if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) { + if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_VERA) { return "Commander X16"; } break; case 3: - if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_BUBSYS_WSG) { + if (ds.system[0]==DIV_SYSTEM_AY8910 && ds.system[1]==DIV_SYSTEM_AY8910 && ds.system[2]==DIV_SYSTEM_BUBSYS_WSG) { return "Konami Bubble System"; } break; @@ -205,12 +205,12 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { if (isMultiSystemAcceptable) return "multi-system"; String ret=""; - for (int i=0; i0) ret+=" + "; - if (song.system[i]==DIV_SYSTEM_N163) { + if (ds.system[i]==DIV_SYSTEM_N163) { ret+=getConfString("c163Name","Namco C163"); } else { - ret+=getSystemName(song.system[i]); + ret+=getSystemName(ds.system[i]); } } diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index ef1c5dc37..a500b4848 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -1920,24 +1920,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { WString ws; ws=utf8To16(song.name.c_str()); w->writeWString(ws,false); // name - w->writeS(0); // japanese name - w->writeS(0); // game name - w->writeS(0); // japanese game name - if (song.systemLen>1) { - ws=L"Multiple Systems"; - } else { - ws=utf8To16(getSystemName(song.system[0])); - } + ws=utf8To16(song.nameJ.c_str()); + w->writeWString(ws,false); // japanese name + ws=utf8To16(song.category.c_str()); + w->writeWString(ws,false); // game name + ws=utf8To16(song.categoryJ.c_str()); + w->writeWString(ws,false); // japanese game name + ws=utf8To16(song.systemName.c_str()); w->writeWString(ws,false); // system name - if (song.systemLen>1) { - ws=L"複数システム"; - } else { - ws=utf8To16(getSystemNameJ(song.system[0])); - } + ws=utf8To16(song.systemNameJ.c_str()); w->writeWString(ws,false); // japanese system name ws=utf8To16(song.author.c_str()); w->writeWString(ws,false); // author name - w->writeS(0); // japanese author name + ws=utf8To16(song.authorJ.c_str()); + w->writeWString(ws,false); // japanese author name w->writeS(0); // date w->writeWString(L"Furnace Tracker",false); // ripper w->writeS(0); // notes diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 90925b934..97c1d78e1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -551,7 +551,9 @@ void FurnaceGUI::updateWindowTitle() { } if (settings.titleBarSys) { - title+=fmt::sprintf(" (%s)",e->getSongSystemName(!settings.noMultiSystem)); + if (e->song.systemName!="") { + title+=fmt::sprintf(" (%s)",e->song.systemName); + } } if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str()); diff --git a/src/gui/gui.h b/src/gui/gui.h index 851e10aa1..c267fcab5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1230,6 +1230,7 @@ class FurnaceGUI { float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; const int* nextDesc; + String nextDescName; OperationMask opMaskDelete, opMaskPullDelete, opMaskInsert, opMaskPaste, opMaskTransposeNote, opMaskTransposeValue; OperationMask opMaskInterpolate, opMaskFade, opMaskInvertVal, opMaskScale; diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 0dea772b7..26fddf9fb 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -65,6 +65,7 @@ void FurnaceGUI::drawNewSong() { ImGui::TableNextColumn(); if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { nextDesc=i.definition.data(); + nextDescName=i.name; accepted=true; } } @@ -97,7 +98,7 @@ void FurnaceGUI::drawNewSong() { } if (accepted) { - e->createNew(nextDesc); + e->createNew(nextDesc,nextDescName); undoHist.clear(); redoHist.clear(); curFileName=""; diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index e90043b7a..b823d085c 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -207,6 +207,33 @@ void FurnaceGUI::drawSongInfo() { } ImGui::EndTable(); } + + if (ImGui::TreeNode("Additional Information")) { + if (ImGui::BeginTable("ExtraData",2,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Album"); + ImGui::TableNextColumn(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Category",&e->song.category)) { + MARK_MODIFIED; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("System"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##SystemName",&e->song.systemName)) { + MARK_MODIFIED; + updateWindowTitle(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; ImGui::End(); From 6051f92e684f031b790504b705cd601307a30a62 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 18:22:42 -0500 Subject: [PATCH 034/194] GUI: add setting to customize initial system name --- src/gui/gui.h | 4 +++- src/gui/settings.cpp | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index c267fcab5..bd2a5494c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1100,6 +1100,7 @@ class FurnaceGUI { String midiInDevice; String midiOutDevice; String c163Name; + String initialSysName; std::vector initialSys; Settings(): @@ -1203,7 +1204,8 @@ class FurnaceGUI { audioDevice(""), midiInDevice(""), midiOutDevice(""), - c163Name("") {} + c163Name(""), + initialSysName("Sega Genesis/Mega Drive") {} } settings; char finalLayoutPath[4096]; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 2d05ab5ac..b97390831 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -252,9 +252,9 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); - ImGui::Text("Initial system/chips:"); + ImGui::Text("Initial system:"); ImGui::SameLine(); - if (ImGui::Button("Current systems")) { + if (ImGui::Button("Current system")) { settings.initialSys.clear(); for (int i=0; isong.systemLen; i++) { settings.initialSys.push_back(e->song.system[i]); @@ -262,6 +262,7 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(e->song.systemPan[i]); settings.initialSys.push_back(e->song.systemFlags[i]); } + settings.initialSysName=e->song.systemName; } ImGui::SameLine(); if (ImGui::Button("Randomize")) { @@ -282,6 +283,31 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(0); settings.initialSys.push_back(0); } + // randomize system name + std::vector wordPool[6]; + for (size_t i=0; igetSystemName((DivSystem)settings.initialSys[i*4]); + String nameWord; + sName+=" "; + for (char& i: sName) { + if (i==' ') { + if (nameWord!="") { + wordPool[wpPos++].push_back(nameWord); + if (wpPos>=6) break; + nameWord=""; + } + } else { + nameWord+=i; + } + } + } + settings.initialSysName=""; + for (int i=0; i<6; i++) { + if (wordPool[i].empty()) continue; + settings.initialSysName+=wordPool[i][rand()%wordPool[i].size()]; + settings.initialSysName+=" "; + } } ImGui::SameLine(); if (ImGui::Button("Reset to defaults")) { @@ -294,7 +320,14 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(32); settings.initialSys.push_back(0); settings.initialSys.push_back(0); + settings.initialSysName="Sega Genesis/Mega Drive"; } + + ImGui::Text("Name"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##InitSysName",&settings.initialSysName); + for (size_t i=0; igetConfInt("dragMovesSelection",2); settings.unsignedDetune=e->getConfInt("unsignedDetune",0); settings.noThreadedInput=e->getConfInt("noThreadedInput",0); + settings.initialSysName=e->getConfString("initialSysName",""); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2286,6 +2320,7 @@ void FurnaceGUI::commitSettings() { e->setConf("moveWindowTitle",settings.moveWindowTitle); e->setConf("hiddenSystems",settings.hiddenSystems); e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); + e->setConf("initialSysName",settings.initialSysName); e->setConf("horizontalDataView",settings.horizontalDataView); e->setConf("noMultiSystem",settings.noMultiSystem); e->setConf("oldMacroVSlider",settings.oldMacroVSlider); From efa75a4480d44f21fe579e767c44978b0eeebb33 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 18:40:48 -0500 Subject: [PATCH 035/194] GUI: sub-song info experiment --- src/gui/songInfo.cpp | 49 +++++++--------- src/gui/subSongs.cpp | 133 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index b823d085c..41ec082ac 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -64,6 +64,25 @@ void FurnaceGUI::drawSongInfo() { if (ImGui::InputText("##Author",&e->song.author)) { MARK_MODIFIED; } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Album"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Category",&e->song.category)) { + MARK_MODIFIED; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("System"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##SystemName",&e->song.systemName)) { + MARK_MODIFIED; + updateWindowTitle(); + } + ImGui::EndTable(); } @@ -72,6 +91,7 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + /* ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("TimeBase"); @@ -193,12 +213,14 @@ void FurnaceGUI::drawSongInfo() { ImGui::Text("NTSC"); } } + */ ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Tuning (A-4)"); ImGui::TableNextColumn(); float tune=e->song.tuning; + float avail=ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(avail); if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED if (tune<220.0f) tune=220.0f; @@ -207,33 +229,6 @@ void FurnaceGUI::drawSongInfo() { } ImGui::EndTable(); } - - if (ImGui::TreeNode("Additional Information")) { - if (ImGui::BeginTable("ExtraData",2,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Album"); - ImGui::TableNextColumn(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Category",&e->song.category)) { - MARK_MODIFIED; - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("System"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##SystemName",&e->song.systemName)) { - MARK_MODIFIED; - updateWindowTitle(); - } - ImGui::EndTable(); - } - ImGui::TreePop(); - } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; ImGui::End(); diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index b13b84204..c100600d1 100644 --- a/src/gui/subSongs.cpp +++ b/src/gui/subSongs.cpp @@ -2,6 +2,7 @@ #include "imgui.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include "intConst.h" void FurnaceGUI::drawSubSongs() { if (nextWindow==GUI_WINDOW_SUBSONGS) { @@ -11,7 +12,7 @@ void FurnaceGUI::drawSubSongs() { } if (!subSongsOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Subsongs",&subSongsOpen,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar|globalWinFlags)) { + if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) { char id[1024]; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x); if (e->curSubSong->name.empty()) { @@ -93,6 +94,136 @@ void FurnaceGUI::drawSubSongs() { if (ImGui::InputText("##SubSongName",&e->curSubSong->name)) { MARK_MODIFIED; } + + if (ImGui::BeginTable("OtherSubProps",3,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("TimeBase"); + ImGui::TableNextColumn(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + unsigned char realTB=e->curSubSong->timeBase+1; + if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED + if (realTB<1) realTB=1; + if (realTB>16) realTB=16; + e->curSubSong->timeBase=realTB-1; + } + ImGui::TableNextColumn(); + ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Speed"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->curSubSong->speed1<1) e->curSubSong->speed1=1; + if (e->isPlaying()) play(); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->curSubSong->speed2<1) e->curSubSong->speed2=1; + if (e->isPlaying()) play(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Virtual Tempo"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1; + if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Numerator"); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1; + if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Denominator (set to base tempo)"); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Highlight"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Pattern Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + int patLen=e->curSubSong->patLen; + if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED + if (patLen<1) patLen=1; + if (patLen>256) patLen=256; + e->curSubSong->patLen=patLen; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Song Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + int ordLen=e->curSubSong->ordersLen; + if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED + if (ordLen<1) ordLen=1; + if (ordLen>256) ordLen=256; + e->curSubSong->ordersLen=ordLen; + if (curOrder>=ordLen) { + setOrder(ordLen-1); + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { + tempoView=!tempoView; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz; + if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED + if (tempoView) setHz/=2.5; + if (setHz<10) setHz=10; + if (setHz>999) setHz=999; + e->setSongRate(setHz,setHz<52); + } + if (tempoView) { + ImGui::TableNextColumn(); + ImGui::Text("= %gHz",e->curSubSong->hz); + } else { + if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) { + ImGui::TableNextColumn(); + ImGui::Text("PAL"); + } + if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) { + ImGui::TableNextColumn(); + ImGui::Text("NTSC"); + } + } + + ImGui::EndTable(); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS; ImGui::End(); From dfcb9551e7a371b70da3003ab8a07b46bb0de48e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 18:53:42 -0500 Subject: [PATCH 036/194] GUI: update credits --- src/gui/about.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index d3d1d9e48..210040c8f 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -27,8 +27,8 @@ const char* aboutLine[]={ "", ("Furnace " DIV_VERSION), "", - "the free software multi-system chiptune tracker,", - "compatible with DefleMask modules.", + "the biggest multi-system chiptune tracker!", + "featuring DefleMask song compatibility.", "", "zero disassembly.", "just clean-room design,", @@ -78,6 +78,7 @@ const char* aboutLine[]={ "kleeder", "jaezu", "Laggy", + "LovelyA72", "LunaMoth", "Mahbod Karamoozian", "Miker", From 984d61d0818c8a1ab01f4cd1a83c0284a6b840a9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 18:53:49 -0500 Subject: [PATCH 037/194] GUI: update song information --- src/gui/songInfo.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index 41ec082ac..9cdf508a9 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -229,6 +229,8 @@ void FurnaceGUI::drawSongInfo() { } ImGui::EndTable(); } + + ImGui::TextWrapped("if this feels incomplete, go to Subsongs.\nthe outcome of this Song Information window will be determined by a poll on the Furnace Discord."); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; ImGui::End(); From 26d60dd1079c28d8d63a83246e38f15a0e0e39f3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 19:01:30 -0500 Subject: [PATCH 038/194] GUI: fix per-chan osc debug crash fixes #600 --- src/gui/debugWindow.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 12e775b53..6ad2ddddf 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -220,24 +220,34 @@ void FurnaceGUI::drawDebug() { ImGui::Text("Data"); for (int j=0; jgetChannelCount(system); j++, c++) { + DivDispatchOscBuffer* oscBuf=e->getOscBuffer(c); + if (oscBuf==NULL) { + ImGui::TableNextRow(); + // channel + ImGui::TableNextColumn(); + ImGui::Text("%d",j); + ImGui::TableNextColumn(); + ImGui::Text(""); + continue; + } ImGui::TableNextRow(); // channel ImGui::TableNextColumn(); ImGui::Text("%d",j); // follow ImGui::TableNextColumn(); - ImGui::Checkbox(fmt::sprintf("##%d_OSCFollow_%d",i,c).c_str(),&e->getOscBuffer(c)->follow); + ImGui::Checkbox(fmt::sprintf("##%d_OSCFollow_%d",i,c).c_str(),&oscBuf->follow); // address ImGui::TableNextColumn(); - int needle=e->getOscBuffer(c)->follow?e->getOscBuffer(c)->needle:e->getOscBuffer(c)->followNeedle; - ImGui::BeginDisabled(e->getOscBuffer(c)->follow); + int needle=oscBuf->follow?oscBuf->needle:oscBuf->followNeedle; + ImGui::BeginDisabled(oscBuf->follow); if (ImGui::InputInt(fmt::sprintf("##%d_OSCFollowNeedle_%d",i,c).c_str(),&needle,1,100)) { - e->getOscBuffer(c)->followNeedle=MIN(MAX(needle,0),65535); + oscBuf->followNeedle=MIN(MAX(needle,0),65535); } ImGui::EndDisabled(); // data ImGui::TableNextColumn(); - ImGui::Text("%d",e->getOscBuffer(c)->data[needle]); + ImGui::Text("%d",oscBuf->data[needle]); } ImGui::EndTable(); } From 78b54190047667b6b8b1d4a906c58a31687f064c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 19:08:39 -0500 Subject: [PATCH 039/194] GUI: fix effect list hotkey --- src/gui/guiConst.cpp | 2 +- src/gui/settings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 64e7b18db..9228d1c7f 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -489,9 +489,9 @@ 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), + D("WINDOW_SUBSONGS", "Subsongs", 0), D("WINDOW_FIND", "Find/Replace", FURKMOD_CMD|SDLK_f), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b97390831..3067eed8c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1653,6 +1653,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_OSCILLOSCOPE); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHAN_OSC); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EFFECT_LIST); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_VOL_METER); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_STATS); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_COMPAT_FLAGS); From 8011e7adc765632c3bed4614284b42c6744cae34 Mon Sep 17 00:00:00 2001 From: Aleksi Knutsi <53163105+host12prog@users.noreply.github.com> Date: Sun, 24 Jul 2022 07:13:30 +0700 Subject: [PATCH 040/194] Implement Phase Reset Timer macro for Sound Unit (#573) * Implement Phase Reset Timer Macro * And make the macro actually work * Delete ex4Max variable --- src/engine/platform/su.cpp | 7 +++++++ src/gui/insEdit.cpp | 1 + 2 files changed, 8 insertions(+) diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 48d0ba9a1..c2cd6b25e 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -187,6 +187,13 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chan[i].control=chan[i].std.ex3.val&15; writeControl(i); } + if (chan[i].std.ex4.had) { + chan[i].syncTimer=chan[i].std.ex4.val&65535; + chan[i].timerSync=(chan[i].syncTimer>0); + chWrite(i,0x1e,chan[i].syncTimer&0xff); + chWrite(i,0x1f,chan[i].syncTimer>>8); + writeControlUpper(i); + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 25ee330b6..f0d6d3da3 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3678,6 +3678,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_SU) { macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,suControlBits)); + macroList.push_back(FurnaceGUIMacroDesc("Phase Reset Timer",&ins->std.ex4Macro,0,65535,160,uiColors[GUI_COLOR_MACRO_OTHER])); // again reuse code from resonance macro but use ex4 instead } drawMacros(macroList); From de77d51d7af16116537dc8f70663fbb2d8bcb386 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 21:19:43 -0500 Subject: [PATCH 041/194] GUI: update credits --- src/gui/about.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 210040c8f..00d899f81 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -96,6 +96,7 @@ const char* aboutLine[]={ "-- additional feedback/fixes --", "fd", "GENATARi", + "host12prog", "plane", "TheEssem", "", From e08399156a863cac9da2f4b60829c73f8fdda436 Mon Sep 17 00:00:00 2001 From: Christoph Neidahl Date: Sun, 24 Jul 2022 05:11:30 +0200 Subject: [PATCH 042/194] Haiku support (#596) * Don't apply Wayland videodriver workaround on Haiku * dirent.d_type-less type detecting in IGFD The Dumb Way(tm). `stat`'s `st_mode` should be nicer? * CMake check for dirent.d_type, stat-based fallback * Move config dir setup to separate function Nicer to work with than macro kerfuffle. * Default sysFileDialog to off on Haiku * Logging stuff * Honour CMAKE_INSTALL_BINDIR * Use find_directory on Haiku Includes forgotten configPath line when home==NULL. * Address PR review notes --- CMakeLists.txt | 14 +++++-- extern/igfd/ImGuiFileDialog.cpp | 69 +++++++++++++++++++++---------- src/check/check_dirent_type.c | 7 ++++ src/engine/config.cpp | 73 +++++++++++++++++++++++++++++++++ src/engine/engine.cpp | 72 +------------------------------- src/engine/engine.h | 3 ++ src/gui/settings.cpp | 9 +++- src/main.cpp | 2 +- 8 files changed, 152 insertions(+), 97 deletions(-) create mode 100644 src/check/check_dirent_type.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e7190ece4..5331d8bcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -583,6 +583,13 @@ if (NOT WIN32 AND NOT APPLE) endif() endif() +if (NOT WIN32) + try_compile(HAVE_DIRENT_TYPE ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_dirent_type.c) + if (HAVE_DIRENT_TYPE) + list(APPEND DEPENDENCIES_DEFINES HAVE_DIRENT_TYPE) + endif() +endif() + set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} src/main.cpp) if (USE_BACKWARD) @@ -692,10 +699,9 @@ if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYST endif() if (NOT ANDROID OR TERMUX) - install(TARGETS furnace RUNTIME DESTINATION bin) - if (NOT WIN32 AND NOT APPLE) include(GNUInstallDirs) + install(TARGETS furnace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}) @@ -708,8 +714,10 @@ if (NOT ANDROID OR TERMUX) install(FILES res/icon.iconset/icon_${res}@2x.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}@2/apps) endforeach() install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps) + else() + install(TARGETS furnace RUNTIME DESTINATION bin) endif() - + set(CPACK_PACKAGE_NAME "Furnace") set(CPACK_PACKAGE_VENDOR "tildearrow") set(CPACK_PACKAGE_DESCRIPTION "free and open-source chiptune tracker") diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 776ad3738..63ae3b879 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -58,13 +58,13 @@ SOFTWARE. #ifndef PATH_MAX #define PATH_MAX 260 #endif // PATH_MAX -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__) #define UNIX #define stricmp strcasecmp #include // this option need c++17 #ifndef USE_STD_FILESYSTEM - #include + #include #endif // USE_STD_FILESYSTEM #define PATH_SEP '/' #endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) @@ -1547,28 +1547,53 @@ namespace IGFD for (i = 0; i < n; i++) { struct dirent* ent = files[i]; - + std::string where = path + std::string("/") + std::string(ent->d_name); char fileType = 0; - switch (ent->d_type) +#ifdef HAVE_DIRENT_TYPE + if (ent->d_type != DT_UNKNOWN) { - case DT_REG: - fileType = 'f'; break; - case DT_DIR: - fileType = 'd'; break; - case DT_LNK: - std::string where = path+std::string("/")+std::string(ent->d_name); - DIR* dirTest = opendir(where.c_str()); - if (dirTest==NULL) { - if (errno==ENOTDIR) { - fileType = 'f'; - } else { - fileType = 'l'; - } - } else { - fileType = 'd'; - closedir(dirTest); - } - break; + switch (ent->d_type) + { + case DT_REG: + fileType = 'f'; break; + case DT_DIR: + fileType = 'd'; break; + case DT_LNK: + DIR* dirTest = opendir(where.c_str()); + if (dirTest == NULL) + { + if (errno == ENOTDIR) + { + fileType = 'f'; + } + else + { + fileType = 'l'; + } + } + else + { + fileType = 'd'; + closedir(dirTest); + } + break; + } + } + else +#endif // HAVE_DIRENT_TYPE + { + struct stat filestat; + if (stat(where.c_str(), &filestat) == 0) + { + if (S_ISDIR(filestat.st_mode)) + { + fileType = 'd'; + } + else + { + fileType = 'f'; + } + } } auto fileNameExt = ent->d_name; diff --git a/src/check/check_dirent_type.c b/src/check/check_dirent_type.c new file mode 100644 index 000000000..e65a0d6be --- /dev/null +++ b/src/check/check_dirent_type.c @@ -0,0 +1,7 @@ +#include + +int main(int, char**) { + struct dirent deTest = { }; + unsigned char deType = deTest.d_type; + return 0; +} diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 92866a9f9..f404c0a4f 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -23,11 +23,84 @@ #include #ifdef _WIN32 +#include "winStuff.h" #define CONFIG_FILE "\\furnace.cfg" #else +#ifdef __HAIKU__ +#include +#include +#endif +#include +#include +#include #define CONFIG_FILE "/furnace.cfg" #endif +void DivEngine::initConfDir() { +#ifdef _WIN32 + // maybe move this function in here instead? + configPath=getWinConfigPath(); +#elif defined (IS_MOBILE) + configPath=SDL_GetPrefPath(); +#else +#ifdef __HAIKU__ + char userSettingsDir[PATH_MAX]; + status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX); + if (findUserDir==B_OK) { + configPath=userSettingsDir; + } else { + logW("unable to find/create user settings directory (%s)!",strerror(findUserDir)); + configPath="."; + return; + } +#else + // TODO this should check XDG_CONFIG_HOME first + char* home=getenv("HOME"); + if (home==NULL) { + int uid=getuid(); + struct passwd* entry=getpwuid(uid); + if (entry==NULL) { + logW("unable to determine home directory (%s)!",strerror(errno)); + configPath="."; + return; + } else { + configPath=entry->pw_dir; + } + } else { + configPath=home; + } +#ifdef __APPLE__ + configPath+="/Library/Application Support"; +#else + // FIXME this doesn't honour XDG_CONFIG_HOME *at all* + configPath+="/.config"; +#endif // __APPLE__ +#endif // __HAIKU__ +#ifdef __APPLE__ + configPath+="/Furnace"; +#else + configPath+="/furnace"; +#endif // __APPLE__ + struct stat st; + std::string pathSep="/"; + configPath+=pathSep; + size_t sepPos=configPath.find(pathSep,1); + while (sepPos!=std::string::npos) { + std::string subpath=configPath.substr(0,sepPos++); + if (stat(subpath.c_str(),&st)!=0) { + logI("creating config path element %s ...",subpath.c_str()); + if (mkdir(subpath.c_str(),0755)!=0) { + logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno)); + configPath="."; + return; + } + } + sepPos=configPath.find(pathSep,sepPos); + } + configPath.resize(configPath.length()-pathSep.length()); +#endif // _WIN32 +} + bool DivEngine::saveConf() { configFile=configPath+String(CONFIG_FILE); FILE* f=ps_fopen(configFile.c_str(),"wb"); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f1e8dc043..98947ead3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -27,11 +27,6 @@ #include "../audio/sdlAudio.h" #endif #include -#ifndef _WIN32 -#include -#include -#include -#endif #ifdef HAVE_JACK #include "../audio/jack.h" #endif @@ -2989,36 +2984,6 @@ void DivEngine::quitDispatch() { BUSY_END; } -#define CHECK_CONFIG_DIR_MAC() \ - configPath+="/Library/Application Support/Furnace"; \ - if (stat(configPath.c_str(),&st)<0) { \ - logI("creating config dir..."); \ - if (mkdir(configPath.c_str(),0755)<0) { \ - logW("could not make config dir! (%s)",strerror(errno)); \ - configPath="."; \ - } \ - } - -#define CHECK_CONFIG_DIR() \ - configPath+="/.config"; \ - if (stat(configPath.c_str(),&st)<0) { \ - logI("creating user config dir..."); \ - if (mkdir(configPath.c_str(),0755)<0) { \ - logW("could not make user config dir! (%s)",strerror(errno)); \ - configPath="."; \ - } \ - } \ - if (configPath!=".") { \ - configPath+="/furnace"; \ - if (stat(configPath.c_str(),&st)<0) { \ - logI("creating config dir..."); \ - if (mkdir(configPath.c_str(),0755)<0) { \ - logW("could not make config dir! (%s)",strerror(errno)); \ - configPath="."; \ - } \ - } \ - } - bool DivEngine::initAudioBackend() { // load values if (audioEngine==DIV_AUDIO_NULL) { @@ -3148,45 +3113,12 @@ bool DivEngine::deinitAudioBackend() { return true; } -#ifdef _WIN32 -#include "winStuff.h" -#endif - bool DivEngine::init() { // register systems if (!systemsRegistered) registerSystems(); - + // init config -#ifdef _WIN32 - configPath=getWinConfigPath(); -#elif defined(IS_MOBILE) - configPath=SDL_GetPrefPath("tildearrow","furnace"); -#else - struct stat st; - char* home=getenv("HOME"); - if (home==NULL) { - int uid=getuid(); - struct passwd* entry=getpwuid(uid); - if (entry==NULL) { - logW("unable to determine config directory! (%s)",strerror(errno)); - configPath="."; - } else { - configPath=entry->pw_dir; -#ifdef __APPLE__ - CHECK_CONFIG_DIR_MAC(); -#else - CHECK_CONFIG_DIR(); -#endif - } - } else { - configPath=home; -#ifdef __APPLE__ - CHECK_CONFIG_DIR_MAC(); -#else - CHECK_CONFIG_DIR(); -#endif - } -#endif + initConfDir(); logD("config path: %s",configPath.c_str()); loadConf(); diff --git a/src/engine/engine.h b/src/engine/engine.h index f6567dd3a..0ec74eb5c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -485,6 +485,9 @@ class DivEngine { // returns the minimum VGM version which may carry the specified system, or 0 if none. int minVGMVersion(DivSystem which); + // determine and setup config dir + void initConfDir(); + // save config bool saveConf(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3067eed8c..f67671194 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -39,6 +39,13 @@ #define POWER_SAVE_DEFAULT 0 #endif +#ifdef __HAIKU__ +// NFD doesn't support Haiku +#define SYS_FILE_DIALOG_DEFAULT 0 +#else +#define SYS_FILE_DIALOG_DEFAULT 1 +#endif + const char* mainFonts[]={ "IBM Plex Sans", "Liberation Sans", @@ -2062,7 +2069,7 @@ void FurnaceGUI::syncSettings() { settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); settings.stepOnInsert=e->getConfInt("stepOnInsert",0); settings.unifiedDataView=e->getConfInt("unifiedDataView",0); - settings.sysFileDialog=e->getConfInt("sysFileDialog",1); + settings.sysFileDialog=e->getConfInt("sysFileDialog",SYS_FILE_DIALOG_DEFAULT); settings.roundedWindows=e->getConfInt("roundedWindows",1); settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); diff --git a/src/main.cpp b/src/main.cpp index 970bc784f..2ef45c23d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -295,7 +295,7 @@ int main(int argc, char** argv) { logE("CoInitializeEx failed!"); } #endif -#if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID)) +#if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID) || defined(__HAIKU__)) // workaround for Wayland HiDPI issue if (getenv("SDL_VIDEODRIVER")==NULL) { setenv("SDL_VIDEODRIVER","x11",1); From 617569b6b8b92c95204fdd7f4e570238e1a582a0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 22:19:07 -0500 Subject: [PATCH 043/194] re-enable backward on Windows/macOS --- CMakeLists.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7190ece4..ab1f0f226 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,12 +33,11 @@ if (ANDROID) endif() else() set(USE_RTMIDI_DEFAULT ON) - CHECK_INCLUDE_FILE(execinfo.h EXECINFO_FOUND) - if (EXECINFO_FOUND) + if (WIN32 OR APPLE) set(USE_BACKWARD_DEFAULT ON) else() - find_library(EXECINFO_IS_LIBRARY execinfo) - if (EXECINFO_IS_LIBRARY) + CHECK_INCLUDE_FILE(execinfo.h EXECINFO_FOUND) + if (EXECINFO_FOUND) set(USE_BACKWARD_DEFAULT ON) else() set(USE_BACKWARD_DEFAULT OFF) From 84c955058ba6fc89c6d89fb5152e04867ae77c94 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Jul 2022 22:22:05 -0500 Subject: [PATCH 044/194] GUI: Z280 whoops issue #576 --- src/gui/sysConf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 2cb177984..e54074861 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -594,7 +594,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::RadioButton("14.32MHz (NTSC)",(flags&255)==1)) { copyOfFlags=(flags&(~255))|1; } - if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==3)) { + if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==2)) { copyOfFlags=(flags&(~255))|2; } if (ImGui::RadioButton("16MHz",(flags&255)==3)) { From b48b7c8bc5979b140d4e4e713653c9e6f9d5446b Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 24 Jul 2022 12:23:38 +0900 Subject: [PATCH 045/194] Apply loop end position for generic DAC --- src/engine/platform/pcmdac.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 314ba7dd1..213cb85a3 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -50,12 +50,10 @@ void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t l } else { DivSample* s=parent->getSample(chan.sample); if (s->samples>0) { - if (chan.audPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan.audPos=s->loopStart; - } else { - chan.sample=-1; - } + if (s->isLoopable() && chan.audPos>=s->getEndPosition()) { + chan.audPos=s->loopStart; + } else if (chan.audPos>=s->samples) { + chan.sample=-1; } if (chan.audPossamples) { output=s->data16[chan.audPos]; From 6697be4d9555fbef2433a6eb3d8cadc6cf1916d1 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 24 Jul 2022 13:28:26 +0900 Subject: [PATCH 046/194] Add/Update more presets Williams/Midway ADPCM Sound board Used for conjunction with their Y/T unit, it has ordinary de facto standard OPM+MSM6295 on this era with software controlled DAC from predecessors. Konami Battlantis Used at Battlantis arcade hardware, It is early SB Pro but mono configuration. Sega System 24 This Sega's early arcade system featured to floppy disk and high resolution graphics. Sound hardware is similar as their System 16, but ADPCM is replaced to software controlled DAC. Namco System 86 Predecessor of System 1(a.k.a. System 87), It features similar sound hardware and optional ROM and DAC expansion. Namco Thunder Ceptor Used at Thunder Ceptor, their Front view arcade machine. Namco system 86 and System 1 sound system is directly derived from this machine's sound system. Irem M72 Irem's first arcade system with FM sound system, All released game except R-Type is featured also LOUD software controlled DAC, inherited from their predecessors sound system. --- src/gui/presets.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 3774f232c..ef2236223 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1241,6 +1241,15 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Williams/Midway Y/T unit w/ADPCM sound board", { + // ADPCM sound board + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 15624|(7<<16), // variable via OPM timer? + DIV_SYSTEM_MSM6295, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Konami Gyruss", { DIV_SYSTEM_AY8910, 64, 0, 0, @@ -1261,6 +1270,34 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis", { + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis (drums mode on first OPL2)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis (drums mode on second OPL2)", { + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis (drums mode on both OPL2s)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // "" + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Konami Hexion", { DIV_SYSTEM_SCC, 64, 0, 2, // 1.5MHz (3MHz input) @@ -1326,6 +1363,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 24", { + DIV_SYSTEM_YM2151, 64, 0, 2, // 4MHz + DIV_SYSTEM_PCM_DAC, 64, 0, 61499|(7<<16), // software controlled, variable rate via configurable timers + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Sega System 18", { DIV_SYSTEM_YM2612, 64, 0, 2, // discrete 8MHz YM3438 @@ -1863,7 +1907,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K", { DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1871,7 +1915,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K (extended channel 3)", { DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1879,7 +1923,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K (drums mode)", { DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1887,7 +1931,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K (extended channel 3; drums mode)", { DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1929,10 +1973,27 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Namco System 86", { // without expansion board case; Hopping Mappy, etc + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Namco Thunder Ceptor", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 7999|(7<<16), // M65C02 software driven, correct sample rate? + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Namco System 1", { DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 5999|(7<<16), // sample rate verified from https://github.com/mamedev/mame/blob/master/src/devices/sound/n63701x.cpp + DIV_SYSTEM_PCM_DAC, 64, 0, 5999|(7<<16), // "" 0 } )); @@ -2045,6 +2106,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Irem M72", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 7811|(7<<16), + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); From 588f3f737ca84f2d377e8feb774909da986377d9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 24 Jul 2022 01:57:10 -0500 Subject: [PATCH 047/194] preliminary Future Composer module loading only loads patterns and doesn't deduplicate conversion required to fit in the Furnace format (no transpose ins/note) even the pattern loader itself isn't complete due to how different the format is --- src/engine/engine.h | 2 + src/engine/fileOps.cpp | 230 +++++++++++++++++++++++++++++++++++++++++ src/gui/gui.cpp | 4 +- src/gui/songInfo.cpp | 3 - 4 files changed, 234 insertions(+), 5 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 0ec74eb5c..b5c158058 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -50,6 +50,7 @@ // for imports #define DIV_VERSION_MOD 0xff01 +#define DIV_VERSION_FC 0xff02 enum DivStatusView { DIV_STATUS_NOTHING=0, @@ -397,6 +398,7 @@ class DivEngine { bool loadFur(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len); bool loadFTM(unsigned char* file, size_t len); + bool loadFC(unsigned char* file, size_t len); void loadDMP(SafeReader& reader, std::vector& ret, String& stripPath); void loadTFI(SafeReader& reader, std::vector& ret, String& stripPath); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index e66e2c8d9..aebff43bc 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -28,6 +28,8 @@ #define DIV_DMF_MAGIC ".DelekDefleMask." #define DIV_FUR_MAGIC "-Furnace module-" #define DIV_FTM_MAGIC "FamiTracker Module" +#define DIV_FC13_MAGIC "SMOD" +#define DIV_FC14_MAGIC "FC14" struct InflateBlock { unsigned char* buf; @@ -2259,6 +2261,232 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { return success; } +bool DivEngine::loadFC(unsigned char* file, size_t len) { + struct InvalidHeaderException {}; + bool success=false; + char magic[4]={0,0,0,0}; + SafeReader reader=SafeReader(file,len); + warnings=""; + bool isFC14=false; + unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr; + unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen; + + struct FCSequence { + unsigned char pat[4]; + signed char transpose[4]; + signed char offsetIns[4]; + unsigned char speed; + }; + std::vector seq; + struct FCPattern { + unsigned char note[32]; + unsigned char val[32]; + }; + std::vector pat; + + struct FCSample { + unsigned short loopLen, len, loopStart; + } sample[10]; + + try { + DivSong ds; + ds.tuning=436.0; + ds.version=DIV_VERSION_FC; + ds.linearPitch=0; + ds.noSlidesOnFirstTick=true; + ds.rowResetsArpPos=true; + ds.ignoreJumpAtEnd=false; + + // load here + if (!reader.seek(0,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + reader.read(magic,4); + + if (memcmp(magic,DIV_FC13_MAGIC,4)==0) { + isFC14=false; + } else if (memcmp(magic,DIV_FC14_MAGIC,4)==0) { + isFC14=true; + } else { + logW("the magic isn't complete"); + throw EndOfFileException(&reader,reader.tell()); + } + + ds.systemLen=1; + ds.system[0]=DIV_SYSTEM_DUMMY; + ds.systemVol[0]=64; + ds.systemPan[0]=0; + ds.systemFlags[0]=1|(80<<8); // PAL + ds.systemName="Amiga"; + + seqLen=reader.readI_BE(); + if (seqLen%13) { + logW("sequence length is not multiple of 13 (%d)",seqLen); + //throw EndOfFileException(&reader,reader.tell()); + } + patPtr=reader.readI_BE(); + patLen=reader.readI_BE(); + if (patLen%64) { + logW("pattern length is not multiple of 64 (%d)",patLen); + throw EndOfFileException(&reader,reader.tell()); + } + freqMacroPtr=reader.readI_BE(); + freqMacroLen=reader.readI_BE(); + volMacroPtr=reader.readI_BE(); + volMacroLen=reader.readI_BE(); + samplePtr=reader.readI_BE(); + if (isFC14) { + reader.readI_BE(); // wave len + } else { + sampleLen=reader.readI_BE(); + } + + logD("patPtr: %d",patPtr); + logD("patLen: %d",patLen); + logD("freqMacroPtr: %d",freqMacroPtr); + logD("freqMacroLen: %d",freqMacroLen); + logD("volMacroPtr: %d",volMacroPtr); + logD("volMacroLen: %d",volMacroLen); + logD("samplePtr: %d",samplePtr); + logD("sampleLen: %d",sampleLen); + + // sample info + logD("samples:"); + for (int i=0; i<10; i++) { + sample[i].loopLen=reader.readS_BE(); + sample[i].len=reader.readS_BE(); + sample[i].loopStart=reader.readS_BE(); + + logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen); + } + + // wavetable lengths + if (isFC14) for (int i=0; i<20; i++) { + reader.readS_BE(); + reader.readS_BE(); + } + + // sequences + seqLen/=13; + logD("reading sequences... (%d)",seqLen); + for (unsigned int i=0; iordersLen=seqLen; + ds.subsong[0]->patLen=32; + ds.subsong[0]->hz=50; + ds.subsong[0]->pal=true; + ds.subsong[0]->customTempo=true; + ds.subsong[0]->pat[3].effectCols=3; + ds.subsong[0]->speed1=3; + ds.subsong[0]->speed2=3; + + for (unsigned int i=0; iorders.ord[j][i]=i; + DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true); + if (j==3 && seq[i].speed) { + p->data[0][6]=0x09; + p->data[0][7]=seq[i].speed; + p->data[0][8]=0x0f; + p->data[0][9]=seq[i].speed; + } + + for (int k=0; k<32; k++) { + FCPattern& fp=pat[seq[i].pat[j]]; + if (fp.note[k]>0 && fp.note[k]<0x49) { + short note=(fp.note[k]+seq[i].transpose[j])%12; + short octave=2+((fp.note[k]+seq[i].transpose[j])/12); + if (fp.note[k]>=0x3d) octave-=6; + if (note==0) { + note=12; + octave--; + } + octave&=0xff; + p->data[k][0]=note; + p->data[k][1]=octave; + if (fp.val[k]) { + if (fp.val[k]&0xe0) { + + } else { + p->data[k][2]=fp.val[k]-1; + } + } + } + } + } + } + + if (active) quitDispatch(); + BUSY_BEGIN_SOFT; + saveLock.lock(); + song.unload(); + song=ds; + changeSong(0); + recalcChans(); + saveLock.unlock(); + BUSY_END; + if (active) { + initDispatch(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; + } + success=true; + } catch (EndOfFileException& e) { + //logE("premature end of file!"); + lastError="incomplete file"; + } catch (InvalidHeaderException& e) { + //logE("invalid header!"); + lastError="invalid header!"; + } + return success; +} + #define CHECK_BLOCK_VERSION(x) \ if (blockVersion>x) { \ logE("incompatible block version %d for %s!",blockVersion,blockName); \ @@ -2731,6 +2959,8 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return loadFTM(file,len); } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); + } else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) { + return loadFC(file,len); } // step 3: try loading as .mod diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 97c1d78e1..2c80c5fbe 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1226,9 +1226,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", - {"compatible files", "*.fur *.dmf *.mod", + {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod", "all files", ".*"}, - "compatible files{.fur,.dmf,.mod},.*", + "compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod},.*", workingDirSong, dpiScale ); diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index 9cdf508a9..f1428db5e 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -91,7 +91,6 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); - /* ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("TimeBase"); @@ -213,14 +212,12 @@ void FurnaceGUI::drawSongInfo() { ImGui::Text("NTSC"); } } - */ ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Tuning (A-4)"); ImGui::TableNextColumn(); float tune=e->song.tuning; - float avail=ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(avail); if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED if (tune<220.0f) tune=220.0f; From ffe06013d7a23f974b0af1001f9b8cabb6583957 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 24 Jul 2022 01:58:14 -0500 Subject: [PATCH 048/194] GUI: fix preset typo --- src/gui/presets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index ef2236223..f8e7a522c 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1976,7 +1976,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "Namco System 86", { // without expansion board case; Hopping Mappy, etc DIV_SYSTEM_YM2151, 64, 0, 0, - DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0 + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, 0 } )); From 9a0609ae1a6bc666449891cad9b956d4c9a1e8e9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 24 Jul 2022 02:24:57 -0500 Subject: [PATCH 049/194] fix build... --- src/engine/fileOps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index aebff43bc..71b8f90d1 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2340,6 +2340,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { samplePtr=reader.readI_BE(); if (isFC14) { reader.readI_BE(); // wave len + sampleLen=0; } else { sampleLen=reader.readI_BE(); } From 1d777196408652036df388368b612ba579087247 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 24 Jul 2022 02:45:21 -0500 Subject: [PATCH 050/194] prevent exception in MIDI in/out from crashing --- src/audio/rtmidi.cpp | 49 +++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/audio/rtmidi.cpp b/src/audio/rtmidi.cpp index 31a3e66ae..dd8a0a75e 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -46,23 +46,28 @@ String sanitizePortName(const String& name) { bool TAMidiInRtMidi::gather() { std::vector msg; if (port==NULL) return false; - while (true) { - TAMidiMessage m; - double t=port->getMessage(&msg); - if (msg.empty()) break; + try { + while (true) { + TAMidiMessage m; + double t=port->getMessage(&msg); + if (msg.empty()) break; - // parse message - m.time=t; - m.type=msg[0]; - if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { - memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); - } else if (m.type==TA_MIDI_SYSEX) { - m.sysExData.reset(new unsigned char[msg.size()]); - m.sysExLen=msg.size(); - logD("got a SysEx of length %ld!",msg.size()); - memcpy(m.sysExData.get(),msg.data(),msg.size()); + // parse message + m.time=t; + m.type=msg[0]; + if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { + memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); + } else if (m.type==TA_MIDI_SYSEX) { + m.sysExData.reset(new unsigned char[msg.size()]); + m.sysExLen=msg.size(); + logD("got a SysEx of length %ld!",msg.size()); + memcpy(m.sysExData.get(),msg.data(),msg.size()); + } + queue.push(m); } - queue.push(m); + } catch (RtMidiError& e) { + logE("MIDI input error! %s",e.what()); + return false; } return true; } @@ -180,7 +185,12 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) { return false; } len=what.sysExLen; - port->sendMessage(what.sysExData.get(),len); + try { + port->sendMessage(what.sysExData.get(),len); + } catch (RtMidiError& e) { + logE("MIDI output error! %s",e.what()); + return false; + } return true; break; case TA_MIDI_MTC_FRAME: @@ -194,7 +204,12 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) { len=1; break; } - port->sendMessage((const unsigned char*)&what.type,len); + try { + port->sendMessage((const unsigned char*)&what.type,len); + } catch (RtMidiError& e) { + logE("MIDI output error! %s",e.what()); + return false; + } return true; } From b75787603aec96eff8896c4a5b560211eb1b9357 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 24 Jul 2022 02:52:39 -0500 Subject: [PATCH 051/194] I missed something --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index 537150215..ca8092a5a 100644 --- a/papers/format.md +++ b/papers/format.md @@ -244,6 +244,7 @@ size | description | - 0xc2: Neo Geo CSM (YM2610) - 18 channels | - 0xc3: OPN CSM - 10 channels | - 0xc4: PC-98 CSM - 20 channels + | - 0xc5: YM2610B CSM - 20 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels From 542a46e89b4a90fe73fb1b8e9870e5347c92f3bf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 24 Jul 2022 03:41:01 -0500 Subject: [PATCH 052/194] remove log spam (hopefully) --- src/audio/rtmidi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audio/rtmidi.cpp b/src/audio/rtmidi.cpp index dd8a0a75e..258f69292 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -67,6 +67,7 @@ bool TAMidiInRtMidi::gather() { } } catch (RtMidiError& e) { logE("MIDI input error! %s",e.what()); + closeDevice(); return false; } return true; From 3183400019d6c822fa6d687b72d9ed74a8b49070 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 25 Jul 2022 16:21:39 -0500 Subject: [PATCH 053/194] it appears SDL2 takes over interrupt in console mode --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2ef45c23d..9ff5b9934 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,7 @@ #include #include #include -#ifdef HAVE_GUI +#ifdef HAVE_SDL2 #include "SDL_events.h" #endif #include "ta-log.h" @@ -467,7 +467,7 @@ int main(int argc, char** argv) { if (consoleMode) { logI("playing..."); e.play(); -#ifdef HAVE_GUI +#ifdef HAVE_SDL2 SDL_Event ev; while (true) { SDL_WaitEvent(&ev); From 83386d082d1da5609fc4d7d9f2ba673750ca9020 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 25 Jul 2022 17:23:56 -0500 Subject: [PATCH 054/194] add a proper CLI featuring skip order (left/right) and pause (space)! currently available on macOS and Linux only. --- CMakeLists.txt | 6 +- src/cli/cli.cpp | 137 ++++++++++++++++++++++++++++++++++++++++ src/cli/cli.h | 55 ++++++++++++++++ src/engine/playback.cpp | 6 +- src/main.cpp | 40 ++++++++---- 5 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 src/cli/cli.cpp create mode 100644 src/cli/cli.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 861392563..78792c983 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -478,6 +478,10 @@ if (WIN32) list(APPEND ENGINE_SOURCES res/furnace.rc) endif() +set(CLI_SOURCES +src/cli/cli.cpp +) + set(GUI_SOURCES extern/imgui_patched/imgui.cpp extern/imgui_patched/imgui_draw.cpp @@ -589,7 +593,7 @@ if (NOT WIN32) endif() endif() -set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} src/main.cpp) +set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} ${CLI_SOURCES} src/main.cpp) if (USE_BACKWARD) list(APPEND USED_SOURCES src/backtrace.cpp) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp new file mode 100644 index 000000000..956796001 --- /dev/null +++ b/src/cli/cli.cpp @@ -0,0 +1,137 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "cli.h" +#include "../ta-log.h" + +bool cliQuit=false; + +static void handleTerm(int) { + cliQuit=true; +} + +void FurnaceCLI::bindEngine(DivEngine* eng) { + e=eng; +} + +bool FurnaceCLI::loop() { + bool escape=false; + bool escapeSecondStage=false; + while (!cliQuit) { + unsigned char c; + if (read(STDIN_FILENO,&c,1)<=0) continue; + if (escape) { + if (escapeSecondStage) { + switch (c) { + case 'C': // right + e->setOrder(e->getOrder()+1); + escape=false; + escapeSecondStage=false; + break; + case 'D': // left + e->setOrder(e->getOrder()-1); + escape=false; + escapeSecondStage=false; + break; + default: + escape=false; + escapeSecondStage=false; + break; + } + } else { + switch (c) { + case '[': case 'O': + escapeSecondStage=true; + break; + default: + escape=false; + break; + } + } + } else { + switch (c) { + case 0x1b: // + escape=true; + break; + case 'h': // left + e->setOrder(e->getOrder()-1); + break; + case 'l': // right + e->setOrder(e->getOrder()+1); + break; + case ' ': + if (e->isHalted()) { + e->resume(); + } else { + e->halt(); + } + break; + } + } + } + printf("\n"); + return true; +} + +bool FurnaceCLI::finish() { + if (tcsetattr(0,TCSAFLUSH,&termpropold)!=0) { + logE("could not set console attributes!"); + logE("you may have to run `reset` on your terminal."); + return false; + } + return true; +} + +// blatantly copied from tildearrow/tfmxplay +bool FurnaceCLI::init() { +#ifdef _WIN32 + winin=GetStdHandle(STD_INPUT_HANDLE); + winout=GetStdHandle(STD_OUTPUT_HANDLE); + int termprop=0; + int termpropi=0; + GetConsoleMode(winout,(LPDWORD)&termprop); + GetConsoleMode(winin,(LPDWORD)&termpropi); + termprop|=ENABLE_VIRTUAL_TERMINAL_PROCESSING; + termpropi&=~ENABLE_LINE_INPUT; + SetConsoleMode(winout,termprop); + SetConsoleMode(winin,termpropi); +#else + sigemptyset(&intsa.sa_mask); + intsa.sa_flags=0; + intsa.sa_handler=handleTerm; + sigaction(SIGINT,&intsa,NULL); + + if (tcgetattr(0,&termprop)!=0) { + logE("could not get console attributes!"); + return false; + } + memcpy(&termpropold,&termprop,sizeof(struct termios)); + termprop.c_lflag&=~ECHO; + termprop.c_lflag&=~ICANON; + if (tcsetattr(0,TCSAFLUSH,&termprop)!=0) { + logE("could not set console attributes!"); + return false; + } +#endif + return true; +} + +FurnaceCLI::FurnaceCLI(): + e(NULL) { +} diff --git a/src/cli/cli.h b/src/cli/cli.h new file mode 100644 index 000000000..0df6658e3 --- /dev/null +++ b/src/cli/cli.h @@ -0,0 +1,55 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _FUR_CLI_H +#define _FUR_CLI_H + +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +#include "../engine/engine.h" + +class FurnaceCLI { + DivEngine* e; + +#ifdef _WIN32 + HANDLE winin; + HANDLE winout; +#else + struct sigaction intsa; + struct termios termprop; + struct termios termpropold; +#endif + + public: + void bindEngine(DivEngine* eng); + bool loop(); + bool finish(); + bool init(); + FurnaceCLI(); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index bff0c3c3f..e9d5aa952 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -201,7 +201,7 @@ const char* formatNote(unsigned char note, unsigned char octave) { int DivEngine::dispatchCmd(DivCommand c) { if (view==DIV_STATUS_COMMANDS) { - printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2); + if (!skipping) printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2); } totalCmds++; if (cmdStreamEnabled && cmdStream.size()<2000) { @@ -771,7 +771,7 @@ void DivEngine::nextRow() { static char pb1[4096]; static char pb2[4096]; static char pb3[4096]; - if (view==DIV_STATUS_PATTERN) { + if (view==DIV_STATUS_PATTERN && !skipping) { strcpy(pb1,""); strcpy(pb3,""); for (int i=0; i %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 (consoleMode && subticks<=1 && !skipping) 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; diff --git a/src/main.cpp b/src/main.cpp index 9ff5b9934..f25d08e77 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,8 @@ #include #endif +#include "cli/cli.h" + #ifdef HAVE_GUI #include "gui/gui.h" #endif @@ -46,6 +48,8 @@ DivEngine e; FurnaceGUI g; #endif +FurnaceCLI cli; + String outName; String vgmOutName; int loops=1; @@ -465,25 +469,39 @@ int main(int argc, char** argv) { } if (consoleMode) { + bool cliSuccess=false; + cli.bindEngine(&e); + if (!cli.init()) { + reportError("error while starting CLI!"); + } else { + cliSuccess=true; + } logI("playing..."); e.play(); + if (cliSuccess) { + cli.loop(); + cli.finish(); + e.quit(); + return 0; + } else { #ifdef HAVE_SDL2 - SDL_Event ev; - while (true) { - SDL_WaitEvent(&ev); - if (ev.type==SDL_QUIT) break; - } - e.quit(); - return 0; + SDL_Event ev; + while (true) { + SDL_WaitEvent(&ev); + if (ev.type==SDL_QUIT) break; + } + e.quit(); + return 0; #else - while (true) { + while (true) { #ifdef _WIN32 - Sleep(500); + Sleep(500); #else - usleep(500000); + usleep(500000); +#endif + } #endif } -#endif } #ifdef HAVE_GUI From b0c2b10135d4e8daa0b3462300e25666ccb85416 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 25 Jul 2022 18:32:26 -0500 Subject: [PATCH 055/194] GUI: add "scale" option to find/replace --- src/gui/findReplace.cpp | 62 ++++++++++++++++++++++++++++++++++++++++- src/gui/gui.h | 1 + 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp index e982882d4..559d7f7ff 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -39,6 +39,7 @@ const char* queryReplaceModes[GUI_QUERY_REPLACE_MAX]={ "set", "add", "add (overflow)", + "scale", "clear" }; @@ -292,6 +293,8 @@ void FurnaceGUI::doReplace() { } } break; + case GUI_QUERY_REPLACE_SCALE: + break; case GUI_QUERY_REPLACE_CLEAR: p->data[i.y][0]=0; p->data[i.y][1]=0; @@ -314,6 +317,13 @@ void FurnaceGUI::doReplace() { case GUI_QUERY_REPLACE_ADD_OVERFLOW: if (p->data[i.y][2]>=0) p->data[i.y][2]=(p->data[i.y][2]+queryReplaceIns)&0xff; break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][2]>=0) { + p->data[i.y][2]=(p->data[i.y][2]*queryReplaceIns)/100; + if (p->data[i.y][2]<0) p->data[i.y][2]=0; + if (p->data[i.y][2]>255) p->data[i.y][2]=255; + } + break; case GUI_QUERY_REPLACE_CLEAR: p->data[i.y][2]=-1; break; @@ -335,6 +345,13 @@ void FurnaceGUI::doReplace() { case GUI_QUERY_REPLACE_ADD_OVERFLOW: if (p->data[i.y][3]>=0) p->data[i.y][3]=(p->data[i.y][3]+queryReplaceVol)&0xff; break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][3]>=0) { + p->data[i.y][3]=(p->data[i.y][3]*queryReplaceVol)/100; + if (p->data[i.y][3]<0) p->data[i.y][3]=0; + if (p->data[i.y][3]>255) p->data[i.y][3]=255; + } + break; case GUI_QUERY_REPLACE_CLEAR: p->data[i.y][3]=-1; break; @@ -402,6 +419,13 @@ void FurnaceGUI::doReplace() { case GUI_QUERY_REPLACE_ADD_OVERFLOW: if (p->data[i.y][4+pos*2]>=0) p->data[i.y][4+pos*2]=(p->data[i.y][4+pos*2]+queryReplaceEffect[j])&0xff; break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][4+pos*2]>=0) { + p->data[i.y][4+pos*2]=(p->data[i.y][4+pos*2]*queryReplaceEffect[j])/100; + if (p->data[i.y][4+pos*2]<0) p->data[i.y][4+pos*2]=0; + if (p->data[i.y][4+pos*2]>255) p->data[i.y][4+pos*2]=255; + } + break; case GUI_QUERY_REPLACE_CLEAR: p->data[i.y][4+pos*2]=-1; break; @@ -423,6 +447,13 @@ void FurnaceGUI::doReplace() { case GUI_QUERY_REPLACE_ADD_OVERFLOW: if (p->data[i.y][5+pos*2]>=0) p->data[i.y][5+pos*2]=(p->data[i.y][5+pos*2]+queryReplaceEffectVal[j])&0xff; break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][5+pos*2]>=0) { + p->data[i.y][5+pos*2]=(p->data[i.y][5+pos*2]*queryReplaceEffectVal[j])/100; + if (p->data[i.y][5+pos*2]<0) p->data[i.y][5+pos*2]=0; + if (p->data[i.y][5+pos*2]>255) p->data[i.y][5+pos*2]=255; + } + break; case GUI_QUERY_REPLACE_CLEAR: p->data[i.y][5+pos*2]=-1; break; @@ -919,6 +950,8 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceNote<-180) queryReplaceNote=-180; if (queryReplaceNote>180) queryReplaceNote=180; } + } else if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SCALE) { + ImGui::Text("INVALID"); } ImGui::EndDisabled(); @@ -941,6 +974,13 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceIns<-255) queryReplaceIns=-255; if (queryReplaceIns>255) queryReplaceIns=255; } + } else if (queryReplaceInsMode==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>400) queryReplaceIns=400; + if (ImGui::InputInt("##IRValue",&queryReplaceIns,1,12)) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>400) queryReplaceIns=400; + } } ImGui::EndDisabled(); @@ -963,6 +1003,13 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceVol<-255) queryReplaceVol=-255; if (queryReplaceVol>255) queryReplaceVol=255; } + } else if (queryReplaceVolMode==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>400) queryReplaceVol=400; + if (ImGui::InputInt("##VRValue",&queryReplaceVol,1,12)) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>400) queryReplaceVol=400; + } } ImGui::EndDisabled(); @@ -987,6 +1034,13 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceEffect[i]<-255) queryReplaceEffect[i]=-255; if (queryReplaceEffect[i]>255) queryReplaceEffect[i]=255; } + } else if (queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceEffect[i]<0) queryReplaceEffect[i]=0; + if (queryReplaceEffect[i]>400) queryReplaceEffect[i]=400; + if (ImGui::InputInt("##ERValue",&queryReplaceEffect[i],1,12)) { + if (queryReplaceEffect[i]<0) queryReplaceEffect[i]=0; + if (queryReplaceEffect[i]>400) queryReplaceEffect[i]=400; + } } ImGui::EndDisabled(); @@ -1009,10 +1063,16 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceEffectVal[i]<-255) queryReplaceEffectVal[i]=-255; if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; } + } else if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; + if (queryReplaceEffectVal[i]>400) queryReplaceEffectVal[i]=400; + if (ImGui::InputInt("##ERValueV",&queryReplaceEffectVal[i],1,12)) { + if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; + if (queryReplaceEffectVal[i]>400) queryReplaceEffectVal[i]=400; + } } ImGui::EndDisabled(); - ImGui::PopID(); } diff --git a/src/gui/gui.h b/src/gui/gui.h index bd2a5494c..8fcf38b30 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -885,6 +885,7 @@ enum FurnaceGUIFindQueryReplaceModes { GUI_QUERY_REPLACE_SET=0, GUI_QUERY_REPLACE_ADD, GUI_QUERY_REPLACE_ADD_OVERFLOW, + GUI_QUERY_REPLACE_SCALE, GUI_QUERY_REPLACE_CLEAR, GUI_QUERY_REPLACE_MAX From 09e457003b31907a2e4047b3794cf3827420569c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 25 Jul 2022 18:41:47 -0500 Subject: [PATCH 056/194] add option for soft-clipping --- src/engine/engine.cpp | 1 + src/engine/engine.h | 1 + src/engine/playback.cpp | 8 ++++++++ src/gui/gui.h | 2 ++ src/gui/settings.cpp | 8 ++++++++ 5 files changed, 20 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 98947ead3..40a51bdc2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2996,6 +2996,7 @@ bool DivEngine::initAudioBackend() { lowQuality=getConfInt("audioQuality",0); forceMono=getConfInt("forceMono",0); + clampSamples=getConfInt("clampSamples",0); lowLatency=getConfInt("lowLatency",0); metroVol=(float)(getConfInt("metroVol",100))/100.0f; if (metroVol<0.0f) metroVol=0.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index b5c158058..482b58dcb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -298,6 +298,7 @@ class DivEngine { bool stopExport; bool halted; bool forceMono; + bool clampSamples; bool cmdStreamEnabled; bool softLocked; bool firstTick; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index e9d5aa952..662e2ddb4 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1413,6 +1413,14 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi out[1][i]=out[0][i]; } } + if (clampSamples) { + for (size_t i=0; i1.0) out[0][i]=1.0; + if (out[1][i]<-1.0) out[1][i]=-1.0; + if (out[1][i]>1.0) out[1][i]=1.0; + } + } isBusy.unlock(); std::chrono::steady_clock::time_point ts_processEnd=std::chrono::steady_clock::now(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 8fcf38b30..875501d24 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1094,6 +1094,7 @@ class FurnaceGUI { int dragMovesSelection; int unsignedDetune; int noThreadedInput; + int clampSamples; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1199,6 +1200,7 @@ class FurnaceGUI { dragMovesSelection(1), unsignedDetune(0), noThreadedInput(0), + clampSamples(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f67671194..eae165d27 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -659,6 +659,11 @@ void FurnaceGUI::drawSettings() { settings.forceMono=forceMonoB; } + bool clampSamplesB=settings.clampSamples; + if (ImGui::Checkbox("Software clipping",&clampSamplesB)) { + settings.clampSamples=clampSamplesB; + } + TAAudioDesc& audioWant=e->getAudioDescWant(); TAAudioDesc& audioGot=e->getAudioDescGot(); @@ -2118,6 +2123,7 @@ void FurnaceGUI::syncSettings() { settings.unsignedDetune=e->getConfInt("unsignedDetune",0); settings.noThreadedInput=e->getConfInt("noThreadedInput",0); settings.initialSysName=e->getConfString("initialSysName",""); + settings.clampSamples=e->getConfInt("clampSamples",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2205,6 +2211,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.dragMovesSelection,0,2); clampSetting(settings.unsignedDetune,0,1); clampSetting(settings.noThreadedInput,0,1); + clampSetting(settings.clampSamples,0,1); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2343,6 +2350,7 @@ void FurnaceGUI::commitSettings() { e->setConf("dragMovesSelection",settings.dragMovesSelection); e->setConf("unsignedDetune",settings.unsignedDetune); e->setConf("noThreadedInput",settings.noThreadedInput); + e->setConf("clampSamples",settings.clampSamples); // colors for (int i=0; i Date: Mon, 25 Jul 2022 19:09:42 -0500 Subject: [PATCH 057/194] FC loader: read slides --- src/engine/fileOps.cpp | 43 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 71b8f90d1..30a9db943 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2292,10 +2292,10 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_FC; - ds.linearPitch=0; - ds.noSlidesOnFirstTick=true; - ds.rowResetsArpPos=true; - ds.ignoreJumpAtEnd=false; + //ds.linearPitch=0; + //ds.noSlidesOnFirstTick=true; + //ds.rowResetsArpPos=true; + //ds.ignoreJumpAtEnd=false; // load here if (!reader.seek(0,SEEK_SET)) { @@ -2436,6 +2436,9 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { p->data[0][9]=seq[i].speed; } + bool ignoreNext=false; + bool isSliding=false; + for (int k=0; k<32; k++) { FCPattern& fp=pat[seq[i].pat[j]]; if (fp.note[k]>0 && fp.note[k]<0x49) { @@ -2449,9 +2452,37 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { octave&=0xff; p->data[k][0]=note; p->data[k][1]=octave; - if (fp.val[k]) { + if (isSliding) { + isSliding=false; + p->data[k][4]=2; + p->data[k][5]=0; + } + } + if (fp.val[k]) { + if (ignoreNext) { + ignoreNext=false; + } else { if (fp.val[k]&0xe0) { - + if (fp.val[k]&0x40) { + p->data[k][4]=2; + p->data[k][5]=0; + isSliding=false; + } else if (fp.val[k]&0x80) { + isSliding=true; + if (k<31) { + if (fp.val[k+1]&0x20) { + p->data[k][4]=2; + p->data[k][5]=fp.val[k+1]&0x1f; + } else { + p->data[k][4]=1; + p->data[k][5]=fp.val[k+1]&0x1f; + } + ignoreNext=true; + } else { + p->data[k][4]=2; + p->data[k][5]=0; + } + } } else { p->data[k][2]=fp.val[k]-1; } From 8d17500315a1fb15de0b297e4975b27957e65f77 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 25 Jul 2022 19:45:49 -0500 Subject: [PATCH 058/194] jlhafasjkhdgkdhjasfd --- src/engine/fileOps.cpp | 164 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 7 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 30a9db943..6a6685b20 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2268,9 +2268,12 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { SafeReader reader=SafeReader(file,len); warnings=""; bool isFC14=false; - unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr; + unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr, wavePtr; unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen; + unsigned char waveLen[40]; + unsigned char waveLoopLen[40]; + struct FCSequence { unsigned char pat[4]; signed char transpose[4]; @@ -2283,6 +2286,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { unsigned char val[32]; }; std::vector pat; + struct FCMacro { + unsigned char val[64]; + }; + std::vector freqMacros; + std::vector volMacros; struct FCSample { unsigned short loopLen, len, loopStart; @@ -2335,14 +2343,23 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } freqMacroPtr=reader.readI_BE(); freqMacroLen=reader.readI_BE(); + if (freqMacroLen%64) { + logW("freq sequence length is not multiple of 64 (%d)",freqMacroLen); + //throw EndOfFileException(&reader,reader.tell()); + } volMacroPtr=reader.readI_BE(); volMacroLen=reader.readI_BE(); + if (volMacroLen%64) { + logW("vol sequence length is not multiple of 64 (%d)",volMacroLen); + //throw EndOfFileException(&reader,reader.tell()); + } samplePtr=reader.readI_BE(); if (isFC14) { - reader.readI_BE(); // wave len + wavePtr=reader.readI_BE(); // wave len sampleLen=0; } else { sampleLen=reader.readI_BE(); + wavePtr=0; } logD("patPtr: %d",patPtr); @@ -2352,7 +2369,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { logD("volMacroPtr: %d",volMacroPtr); logD("volMacroLen: %d",volMacroLen); logD("samplePtr: %d",samplePtr); - logD("sampleLen: %d",sampleLen); + if (isFC14) { + logD("wavePtr: %d",wavePtr); + } else { + logD("sampleLen: %d",sampleLen); + } // sample info logD("samples:"); @@ -2365,9 +2386,14 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } // wavetable lengths - if (isFC14) for (int i=0; i<20; i++) { - reader.readS_BE(); - reader.readS_BE(); + if (isFC14) { + logD("wavetables:"); + for (int i=0; i<40; i++) { + waveLen[i]=reader.readC(); + waveLoopLen[i]=reader.readC(); + + logD("- %d: %.4x (%.4x)",i,waveLen[i],waveLoopLen[i]); + } } // sequences @@ -2413,7 +2439,96 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { pat.push_back(p); } - // TODO: read the rest + // freq sequences + if (!reader.seek(freqMacroPtr,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + freqMacroLen/=64; + logD("reading freq sequences... (%d)",freqMacroLen); + for (unsigned int i=0; idepth=DIV_SAMPLE_DEPTH_8BIT; + if (sample[i].len>0) { + s->init(sample[i].len); + } + s->loopStart=sample[i].loopStart*2; + s->loopEnd=(sample[i].loopStart+sample[i].loopLen)*2; + reader.read(s->data8,sample[i].len); + ds.sample.push_back(s); + } + ds.sampleLen=(int)ds.sample.size(); + + // wavetables + if (isFC14) { + if (!reader.seek(wavePtr,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + logD("reading wavetables..."); + for (int i=0; i<40; i++) { + DivWavetable* w=new DivWavetable; + w->min=0; + w->max=255; + w->len=MIN(256,waveLoopLen[i]*2); + + for (int i=0; i<256; i++) { + w->data[i]=128; + } + + if (waveLen[i]>0) { + signed char* waveArray=new signed char[waveLen[i]*2]; + reader.read(waveArray,waveLen[i]*2); + int howMany=MIN(waveLen[i]*2,waveLoopLen[i]*2); + if (howMany>256) howMany=256; + for (int i=0; idata[i]=waveArray[i]; + } + delete[] waveArray; + } else { + w->len=32; + for (int i=0; i<32; i++) { + w->data[i]=(i*255)/31; + } + } + + ds.wave.push_back(w); + } + } + ds.waveLen=(int)ds.wave.size(); // convert ds.subsong[0]->ordersLen=seqLen; @@ -2492,6 +2607,41 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } + // convert instruments + for (unsigned int i=0; itype=DIV_INS_AMIGA; + ins->name=fmt::sprintf("Instrument %d",i); + unsigned char seqSpeed=m.val[0]; + unsigned char freqMacro=m.val[1]; + unsigned char vibSpeed=m.val[2]; + unsigned char vibDepth=m.val[3]; + unsigned char vibDelay=m.val[4]; + + ins->std.volMacro.len=0; + for (int j=5; j<64; j++) { + unsigned char pos=ins->std.volMacro.len; + if (++ins->std.volMacro.len>=128) break; + if (m.val[j]==0xe1) { + + } else if (m.val[j]==0xe0) { + + } else if (m.val[j]==0xe8) { + + } else if (m.val[j]==0xe9) { + + } else { + ins->std.volMacro.val[ins->std.volMacro.len]=m.val[j]; + } + + } + + ds.ins.push_back(ins); + } + ds.insLen=(int)ds.ins.size(); + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); From 280592cf333e28853657fa05e3992c193346bb76 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 01:42:34 -0500 Subject: [PATCH 059/194] fix build --- src/engine/fileOps.cpp | 112 ++++++++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 6a6685b20..d480167ee 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2324,7 +2324,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } ds.systemLen=1; - ds.system[0]=DIV_SYSTEM_DUMMY; + ds.system[0]=DIV_SYSTEM_AMIGA; ds.systemVol[0]=64; ds.systemPan[0]=0; ds.systemFlags[0]=1|(80<<8); // PAL @@ -2515,7 +2515,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { int howMany=MIN(waveLen[i]*2,waveLoopLen[i]*2); if (howMany>256) howMany=256; for (int i=0; idata[i]=waveArray[i]; + w->data[i]=waveArray[i]+128; } delete[] waveArray; } else { @@ -2599,7 +2599,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } } else { - p->data[k][2]=fp.val[k]-1; + p->data[k][2]=fp.val[k]&0x3f; } } } @@ -2614,28 +2614,104 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ins->type=DIV_INS_AMIGA; ins->name=fmt::sprintf("Instrument %d",i); - unsigned char seqSpeed=m.val[0]; + ins->amiga.useWave=true; + //unsigned char seqSpeed=m.val[0]; unsigned char freqMacro=m.val[1]; - unsigned char vibSpeed=m.val[2]; - unsigned char vibDepth=m.val[3]; - unsigned char vibDelay=m.val[4]; + //unsigned char vibSpeed=m.val[2]; + //unsigned char vibDepth=m.val[3]; + //unsigned char vibDelay=m.val[4]; + unsigned char lastVal=m.val[5]; + + signed char loopMap[64]; + memset(loopMap,-1,64); + + // volume sequence ins->std.volMacro.len=0; for (int j=5; j<64; j++) { - unsigned char pos=ins->std.volMacro.len; - if (++ins->std.volMacro.len>=128) break; - if (m.val[j]==0xe1) { - - } else if (m.val[j]==0xe0) { - - } else if (m.val[j]==0xe8) { - - } else if (m.val[j]==0xe9) { - + loopMap[j]=ins->std.volMacro.len; + if (m.val[j]==0xe1) { // end + break; + } else if (m.val[j]==0xe0) { // loop + if (++j>=64) break; + ins->std.volMacro.loop=loopMap[m.val[j]&63]; + break; + } else if (m.val[j]==0xe8) { // sustain + if (++j>=64) break; + unsigned char susTime=m.val[j]; + // TODO: <= or std.volMacro.val[ins->std.volMacro.len]=lastVal; + if (++ins->std.volMacro.len>=128) break; + } + } else if (m.val[j]==0xe9 || m.val[j]==0xea) { // volume slide + if (++j>=64) break; + signed char slideStep=m.val[j]; + if (++j>=64) break; + unsigned char slideTime=m.val[j]; + // TODO: <= or 0) { + lastVal+=slideStep; + if (lastVal>63) lastVal=63; + } else { + if (-slideStep>lastVal) { + lastVal=0; + } else { + lastVal-=slideStep; + } + } + ins->std.volMacro.val[ins->std.volMacro.len]=lastVal; + if (++ins->std.volMacro.len>=128) break; + } } else { ins->std.volMacro.val[ins->std.volMacro.len]=m.val[j]; + lastVal=m.val[j]; + if (++ins->std.volMacro.len>=128) break; + } + } + + // frequency sequence + lastVal=0; + ins->amiga.initSample=-1; + if (freqMacro=64) break; + unsigned char wave=fm.val[j]; + if (wave<10) { // sample + if (ins->amiga.initSample==-1) { + ins->amiga.initSample=wave; + ins->amiga.useWave=false; + } + } else { // waveform + ins->std.waveMacro.val[ins->std.waveMacro.len]=wave-10; + ins->std.waveMacro.open=true; + lastVal=wave; + if (++ins->std.waveMacro.len>=128) break; + if (++ins->std.arpMacro.len>=128) break; + } + } else if (fm.val[j]==0xe0) { + logV("unhandled loop!"); + } else if (fm.val[j]==0xe3) { + logV("unhandled vibrato!"); + } else if (fm.val[j]==0xe8) { + logV("unhandled sustain!"); + } else if (fm.val[j]==0xe7) { + logV("unhandled newseq!"); + } else if (fm.val[j]==0xe9) { + logV("unhandled pack!"); + } else if (fm.val[j]==0xea) { + logV("unhandled pitch!"); + } else { + ins->std.arpMacro.val[ins->std.arpMacro.len]=(signed char)fm.val[j]; + ins->std.arpMacro.open=true; + if (++ins->std.arpMacro.len>=128) break; + } } - } ds.ins.push_back(ins); From 26176f58eb65806a51d25b3726491549b10c5355 Mon Sep 17 00:00:00 2001 From: 20Enderdude20 Date: Tue, 26 Jul 2022 00:12:34 -0700 Subject: [PATCH 060/194] Use this effect in lower notes like c -1 to C-0 --- instruments/FM/effect/GEN_Wind.fui | Bin 0 -> 1763 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 instruments/FM/effect/GEN_Wind.fui diff --git a/instruments/FM/effect/GEN_Wind.fui b/instruments/FM/effect/GEN_Wind.fui new file mode 100644 index 0000000000000000000000000000000000000000..181d90d7f303bcc3d85f9de651f6d57d1b0d9e4d GIT binary patch literal 1763 zcmeHH%TB{E5S%*e7YLch2gFA>lyBeyh)ab85@!Uc>LrH)U&n<%gWalboG7UUhznwr z)lR(jjJ;7jy?9+V_YY5T(LBE_Pp5MLkt5-1c60k?fH@{OzC53u#JffF2ulDzKn)$t zK2W6vy<9_uaIme8;10LdcLI(^>jCPFcLun1aLQ0;yhuL^NCIM&AWMX&rLvV3mbu>N(~(Fg&M7{ezK5gm*G literal 0 HcmV?d00001 From a9bfe7f452de711168aef64ebf8868fbb20cc7ed Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 02:13:19 -0500 Subject: [PATCH 061/194] fix build... again! --- src/cli/cli.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 956796001..55ac3a865 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -34,8 +34,14 @@ bool FurnaceCLI::loop() { bool escape=false; bool escapeSecondStage=false; while (!cliQuit) { +#ifdef _WIN32 + int c; + c=fgetc(stdin); + if (c==EOF) break; +#else unsigned char c; if (read(STDIN_FILENO,&c,1)<=0) continue; +#endif if (escape) { if (escapeSecondStage) { switch (c) { From e7938ccd11e36900fbbd9d52c93f0ca1796268a5 Mon Sep 17 00:00:00 2001 From: Aleksi Knutsi <53163105+host12prog@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:16:24 +0700 Subject: [PATCH 062/194] Fix 1 typo and 2 capitalization errors --- papers/doc/7-systems/x1-010.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/papers/doc/7-systems/x1-010.md b/papers/doc/7-systems/x1-010.md index 411afb3e7..80b5b41a3 100644 --- a/papers/doc/7-systems/x1-010.md +++ b/papers/doc/7-systems/x1-010.md @@ -8,9 +8,9 @@ Allumer rebadged it for their own arcade hardware. It has 16 channels, which can all be switched between PCM sample or wavetable playback mode. Wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable. -In furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. +In Furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. -# waveform types +# Waveform types This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: @@ -44,4 +44,4 @@ In furnace, you can enable the envelope shape split mode. When it is set, its wa - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. +* PCM frequency: 255 step, formula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. From c6d5f55335125f9f618ef2c47d43d64c79bd421f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 02:28:28 -0500 Subject: [PATCH 063/194] AND REALLY FIX IT THIS TIME --- src/cli/cli.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 55ac3a865..2ac4a492f 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -96,11 +96,14 @@ bool FurnaceCLI::loop() { } bool FurnaceCLI::finish() { +#ifdef _WIN32 +#else if (tcsetattr(0,TCSAFLUSH,&termpropold)!=0) { logE("could not set console attributes!"); logE("you may have to run `reset` on your terminal."); return false; } +#endif return true; } From 606215ef9f33c7e11abf77cca2c018fd8024255b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 02:54:35 -0500 Subject: [PATCH 064/194] OH MY --- src/cli/cli.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 2ac4a492f..87bf76dda 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -22,9 +22,11 @@ bool cliQuit=false; +#ifndef _WIN32 static void handleTerm(int) { cliQuit=true; } +#endif void FurnaceCLI::bindEngine(DivEngine* eng) { e=eng; From 92c3e75bee907cc229432c4c1da143b79ad6d2ed Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 03:11:46 -0500 Subject: [PATCH 065/194] why! --- src/cli/cli.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.h b/src/cli/cli.h index 0df6658e3..55ad36b38 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -20,6 +20,9 @@ #ifndef _FUR_CLI_H #define _FUR_CLI_H + +#include "../engine/engine.h" + #include #ifdef _WIN32 #include @@ -30,8 +33,6 @@ #include #endif -#include "../engine/engine.h" - class FurnaceCLI { DivEngine* e; From 47aba6186dee6bc453175911ee9f0b135b56a514 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 03:34:41 -0500 Subject: [PATCH 066/194] GUI: fix possible crash in sample editing actions --- src/gui/sampleUtil.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/sampleUtil.h b/src/gui/sampleUtil.h index 981a56046..187f5ebf6 100644 --- a/src/gui/sampleUtil.h +++ b/src/gui/sampleUtil.h @@ -20,6 +20,10 @@ #define SAMPLE_OP_BEGIN \ unsigned int start=0; \ unsigned int end=sample->samples; \ + if (sampleSelStart<0) sampleSelStart=0; \ + if (sampleSelStart>(int)sample->samples) sampleSelStart=sample->samples; \ + if (sampleSelEnd<0) sampleSelEnd=0; \ + if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples; \ if (sampleSelStart!=-1 && sampleSelEnd!=-1 && sampleSelStart!=sampleSelEnd) { \ start=sampleSelStart; \ end=sampleSelEnd; \ From 173e9b0df9590536e25b3126c17663cd1b08ffe7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Jul 2022 18:23:01 -0500 Subject: [PATCH 067/194] **Namco C163** --- papers/doc/7-systems/n163.md | 22 +++++++++++++++++++++- src/engine/engine.h | 3 +++ src/engine/sysDef.cpp | 8 ++++---- src/gui/gui.cpp | 6 +++++- src/gui/gui.h | 11 +++++++++++ src/gui/settings.cpp | 4 ++-- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/papers/doc/7-systems/n163.md b/papers/doc/7-systems/n163.md index 6057fa3c9..d3ece830f 100644 --- a/papers/doc/7-systems/n163.md +++ b/papers/doc/7-systems/n163.md @@ -1,4 +1,24 @@ -# Namco C163 +# - ANNOUNCEMENT - + +Start calling it C163! The TRUE name of the chip! + +The line will be consistent with your help: +- Namco C15 +- Namco C30 +- Namco C140 +- **Namco C163** +- Namco C219 +- Namco C352 + +The C names are official as indicated by: + +- MAME +- VGMPlay +- Korg × Bandai Namco (from Kamata info page) + +C stands for Custom! Call it C163! + +# Namco 163 (also called Namco C163, 106, 160 or 129) This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 128 byte of internal RAM, and both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. But waveform RAM area becomes smaller as more channels are activated; as channel registers consumes 8 bytes for each channel. You must avoid conflict with channel register area and waveform for avoid broken channel playback. diff --git a/src/engine/engine.h b/src/engine/engine.h index 482b58dcb..3030cfadb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -52,6 +52,9 @@ #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 +// "Namco C163" +#define DIV_C163_DEFAULT_NAME "Namco 163" + enum DivStatusView { DIV_STATUS_NOTHING=0, DIV_STATUS_PATTERN, diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 813e409e5..740854071 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -178,7 +178,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta } if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_N163) { String ret="Famicom + "; - ret+=getConfString("c163Name","Namco C163"); + ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME); return ret; } if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_MMC5) { @@ -208,7 +208,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta for (int i=0; i0) ret+=" + "; if (ds.system[i]==DIV_SYSTEM_N163) { - ret+=getConfString("c163Name","Namco C163"); + ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME); } else { ret+=getSystemName(ds.system[i]); } @@ -220,7 +220,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta const char* DivEngine::getSystemName(DivSystem sys) { if (sysDefs[sys]==NULL) return "Unknown"; if (sys==DIV_SYSTEM_N163) { - String c1=getConfString("c163Name","Namco C163"); + String c1=getConfString("c163Name",DIV_C163_DEFAULT_NAME); strncpy(c163NameCS,c1.c_str(),1023); return c163NameCS; } @@ -1246,7 +1246,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_N163]=new DivSysDef( - "Namco C163", NULL, 0x8c, 0, 8, false, true, 0, false, + "Namco 163/C163/129/160/106/whatever", NULL, 0x8c, 0, 8, false, true, 0, false, "an expansion chip for the Famicom, with full wavetable.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2c80c5fbe..b883935ea 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4716,7 +4716,11 @@ FurnaceGUI::FurnaceGUI(): pianoView(0), pianoInputPadMode(0), #endif - hasACED(false) { + hasACED(false), + waveGenBaseShape(0), + waveGenDuty(0.0f), + waveGenPower(0.0f), + waveGenInvertPoint(0.0f) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index 875501d24..3b98f77ff 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1466,6 +1466,17 @@ class FurnaceGUI { bool hasACED; unsigned char acedData[23]; + // wave generator + int waveGenBaseShape; + float waveGenDuty, waveGenPower, waveGenInvertPoint; + float waveGenAmp[16]; + float waveGenPhase[16]; + float waveGenTL[4]; + float waveGenFB[4]; + bool waveGenFMCon1[4]; + bool waveGenFMCon2[3]; + bool waveGenFMCon3[2]; + void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index eae165d27..6582a4e21 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1203,7 +1203,7 @@ void FurnaceGUI::drawSettings() { ImGui::Text("N163/C163 chip name"); ImGui::SameLine(); - ImGui::InputTextWithHint("##C163Name","Namco C163",&settings.c163Name); + ImGui::InputTextWithHint("##C163Name",DIV_C163_DEFAULT_NAME,&settings.c163Name); ImGui::Separator(); @@ -2029,7 +2029,7 @@ void FurnaceGUI::syncSettings() { settings.midiInDevice=e->getConfString("midiInDevice",""); settings.midiOutDevice=e->getConfString("midiOutDevice",""); // I'm sorry, but the C163 education program has failed... - settings.c163Name=e->getConfString("c163Name","Namco C163"); + settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME); settings.audioQuality=e->getConfInt("audioQuality",0); settings.audioBufSize=e->getConfInt("audioBufSize",1024); settings.audioRate=e->getConfInt("audioRate",44100); From 7d5f5a91c67a9cf3abc4ee43f850479d00aa8202 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Jul 2022 01:20:26 -0500 Subject: [PATCH 068/194] GUI: wave generator, part 1 --- src/gui/gui.cpp | 17 +++++++++++- src/gui/gui.h | 4 +++ src/gui/waveEdit.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b883935ea..565ff7db5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4720,7 +4720,8 @@ FurnaceGUI::FurnaceGUI(): waveGenBaseShape(0), waveGenDuty(0.0f), waveGenPower(0.0f), - waveGenInvertPoint(0.0f) { + waveGenInvertPoint(0.0f), + waveGenFM(false) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; @@ -4782,6 +4783,20 @@ FurnaceGUI::FurnaceGUI(): memset(acedData,0,23); + memset(waveGenAmp,0,sizeof(float)*16); + memset(waveGenPhase,0,sizeof(float)*16); + memset(waveGenTL,0,sizeof(float)*4); + memset(waveGenMult,0,sizeof(int)*4); + memset(waveGenFB,0,sizeof(float)*4); + memset(waveGenFMCon1,0,sizeof(bool)*4); + memset(waveGenFMCon2,0,sizeof(bool)*3); + memset(waveGenFMCon3,0,sizeof(bool)*2); + + waveGenAmp[0]=1.0f; + waveGenFMCon1[0]=true; + waveGenFMCon2[0]=true; + waveGenFMCon3[0]=true; + memset(pianoKeyHit,0,sizeof(float)*180); memset(pianoKeyPressed,0,sizeof(bool)*180); diff --git a/src/gui/gui.h b/src/gui/gui.h index 3b98f77ff..df3dcd16d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1472,10 +1472,12 @@ class FurnaceGUI { float waveGenAmp[16]; float waveGenPhase[16]; float waveGenTL[4]; + int waveGenMult[4]; float waveGenFB[4]; bool waveGenFMCon1[4]; bool waveGenFMCon2[3]; bool waveGenFMCon3[2]; + bool waveGenFM; void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); @@ -1600,6 +1602,8 @@ class FurnaceGUI { void noteInput(int num, int key, int vol=-1); void valueInput(int num, bool direct=false, int target=-1); + void doGenerateWave(); + void doUndoSample(); void doRedoSample(); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index f8c27b1fa..0d65f5e68 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -17,12 +17,60 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "gui.h" #include "plot_nolerp.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include #include +const char* waveGenBaseShapes[4]={ + "Sine", + "Triangle", + "Saw", + "Pulse" +}; + +void FurnaceGUI::doGenerateWave() { + float finalResult[256]; + if (curWave<0 || curWave>=(int)e->song.wave.size()) return; + + DivWavetable* wave=e->song.wave[curWave]; + memset(finalResult,0,sizeof(float)*256); + + if (waveGenFM) { + + } else { + switch (waveGenBaseShape) { + case 0: // sine + for (int i=0; ilen; i++) { + finalResult[i]=0.5*(1.0+sin(i*2.0*M_PI/(double)wave->len)); + } + break; + case 1: // triangle + for (int i=0; ilen; i++) { + finalResult[i]=2.0*(0.5-fabs(0.5-(i/(double)(wave->len-1)))); + } + break; + case 2: // saw + for (int i=0; ilen; i++) { + finalResult[i]=i/(double)(wave->len-1); + } + break; + case 3: // pulse + for (int i=0; ilen; i++) { + finalResult[i]=(i>=(wave->len/2))?1:0; + } + break; + } + } + + for (int i=0; ilen; i++) { + wave->data[i]=round(finalResult[i]*wave->max); + } +} + void FurnaceGUI::drawWaveEdit() { if (nextWindow==GUI_WINDOW_WAVE_EDIT) { waveEditOpen=true; @@ -144,10 +192,21 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::BeginTabBar("WaveGenOpt")) { if (ImGui::BeginTabItem("Shapes")) { - ImGui::Button("Square"); + waveGenFM=false; + + if (waveGenBaseShape<0) waveGenBaseShape=0; + if (waveGenBaseShape>3) waveGenBaseShape=3; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##WGShape",&waveGenBaseShape,0,3,waveGenBaseShapes[waveGenBaseShape])) { + if (waveGenBaseShape<0) waveGenBaseShape=0; + if (waveGenBaseShape>3) waveGenBaseShape=3; + doGenerateWave(); + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("FM")) { + waveGenFM=true; + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Mangle")) { From 693d457fffb8447727503dd42dee58e2d672f22a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Jul 2022 02:23:29 -0500 Subject: [PATCH 069/194] GUI: wave generator, part 2 --- src/gui/gui.cpp | 6 +-- src/gui/gui.h | 4 +- src/gui/waveEdit.cpp | 114 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 565ff7db5..ab4b3d63c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4718,9 +4718,9 @@ FurnaceGUI::FurnaceGUI(): #endif hasACED(false), waveGenBaseShape(0), - waveGenDuty(0.0f), - waveGenPower(0.0f), - waveGenInvertPoint(0.0f), + waveGenDuty(0.5f), + waveGenPower(1), + waveGenInvertPoint(1.0f), waveGenFM(false) { // value keys valueKeys[SDLK_0]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index df3dcd16d..30113953f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1468,7 +1468,9 @@ class FurnaceGUI { // wave generator int waveGenBaseShape; - float waveGenDuty, waveGenPower, waveGenInvertPoint; + float waveGenDuty; + int waveGenPower; + float waveGenInvertPoint; float waveGenAmp[16]; float waveGenPhase[16]; float waveGenTL[4]; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 0d65f5e68..5dd9b1ccf 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -39,34 +39,67 @@ void FurnaceGUI::doGenerateWave() { DivWavetable* wave=e->song.wave[curWave]; memset(finalResult,0,sizeof(float)*256); + if (wave->len<2) return; + if (waveGenFM) { } else { switch (waveGenBaseShape) { case 0: // sine for (int i=0; ilen; i++) { - finalResult[i]=0.5*(1.0+sin(i*2.0*M_PI/(double)wave->len)); + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=sin((0.5+pos)*2.0*M_PI/(double)wave->len); + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } } break; case 1: // triangle for (int i=0; ilen; i++) { - finalResult[i]=2.0*(0.5-fabs(0.5-(i/(double)(wave->len-1)))); + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=4.0*(0.5-fabs(0.5-(pos/(double)(wave->len-1))))-1.0; + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } } break; case 2: // saw for (int i=0; ilen; i++) { - finalResult[i]=i/(double)(wave->len-1); + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=((2*pos)/(double)(wave->len-1))-1.0; + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } } break; case 3: // pulse for (int i=0; ilen; i++) { - finalResult[i]=(i>=(wave->len/2))?1:0; + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=(pos>=(waveGenDuty*wave->len))?1:-1; + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } } break; } } + for (int i=waveGenInvertPoint*wave->len; ilen; i++) { + finalResult[i]=-finalResult[i]; + } + for (int i=0; ilen; i++) { + finalResult[i]=(1.0+finalResult[i])*0.5; + if (finalResult[i]<0.0f) finalResult[i]=0.0f; + if (finalResult[i]>1.0f) finalResult[i]=1.0f; wave->data[i]=round(finalResult[i]*wave->max); } } @@ -202,6 +235,79 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenBaseShape>3) waveGenBaseShape=3; doGenerateWave(); } + + if (ImGui::BeginTable("WGShapeProps",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Duty"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGDuty",&waveGenDuty,0.0f,1.0f)) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Exponent"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##WGExp",&waveGenPower,1,8)) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("XOR Point"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGXOR",&waveGenInvertPoint,0.0f,1.0f)) { + doGenerateWave(); + } + + ImGui::EndTable(); + } + + if (ImGui::TreeNode("Amplitude/Phase")) { + if (ImGui::BeginTable("WGShapeProps",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.4f); + + for (int i=0; i<16; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + ImGui::TableNextColumn(); + ImGui::PushID(140+i); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGAmp",&waveGenAmp[i],-1.0f,1.0f)) { + doGenerateWave(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + waveGenAmp[i]=0.0f; + doGenerateWave(); + } + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID(140+i); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGPhase",&waveGenPhase[i],0.0f,1.0f)) { + doGenerateWave(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + waveGenPhase[i]=0.0f; + doGenerateWave(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + ImGui::TreePop(); + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("FM")) { From 185b283ef661bc3d7d0be02b60f1b8e0e0e9240a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Jul 2022 02:36:36 -0500 Subject: [PATCH 070/194] GUI: wave generator, part 3 --- src/gui/waveEdit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 5dd9b1ccf..4d8f197da 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -313,6 +313,7 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::BeginTabItem("FM")) { waveGenFM=true; + ImGui::Text("FM stuff here"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Mangle")) { From 2b4b320a74f53370c43c6473fe0c60baa8c7876a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Jul 2022 02:36:48 -0500 Subject: [PATCH 071/194] fix noMultiSystem setting being inverted --- src/engine/engine.cpp | 4 ++-- src/engine/fileOps.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 40a51bdc2..2a000476d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -848,7 +848,7 @@ void DivEngine::createNew(const int* description, String sysName) { initSongWithDesc(description); } if (sysName=="") { - song.systemName=getSongSystemLegacyName(song,getConfInt("noMultiSystem",0)); + song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); } else { song.systemName=sysName; } @@ -3137,7 +3137,7 @@ bool DivEngine::init() { } String sysName=getConfString("initialSysName",""); if (sysName=="") { - song.systemName=getSongSystemLegacyName(song,getConfInt("noMultiSystem",0)); + song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); } else { song.systemName=sysName; } diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index d480167ee..8dcd55d22 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -909,7 +909,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.system[1]=DIV_SYSTEM_FDS; } - ds.systemName=getSongSystemLegacyName(ds,getConfInt("noMultiSystem",0)); + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); if (active) quitDispatch(); BUSY_BEGIN_SOFT; @@ -1497,7 +1497,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.systemNameJ=reader.readString(); ds.categoryJ=reader.readString(); } else { - ds.systemName=getSongSystemLegacyName(ds,getConfInt("noMultiSystem",0)); + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); } // read subsongs From 9447442fede8c999515a02a8521b3d042e8b286c Mon Sep 17 00:00:00 2001 From: freq-mod Date: Wed, 27 Jul 2022 16:09:36 +0200 Subject: [PATCH 072/194] Update waveform editor height/width guide --- src/gui/waveEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 4d8f197da..ac06bcb7c 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -152,7 +152,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); @@ -166,7 +166,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010 and SCC\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); From b3e9f53ec47f30e1c520522c4728fd1f3288983d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Jul 2022 17:57:36 -0500 Subject: [PATCH 073/194] GUI: the poll --- src/gui/songInfo.cpp | 2 - src/gui/subSongs.cpp | 130 ------------------------------------------- 2 files changed, 132 deletions(-) diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index f1428db5e..7f7e026ed 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -226,8 +226,6 @@ void FurnaceGUI::drawSongInfo() { } ImGui::EndTable(); } - - ImGui::TextWrapped("if this feels incomplete, go to Subsongs.\nthe outcome of this Song Information window will be determined by a poll on the Furnace Discord."); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; ImGui::End(); diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index c100600d1..c56bf6405 100644 --- a/src/gui/subSongs.cpp +++ b/src/gui/subSongs.cpp @@ -94,136 +94,6 @@ void FurnaceGUI::drawSubSongs() { if (ImGui::InputText("##SubSongName",&e->curSubSong->name)) { MARK_MODIFIED; } - - if (ImGui::BeginTable("OtherSubProps",3,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("TimeBase"); - ImGui::TableNextColumn(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - unsigned char realTB=e->curSubSong->timeBase+1; - if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED - if (realTB<1) realTB=1; - if (realTB>16) realTB=16; - e->curSubSong->timeBase=realTB-1; - } - ImGui::TableNextColumn(); - ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD)); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Speed"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED - if (e->curSubSong->speed1<1) e->curSubSong->speed1=1; - if (e->isPlaying()) play(); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED - if (e->curSubSong->speed2<1) e->curSubSong->speed2=1; - if (e->isPlaying()) play(); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Virtual Tempo"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_THREE)) { MARK_MODIFIED - if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1; - if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Numerator"); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_THREE)) { MARK_MODIFIED - if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1; - if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Denominator (set to base tempo)"); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Highlight"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) { - MARK_MODIFIED; - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) { - MARK_MODIFIED; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Pattern Length"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - int patLen=e->curSubSong->patLen; - if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED - if (patLen<1) patLen=1; - if (patLen>256) patLen=256; - e->curSubSong->patLen=patLen; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Song Length"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - int ordLen=e->curSubSong->ordersLen; - if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED - if (ordLen<1) ordLen=1; - if (ordLen>256) ordLen=256; - e->curSubSong->ordersLen=ordLen; - if (curOrder>=ordLen) { - setOrder(ordLen-1); - } - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { - tempoView=!tempoView; - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz; - if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED - if (tempoView) setHz/=2.5; - if (setHz<10) setHz=10; - if (setHz>999) setHz=999; - e->setSongRate(setHz,setHz<52); - } - if (tempoView) { - ImGui::TableNextColumn(); - ImGui::Text("= %gHz",e->curSubSong->hz); - } else { - if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) { - ImGui::TableNextColumn(); - ImGui::Text("PAL"); - } - if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) { - ImGui::TableNextColumn(); - ImGui::Text("NTSC"); - } - } - - ImGui::EndTable(); - } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS; ImGui::End(); From 4666a8d61494187a6192d28d25c0f425fafdb014 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Jul 2022 17:57:45 -0500 Subject: [PATCH 074/194] update export-tech.md --- papers/export-tech.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/papers/export-tech.md b/papers/export-tech.md index 3bf715469..560aab324 100644 --- a/papers/export-tech.md +++ b/papers/export-tech.md @@ -4,6 +4,13 @@ TODO +## macro data + +read length, loop and then release (1 byte). +if it is a 2-byte macro, read a dummy byte. + +then read data. + ## pattern data read sequentially. From e7108c060ba3755d5d497a0223de06a98e0dfa4b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 28 Jul 2022 23:24:32 -0500 Subject: [PATCH 075/194] add Namco WSG section to doc/7-systems --- papers/doc/7-systems/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 6d428f3eb..e4b6cf5b9 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -26,7 +26,8 @@ this is a list of systems that Furnace supports, including each system's effects - [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) -- [Namco 163](n163.md) +- [Namco C163](n163.md) +- [Namco WSG](namco.md) - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) From cd4af3c4babaa669992fbe13c94c99e46a06e4b0 Mon Sep 17 00:00:00 2001 From: Aleksi Knutsi <53163105+host12prog@users.noreply.github.com> Date: Fri, 29 Jul 2022 21:20:17 +0700 Subject: [PATCH 076/194] Update soundunit.md --- papers/doc/7-systems/soundunit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/soundunit.md b/papers/doc/7-systems/soundunit.md index d9d0abd70..89e346558 100644 --- a/papers/doc/7-systems/soundunit.md +++ b/papers/doc/7-systems/soundunit.md @@ -25,7 +25,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti - `17xx`: set volume sweep period low byte - `18xx`: set volume sweep period high byte - `19xx`: set cutoff sweep period low byte -- `1Axx`: set cutoff sweep period low byte +- `1Axx`: set cutoff sweep period high byte - `1Bxx`: set frequency sweep boundary - `1Cxx`: set volume sweep boundary - `1Dxx`: set cutoff sweep boundary From 1921fd17595efa7ce7ed8212c33417bd031919a6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 30 Jul 2022 01:00:51 -0500 Subject: [PATCH 077/194] PCE: implement anti-click technology --- src/engine/platform/pce.cpp | 12 +++++++++++- src/engine/platform/pce.h | 5 ++++- src/gui/sysConf.cpp | 19 ++++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index f1eb5a108..a61b61836 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -136,8 +136,9 @@ void DivPlatformPCE::updateWave(int ch) { chWrite(ch,0x04,0x5f); chWrite(ch,0x04,0x1f); for (int i=0; i<32; i++) { - chWrite(ch,0x06,chan[ch].ws.output[i]); + chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]); } + chan[ch].antiClickWavePos&=31; if (chan[ch].active) { chWrite(ch,0x04,0x80|chan[ch].outVol); } @@ -150,6 +151,13 @@ static unsigned char noiseFreq[12]={ void DivPlatformPCE::tick(bool sysTick) { for (int i=0; i<6; i++) { + // anti-click + if (antiClickEnabled && sysTick && chan[i].freq>0) { + chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f)); + chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq; + chan[i].antiClickPeriodCount%=chan[i].freq; + } + chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31); @@ -555,6 +563,8 @@ void DivPlatformPCE::setFlags(unsigned int flags) { } else { chipClock=COLOR_NTSC; } + // flags&4 will be chip revision + antiClickEnabled=!(flags&8); rate=chipClock/12; for (int i=0; i<6; i++) { oscBuf[i]->rate=rate; diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 870b5218a..22a24ddf8 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -28,7 +28,7 @@ class DivPlatformPCE: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, pitch2, note; + int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos; int dacPeriod, dacRate; unsigned int dacPos; int dacSample, ins; @@ -47,6 +47,8 @@ class DivPlatformPCE: public DivDispatch { pitch(0), pitch2(0), note(0), + antiClickPeriodCount(0), + antiClickWavePos(0), dacPeriod(0), dacRate(0), dacPos(0), @@ -69,6 +71,7 @@ class DivPlatformPCE: public DivDispatch { Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; + bool antiClickEnabled; struct QueuedWrite { unsigned char addr; unsigned char val; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index e54074861..365312424 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -109,6 +109,24 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_PCE: { + sysPal=flags&1; + if (ImGui::Checkbox("Pseudo-PAL",&sysPal)) { + copyOfFlags=(flags&(~1))|(unsigned int)sysPal; + } + bool antiClick=flags&8; + if (ImGui::Checkbox("Disable anti-click",&antiClick)) { + copyOfFlags=(flags&(~8))|(antiClick<<3); + } + break; + } + case DIV_SYSTEM_GB: { + bool antiClick=flags&8; + if (ImGui::Checkbox("Disable anti-click",&antiClick)) { + copyOfFlags=(flags&(~8))|(antiClick<<3); + } + break; + } case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: { @@ -631,7 +649,6 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } - case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: case DIV_SYSTEM_BUBSYS_WSG: From 007024c86f1a9e1c7a34478004497bc0bccef725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szpilowski?= Date: Sat, 30 Jul 2022 18:47:05 +0200 Subject: [PATCH 078/194] Create MetalSlug_BaseCamp_SMS_TIA.fur New cover - MetalSlug BaseCamp (form GB Advnace) --- demos/MetalSlug_BaseCamp_SMS_TIA.fur | Bin 0 -> 1917 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/MetalSlug_BaseCamp_SMS_TIA.fur diff --git a/demos/MetalSlug_BaseCamp_SMS_TIA.fur b/demos/MetalSlug_BaseCamp_SMS_TIA.fur new file mode 100644 index 0000000000000000000000000000000000000000..dd2fa6af3138b5913f10da46d4095af644d0518c GIT binary patch literal 1917 zcmV-@2ZH!`ob6p-iyTD|ubJuD+1=S>MZpI_ZoxMpkZ{I;AYO73Jk%uQ67WHg(<~A7 zPP{*gf)M{Cq98;B^~HDn2>Rrc5Bd?*Z=fGQ$sfD7cXn!br*^t_dv3RHyF=$HJ=MRe zuI^vg^vv$%#QB5Wovqipo$a@;9lX^&aUB3Uke7`M=Py1VN}*N@0G>Oy22BtT)O*)f zWQ{(BiDj*%OzyoH<5Cz0S4F9A;81~~h*@-YlT z5s6q%S_qM-i8z6v7SzHh6fqiFL0f=IVK{C)6KE~1DnS3UfAt&rN0-g+^{vi}TYLN6 z-OlFw^2fGr-?*@O<)a#aT520Rd;7Zw+ufc0jtrRqP)iL~>T%STVT{LY>#`z(AU!$Z zdNUC12k8l2X#|aSNM3G3xTXdL^?n`cjsKxsP)Nn00+0&;?oTM%9!OS6ij`nxWfX&& zD3dbDH_G2~54|l@Tim{`7u&Jq;(I$~b~^E;_?Msu2%TtQ23TAs61Lsel`2FcI^|>{ z8oxYTOy;4D>M>bcBee{3`ZVOuhJ93Rar?SnY{!y|@9mV?S*}F%*HKACD`S_3i^)8w zSRL=zV)M==O zYTU0TxWTl$sDY2gYv*Vuh{LqS?dy869ZN30w^L?kxsuWMlgUUWjR?u3#bh2- zKheig6?=9^>ijEfy{{ukw~FnlkFbyfEh*EjsiU1Z4$~I5uj|EjEV=mJPMMwMN>ZPX zC#ff$Oj61P(U+$^F{#IOY2Or1bob=-yR!Z#t1!U!3CUes+`g_C+p*;0dpl)zmMbA0 zj3=ZsP9`LrD5gzFqaRBzIuVWb^N;M@wZ-l0da)f#F21)@W@mYl&!w|hE;mL-h~4sX z^|FxJE|gjJ7%YZAOD*Sw(7bA>?@P+b5%4h_?xIk3$J-l3JEflx?N!Sm(;rHo8n2K4 zWSJZPl=3qtMCb|O31RFHSKk%?5h3(pH&)+LpTmIYffoA{677_}Ex9GbXam1N;85e5 zmrRYfNwnLF4~0*~jVmVInCNo}#MP%Z z7;59JdPAyQ&Iu7aMF{Q=#obxvMTm$=2wQT(T*ysAzp-G1NXa{ls4PS17tDfEbU;o= zo?3g^%WOF#1pdgx+?`r7SrIwxeUWX|$A8{(P6%D}GjXMKbuc-T1?$ujit^_=SX zBSPrMu6{zsCkOA~m>ju2Et|wA9E?%x+neyil=b4u9@}T-e!rMJ7FBB_lN(4*k=!EM zv3_FEE`~6XZ#g0KQzlP{$-9;L4y6}JI_OK2CLJP#%^lDaVg^F!_Yl}YtQZqUT10zH z7&O6G2m5L9ao`iemS6@cVd8Tq0ps8ob}^EhLpsHA!0k-}#{@r_0bl)^j-psm^G?NN zm?%8_*dXQ2eBn;LGX5xFvS!Dm%(#zK5j1c(CKXZ4=%U+(BoD(Ad_;+@B7GR8nWkKL` z?Na)QCI2xFmct70XNiN4|Lph#A!?ow3os$B$KQ4T5h3!MCa&Wl!;zG!-(u<~Onpj@ z2^?Vw-h*&N zk^eS~(oPFJAX|i(7g1a%1osYL;pChU`aMK_!3LBq@DLsWlXfqA*$N@D8>Uw8aIG(L zWei-_u3r9dWG=q<-}^s}{{uqcUmrHO=w&Z2o17COdk>*b6SD(j94re0pKF)WPb~S5 zaj+a#fImwdY{j1&H%j02_#;AO?;$)PMsb#b5Vqp)y8nm}`aML<1h%EGp57F-w}^HM zhtj8HTlywMG_loW#?zKQ9na!+3dh>QWa@K?Lg^zy=wr35)XCMSc3vv*6wbT|O)VU1 zLMWU?i$A4rixAwtln%AoaL0uRA*Lb(cXB)-ChJE`f2!7};!oj_J1-$ut)DX28zO{$ z4>7L+*=qN)muHeQLS+AOL`&W}#f3YsklDk$9phkG-I~={JM#2)z5m{Su3h*at^NA) D#hbI` literal 0 HcmV?d00001 From 6ff51ce8f340803a743e024b33251fdf3e04baa3 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Sun, 31 Jul 2022 11:33:38 +0200 Subject: [PATCH 079/194] #511 Added dynamic popcnt dispatcher --- src/engine/platform/sound/lynx/Mikey.cpp | 63 +++++++++++++++--------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 270e21bdf..32612960b 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -26,6 +26,24 @@ #include #include +#if defined ( _MSC_VER ) +#include + +static void cpuid( int info[4], int infoType ) +{ + __cpuidex( info, infoType, 0 ); +} + +#else +#include + +static void cpuid( int info[4], int infoType ) +{ + __cpuid_count( infoType, 0, info[0], info[1], info[2], info[3] ); +} + +#endif + namespace Lynx { @@ -34,29 +52,7 @@ namespace static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~15; -#if defined ( __cpp_lib_bitops ) - -#define popcnt(X) std::popcount(X) - -#elif defined( _MSC_VER ) - -# include - -uint32_t popcnt( uint32_t x ) -{ - return __popcnt( x ); -} - -#elif defined( __GNUC__ ) - -uint32_t popcnt( uint32_t x ) -{ - return __builtin_popcount( x ); -} - -#else - -uint32_t popcnt( uint32_t x ) +uint32_t popcnt_generic( uint32_t x ) { int v = 0; while ( x != 0 ) @@ -67,7 +63,16 @@ uint32_t popcnt( uint32_t x ) return v; } +uint32_t popcnt_intrinsic( uint32_t x ) +{ +#if defined ( _MSC_VER ) + return __popcnt( x ); +#else + return __builtin_popcount( x ); #endif +} + +static uint32_t( *popcnt )( uint32_t x ); int32_t clamp( int32_t v, int32_t lo, int32_t hi ) { @@ -514,6 +519,18 @@ private: Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique() }, mQueue{ std::make_unique() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate } { enqueueSampling(); + + //detecting popcnt availability + int info[4]; + cpuid( info, 1 ); + if ( ( info[2] & ( (int)1 << 23 ) ) != 0 ) + { + popcnt = &popcnt_intrinsic; + } + else + { + popcnt = &popcnt_generic; + } } Mikey::~Mikey() From a9afcf873c5cabc7da2a491035f35ae4ed8cf9ff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 31 Jul 2022 14:05:23 -0500 Subject: [PATCH 080/194] fix ARM build --- src/engine/platform/sound/lynx/Mikey.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 32612960b..718180033 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -26,6 +26,11 @@ #include #include +#if defined(i386) || defined(__i386__) || defined(__i386) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) +#define IS_INTEL +#endif + +#ifdef IS_INTEL #if defined ( _MSC_VER ) #include @@ -42,6 +47,7 @@ static void cpuid( int info[4], int infoType ) __cpuid_count( infoType, 0, info[0], info[1], info[2], info[3] ); } +#endif #endif namespace Lynx @@ -63,6 +69,7 @@ uint32_t popcnt_generic( uint32_t x ) return v; } +#ifdef IS_INTEL uint32_t popcnt_intrinsic( uint32_t x ) { #if defined ( _MSC_VER ) @@ -71,6 +78,7 @@ uint32_t popcnt_intrinsic( uint32_t x ) return __builtin_popcount( x ); #endif } +#endif static uint32_t( *popcnt )( uint32_t x ); @@ -521,6 +529,7 @@ Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique() }, enqueueSampling(); //detecting popcnt availability +#ifdef IS_INTEL int info[4]; cpuid( info, 1 ); if ( ( info[2] & ( (int)1 << 23 ) ) != 0 ) @@ -531,6 +540,9 @@ Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique() }, { popcnt = &popcnt_generic; } +#else + popcnt = &popcnt_generic; +#endif } Mikey::~Mikey() From 5feba3a7167b54bbebb1bba7159dcbda91b41663 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Sun, 31 Jul 2022 22:26:59 +0200 Subject: [PATCH 081/194] More robust popcnt --- src/engine/platform/sound/lynx/Mikey.cpp | 105 +++++++++++------------ 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 718180033..791336c1b 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -26,39 +26,11 @@ #include #include -#if defined(i386) || defined(__i386__) || defined(__i386) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) -#define IS_INTEL -#endif +#if defined( _MSC_VER ) -#ifdef IS_INTEL -#if defined ( _MSC_VER ) #include -static void cpuid( int info[4], int infoType ) -{ - __cpuidex( info, infoType, 0 ); -} - -#else -#include - -static void cpuid( int info[4], int infoType ) -{ - __cpuid_count( infoType, 0, info[0], info[1], info[2], info[3] ); -} - -#endif -#endif - -namespace Lynx -{ - -namespace -{ - -static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~15; - -uint32_t popcnt_generic( uint32_t x ) +static uint32_t popcnt_generic( uint32_t x ) { int v = 0; while ( x != 0 ) @@ -69,18 +41,60 @@ uint32_t popcnt_generic( uint32_t x ) return v; } -#ifdef IS_INTEL -uint32_t popcnt_intrinsic( uint32_t x ) +#if defined( _M_IX86 ) || defined( _M_X64 ) + +static uint32_t popcnt_intrinsic( uint32_t x ) { -#if defined ( _MSC_VER ) return __popcnt( x ); -#else - return __builtin_popcount( x ); -#endif } + +static uint32_t( *popcnt )( uint32_t ); + +//detecting popcnt availability on msvc intel +static void selectPOPCNT() +{ + int info[4]; + __cpuid( info, 1 ); + if ( ( info[2] & ( (int)1 << 23 ) ) != 0 ) + { + popcnt = &popcnt_intrinsic; + } + else + { + popcnt = &popcnt_generic; + } +} + +#else //defined( _M_IX86 ) || defined( _M_X64 ) + +//MSVC non INTEL should use generic implementation +inline void selectPOPCNT() +{ +} + +#define popcnt popcnt_generic + #endif -static uint32_t( *popcnt )( uint32_t x ); +#else //defined( _MSC_VER ) + +//non MVSC should use builtin implementation + +inline void selectPOPCNT() +{ +} + +#define popcnt __builtin_popcount + +#endif + +namespace Lynx +{ + +namespace +{ + +static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~15; int32_t clamp( int32_t v, int32_t lo, int32_t hi ) { @@ -526,23 +540,8 @@ private: Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique() }, mQueue{ std::make_unique() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate } { + selectPOPCNT(); enqueueSampling(); - - //detecting popcnt availability -#ifdef IS_INTEL - int info[4]; - cpuid( info, 1 ); - if ( ( info[2] & ( (int)1 << 23 ) ) != 0 ) - { - popcnt = &popcnt_intrinsic; - } - else - { - popcnt = &popcnt_generic; - } -#else - popcnt = &popcnt_generic; -#endif } Mikey::~Mikey() From fe07051f8963827568c957d315dba3f26692f528 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 1 Aug 2022 22:51:13 -0500 Subject: [PATCH 082/194] rename Envelope release to Macro release --- src/gui/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 6582a4e21..2ad3ef23c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1712,7 +1712,7 @@ void FurnaceGUI::drawSettings() { ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan)); ImGui::TableNextColumn(); if (i.val==102) { - snprintf(id,4095,"Envelope release##SNType_%d",i.scan); + snprintf(id,4095,"Macro release##SNType_%d",i.scan); if (ImGui::Button(id)) { noteKeys[i.scan]=0; } From 1f57d09fbf0019f93b0e40acd39607d6747bc5af Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 2 Aug 2022 23:16:42 -0500 Subject: [PATCH 083/194] GUI: display correct OPLL patch names --- src/gui/insEdit.cpp | 162 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 140 insertions(+), 22 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f0d6d3da3..09d6f4066 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -43,24 +43,93 @@ const char* fmParamShortNames[3][32]={ {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"} }; -const char* opllInsNames[17]={ - "User", - "Violin", - "Guitar", - "Piano", - "Flute", - "Clarinet", - "Oboe", - "Trumpet", - "Organ", - "Horn", - "Synth", - "Harpsichord", - "Vibraphone", - "Synth Bass", - "Acoustic Bass", - "Electric Guitar", - "Drums" +const char* opllVariants[4]={ + "OPLL", + "YMF281", + "YM2423", + "VRC7" +}; + +const char* opllInsNames[4][17]={ + /* YM2413 */ { + "User", + "Violin", + "Guitar", + "Piano", + "Flute", + "Clarinet", + "Oboe", + "Trumpet", + "Organ", + "Horn", + "Synth", + "Harpsichord", + "Vibraphone", + "Synth Bass", + "Acoustic Bass", + "Electric Guitar", + "Drums" + }, + // help me get the names! + /* YMF281 */ { + "User", + "Name under Register-Wall #1", + "Name under Register-Wall #2", + "Piano", + "Flute", + "Clarinet", + "Name under Register-Wall #6", + "Trumpet", + "Name under Register-Wall #8", + "Name under Register-Wall #9", + "Name under Register-Wall #10", + "Name under Register-Wall #11", + "Name under Register-Wall #12", + "Name under Register-Wall #13", + "Name under Register-Wall #14", + "Name under Register-Wall #15", + "Drums" + }, + // help me get the names! + /* YM2423 */ { + "User", + "Violin", + "What is this", + "Some kind of space sound", + "Voice maybe", + "Clarinet", + "Slap something", + "Trumpet", + "AAAAAA", + "Wow!", + "Synth", + "Synth Key", + "Vibraphone", + "Space Bass", + "Synth Bass", + "Sound like the default Defle patch", + "Drums" + }, + // stolen from FamiTracker + /* VRC7 */ { + "User", + "Bell", + "Guitar", + "Piano", + "Flute", + "Clarinet", + "Rattling Bell", + "Trumpet", + "Reed Organ", + "Soft Bell", + "Xylophone", + "Vibraphone", + "Brass", + "Bass Guitar", + "Synth", + "Chorus", + "Drums" + } }; const char* oplWaveforms[8]={ @@ -1572,10 +1641,59 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale)); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##LLPreset",opllInsNames[ins->fm.opllPreset])) { - for (int i=0; i<17; i++) { - if (ImGui::Selectable(opllInsNames[i])) { - ins->fm.opllPreset=i; + + bool isPresent[4]; + int isPresentCount=0; + memset(isPresent,0,4*sizeof(bool)); + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_VRC7) { + isPresent[3]=true; + } else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) { + isPresent[(e->song.systemFlags[i]>>4)&3]=true; + } + } + if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) { + isPresent[0]=true; + } + for (int i=0; i<4; i++) { + if (isPresent[i]) isPresentCount++; + } + int presentWhich=0; + for (int i=0; i<4; i++) { + if (isPresent[i]) { + presentWhich=i; + break; + } + } + + if (ImGui::BeginCombo("##LLPreset",opllInsNames[presentWhich][ins->fm.opllPreset])) { + if (isPresentCount>1) { + if (ImGui::BeginTable("LLPresetList",isPresentCount)) { + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int i=0; i<4; i++) { + if (!isPresent[i]) continue; + ImGui::TableNextColumn(); + ImGui::Text("%s name",opllVariants[i]); + } + for (int i=0; i<17; i++) { + ImGui::TableNextRow(); + for (int j=0; j<4; j++) { + if (!isPresent[j]) continue; + ImGui::TableNextColumn(); + ImGui::PushID(j*17+i); + if (ImGui::Selectable(opllInsNames[j][i])) { + ins->fm.opllPreset=i; + } + ImGui::PopID(); + } + } + ImGui::EndTable(); + } + } else { + for (int i=0; i<17; i++) { + if (ImGui::Selectable(opllInsNames[presentWhich][i])) { + ins->fm.opllPreset=i; + } } } ImGui::EndCombo(); From d204ae868a66d288128ed80f76f5eeaabd8f496a Mon Sep 17 00:00:00 2001 From: brickblock369 <59150779+brickblock369@users.noreply.github.com> Date: Wed, 3 Aug 2022 14:03:08 +0900 Subject: [PATCH 084/194] Tweaked versions of the instruments These were originally made by me, but I tweaked them a little. --- instruments/FM/guitar/Acoustic Nylon Guitar.dmp | Bin 51 -> 51 bytes instruments/FM/guitar/Acoustic Steel Guitar.dmp | Bin 51 -> 51 bytes .../FM/guitar/Electric Guitar Harmonics.dmp | Bin 51 -> 51 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/instruments/FM/guitar/Acoustic Nylon Guitar.dmp b/instruments/FM/guitar/Acoustic Nylon Guitar.dmp index b79addcf1c60e804b0f268207fc6e4330fcb786e..35fac263c8a2cec190cd2aedd95271200427ca98 100644 GIT binary patch literal 51 wcmd;PVq{=oV&Kq~l;P)RU|<7s)a2Rufr3CLtA;!WKMR=2$RN+f&k1G%05H%2xBvhE literal 51 wcmd;PVq{=oV&E{6l;P)RU|fr3CLtA;!SKMR=2$RN+f&k1G%052T@pa1{> diff --git a/instruments/FM/guitar/Acoustic Steel Guitar.dmp b/instruments/FM/guitar/Acoustic Steel Guitar.dmp index 64e77b9c8b089a462abbbd0a8875b1406e6cfc0e..295a29d6e38c3e6a54124bdd7f23fb88bfe2b56b 100644 GIT binary patch literal 51 wcmd;PVq{=oV&G7hl;P)RU|?flVAPOjnamRM?EEYcQ3iP~eoinG04;O@m;e9( literal 51 wcmd;PVq{=oV&G7hl;P)RU|?ooVAPOjnamRMEc`4GQ3iP~eoinG04z}fi2wiq diff --git a/instruments/FM/guitar/Electric Guitar Harmonics.dmp b/instruments/FM/guitar/Electric Guitar Harmonics.dmp index 07f2103287a0c7b93f7bfbb4c403bf4c69ee084e..14a14b1184c4b47395c645ad75f3607714bbe17e 100644 GIT binary patch literal 51 ycmW;CF%AGA2mrAIi5eMT(vkT8Pnx)OsH-p(%{md#!FJ&S4dyMvK37%`|KR~B?g5Da literal 51 ycmd;PVq{=vVqg-G7w6|^U|?ooU=fpN;ARBU3=ABK@(lby8i<$} Date: Wed, 3 Aug 2022 00:05:58 -0500 Subject: [PATCH 085/194] SoundUnit: add 64K chip revision --- src/engine/platform/sound/su.cpp | 16 ++++++++-------- src/engine/platform/sound/su.h | 7 +++---- src/engine/platform/su.cpp | 11 ++++++++--- src/engine/platform/su.h | 1 + src/gui/sysConf.cpp | 17 +++++++++++++++++ 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp index b7fa731a6..0dd6dcdc7 100644 --- a/src/engine/platform/sound/su.cpp +++ b/src/engine/platform/sound/su.cpp @@ -54,7 +54,7 @@ void SoundUnit::NextSample(short* l, short* r) { chan[i].pcmpos=chan[i].pcmrst; } } - chan[i].pcmpos&=(SOUNDCHIP_PCM_SIZE-1); + chan[i].pcmpos&=(pcmSize-1); } else if (chan[i].flags.pcmloop) { chan[i].pcmpos=chan[i].pcmrst; } @@ -228,9 +228,10 @@ void SoundUnit::NextSample(short* l, short* r) { *r=minval(32767,maxval(-32767,tnsR)); } -void SoundUnit::Init() { +void SoundUnit::Init(int sampleMemSize) { + pcmSize=sampleMemSize; Reset(); - memset(pcm,0,SOUNDCHIP_PCM_SIZE); + memset(pcm,0,pcmSize); for (int i=0; i<256; i++) { SCsine[i]=sin((i/128.0f)*M_PI)*127; SCtriangle[i]=(i>127)?(255-i):(i); @@ -242,9 +243,6 @@ void SoundUnit::Init() { SCpantabR[128+i]=i-1; } SCpantabR[128]=0; - for (int i=0; i<8; i++) { - muted[i]=false; - } } void SoundUnit::Reset() { @@ -282,6 +280,8 @@ void SoundUnit::Write(unsigned char addr, unsigned char data) { } SoundUnit::SoundUnit() { - Init(); - memset(pcm,0,SOUNDCHIP_PCM_SIZE); + Init(65536); // default + for (int i=0; i<8; i++) { + muted[i]=false; + } } diff --git a/src/engine/platform/sound/su.h b/src/engine/platform/sound/su.h index 3152e8568..3b125f030 100644 --- a/src/engine/platform/sound/su.h +++ b/src/engine/platform/sound/su.h @@ -3,8 +3,6 @@ #include #include -#define SOUNDCHIP_PCM_SIZE 8192 - class SoundUnit { signed char SCsine[256]; signed char SCtriangle[256]; @@ -24,6 +22,7 @@ class SoundUnit { int tnsL, tnsR; unsigned short oldfreq[8]; unsigned short oldflags[8]; + unsigned int pcmSize; public: unsigned short resetfreq[8]; unsigned short voldcycles[8]; @@ -84,7 +83,7 @@ class SoundUnit { unsigned short wc; unsigned short restimer; } chan[8]; - signed char pcm[SOUNDCHIP_PCM_SIZE]; + signed char pcm[65536]; bool muted[8]; void Write(unsigned char addr, unsigned char data); void NextSample(short* l, short* r); @@ -94,7 +93,7 @@ class SoundUnit { if (ret>32767) ret=32767; return ret; } - void Init(); + void Init(int sampleMemSize=8192); void Reset(); SoundUnit(); }; diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index c2cd6b25e..2d1a38932 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -555,6 +555,11 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) { for (int i=0; i<8; i++) { oscBuf[i]->rate=rate; } + + sampleMemSize=flags&16; + + su->Init(sampleMemSize?65536:8192); + renderSamples(); } void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) { @@ -570,7 +575,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) { } size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) { - return (index==0)?8192:0; + return (index==0)?(sampleMemSize?65536:8192):0; } size_t DivPlatformSoundUnit::getSampleMemUsage(int index) { @@ -583,6 +588,7 @@ void DivPlatformSoundUnit::renderSamples() { size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; + if (s->data8==NULL) continue; int paddedLen=s->samples; if (memPos>=getSampleMemCapacity(0)) { logW("out of PCM memory for sample %d!",i); @@ -609,9 +615,8 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - setFlags(flags); su=new SoundUnit(); - su->Init(); + setFlags(flags); reset(); return 6; } diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index 1d39854f2..e882c398e 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -96,6 +96,7 @@ class DivPlatformSoundUnit: public DivDispatch { }; std::queue writes; unsigned char lastPan; + bool sampleMemSize; int cycles, curChan, delay; short tempL; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 365312424..7c624f309 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -120,6 +120,23 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_SOUND_UNIT: { + ImGui::Text("CPU rate:"); + if (ImGui::RadioButton("6.18MHz (NTSC)",(flags&15)==0)) { + copyOfFlags=(flags&(~15))|0; + } + if (ImGui::RadioButton("5.95MHz (PAL)",(flags&15)==1)) { + copyOfFlags=(flags&(~15))|1; + } + ImGui::Text("Chip revision (sample memory):"); + if (ImGui::RadioButton("A/B/E (8K)",(flags&16)==0)) { + copyOfFlags=(flags&(~16))|0; + } + if (ImGui::RadioButton("D/F (64K)",(flags&16)==16)) { + copyOfFlags=(flags&(~16))|16; + } + break; + } case DIV_SYSTEM_GB: { bool antiClick=flags&8; if (ImGui::Checkbox("Disable anti-click",&antiClick)) { From 034b4fd4f607f96e08c849b296053bb6b7a9baf5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 00:10:32 -0500 Subject: [PATCH 086/194] GUI: YMF281 patch names thanks nicco1690! --- src/gui/insEdit.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 09d6f4066..08b9fc577 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -70,24 +70,24 @@ const char* opllInsNames[4][17]={ "Electric Guitar", "Drums" }, - // help me get the names! + /* YMF281 */ { "User", - "Name under Register-Wall #1", - "Name under Register-Wall #2", - "Piano", - "Flute", + "Electric String", + "Bow wow", + "Electric Guitar", + "Organ", "Clarinet", - "Name under Register-Wall #6", + "Saxophone", "Trumpet", - "Name under Register-Wall #8", - "Name under Register-Wall #9", - "Name under Register-Wall #10", - "Name under Register-Wall #11", - "Name under Register-Wall #12", - "Name under Register-Wall #13", - "Name under Register-Wall #14", - "Name under Register-Wall #15", + "Street Organ", + "Synth Brass", + "Electric Piano", + "Bass", + "Vibraphone", + "Chime", + "Tom Tom II", + "Noise", "Drums" }, // help me get the names! From 717d99d65058495f38cf76ca7c754732bd9bc46d Mon Sep 17 00:00:00 2001 From: brickblock369 <59150779+brickblock369@users.noreply.github.com> Date: Wed, 3 Aug 2022 21:25:17 +0900 Subject: [PATCH 087/194] Two new OPL instruments --- .../OPL/Low Overdriven Guitar (Sine Carrier).fui | Bin 0 -> 1697 bytes .../Low Overdriven Guitar (Square Carrier).fui | Bin 0 -> 1699 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 instruments/OPL/Low Overdriven Guitar (Sine Carrier).fui create mode 100644 instruments/OPL/Low Overdriven Guitar (Square Carrier).fui diff --git a/instruments/OPL/Low Overdriven Guitar (Sine Carrier).fui b/instruments/OPL/Low Overdriven Guitar (Sine Carrier).fui new file mode 100644 index 0000000000000000000000000000000000000000..9fae9177ba4ca83fca5f3e3d7b3be6d46862832f GIT binary patch literal 1697 zcmdOOD=o@POioqE%quP_($h_6U|>)HVi@rB3l6DdV_-;U;A8N~FIVs{OD#$%$}CIG zQ*bZMEJ-X<&Z;Q zAg7QE(9I&mhuA}&jfBibmmdjsq5FBb^9iL>O8rmBe3<;GJ*Xi73l|0k2et)l4a^K6 fX$A#mgg6cssxhSVa2QLZFy4ZQpPzvNgmD1?J^VSR literal 0 HcmV?d00001 diff --git a/instruments/OPL/Low Overdriven Guitar (Square Carrier).fui b/instruments/OPL/Low Overdriven Guitar (Square Carrier).fui new file mode 100644 index 0000000000000000000000000000000000000000..dfb5eba1eb3bf90aa25522af55374d259e28f6d4 GIT binary patch literal 1699 zcmdOOD=o@POioqE%quP_($h_6U|>)HVi@rB3l6DfV_-;U;A8N~FIVs{OD#$%$}CIG zQ*bZMEJ-X<&8>8h~r?P8bdk{hp|Ko<1L8z`572M7#9El3xPU| literal 0 HcmV?d00001 From 89042f61eb57e14c6b1f966f47088e2dd0f7dc31 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 3 Aug 2022 21:56:22 +0900 Subject: [PATCH 088/194] Fix link for vgsound_emu (moved into https://gitlab.com/cam900/vgsound_emu) --- src/engine/platform/sound/k005289/k005289.cpp | 2 +- src/engine/platform/sound/k005289/k005289.hpp | 2 +- src/engine/platform/sound/n163/n163.cpp | 2 +- src/engine/platform/sound/n163/n163.hpp | 2 +- src/engine/platform/sound/oki/msm6295.cpp | 2 +- src/engine/platform/sound/oki/msm6295.hpp | 2 +- src/engine/platform/sound/oki/util.hpp | 2 +- src/engine/platform/sound/oki/vox.hpp | 2 +- src/engine/platform/sound/scc/scc.cpp | 2 +- src/engine/platform/sound/scc/scc.hpp | 2 +- src/engine/platform/sound/vrcvi/vrcvi.cpp | 2 +- src/engine/platform/sound/vrcvi/vrcvi.hpp | 2 +- src/engine/platform/sound/x1_010/x1_010.cpp | 2 +- src/engine/platform/sound/x1_010/x1_010.hpp | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/engine/platform/sound/k005289/k005289.cpp b/src/engine/platform/sound/k005289/k005289.cpp index 8b21245a1..c9bdf83ae 100644 --- a/src/engine/platform/sound/k005289/k005289.cpp +++ b/src/engine/platform/sound/k005289/k005289.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900 diff --git a/src/engine/platform/sound/k005289/k005289.hpp b/src/engine/platform/sound/k005289/k005289.hpp index d042e1d14..575a98b87 100644 --- a/src/engine/platform/sound/k005289/k005289.hpp +++ b/src/engine/platform/sound/k005289/k005289.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900 diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp index 5d9deab1a..3fd30bc44 100644 --- a/src/engine/platform/sound/n163/n163.cpp +++ b/src/engine/platform/sound/n163/n163.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp index 800d1ea13..317a33b45 100644 --- a/src/engine/platform/sound/n163/n163.hpp +++ b/src/engine/platform/sound/n163/n163.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/oki/msm6295.cpp b/src/engine/platform/sound/oki/msm6295.cpp index 1b7fe568a..e7f39d27e 100644 --- a/src/engine/platform/sound/oki/msm6295.cpp +++ b/src/engine/platform/sound/oki/msm6295.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/oki/msm6295.hpp b/src/engine/platform/sound/oki/msm6295.hpp index 5203f4340..ca8b81d41 100644 --- a/src/engine/platform/sound/oki/msm6295.hpp +++ b/src/engine/platform/sound/oki/msm6295.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/oki/util.hpp b/src/engine/platform/sound/oki/util.hpp index 731772acc..b9c50d7bf 100644 --- a/src/engine/platform/sound/oki/util.hpp +++ b/src/engine/platform/sound/oki/util.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/oki/vox.hpp b/src/engine/platform/sound/oki/vox.hpp index c085c0b7c..23fbfd78e 100644 --- a/src/engine/platform/sound/oki/vox.hpp +++ b/src/engine/platform/sound/oki/vox.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/scc/scc.cpp b/src/engine/platform/sound/scc/scc.cpp index 3e230447a..e2ebcf200 100644 --- a/src/engine/platform/sound/scc/scc.cpp +++ b/src/engine/platform/sound/scc/scc.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst diff --git a/src/engine/platform/sound/scc/scc.hpp b/src/engine/platform/sound/scc/scc.hpp index db99ec877..24a365ccf 100644 --- a/src/engine/platform/sound/scc/scc.hpp +++ b/src/engine/platform/sound/scc/scc.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 87ff05d7c..a811c2f44 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 790061c82..4a80f7577 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp index c047b854a..6b0041bad 100644 --- a/src/engine/platform/sound/x1_010/x1_010.cpp +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp index 3f5d9d4e2..b533b66d8 100644 --- a/src/engine/platform/sound/x1_010/x1_010.hpp +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow From 0183c5d9ff134700d65c68d4193986c0f36fe429 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 01:13:59 -0500 Subject: [PATCH 089/194] GUI: remove one new line --- src/gui/insEdit.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 08b9fc577..958f662d4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -70,7 +70,6 @@ const char* opllInsNames[4][17]={ "Electric Guitar", "Drums" }, - /* YMF281 */ { "User", "Electric String", From 46425655ad548ad6cfe2f31cfdaa455a8efad7c7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 14:38:39 -0500 Subject: [PATCH 090/194] YM2612: fix possible ExtCh DualPCM muting issue --- src/engine/platform/genesisext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index bfe771a6d..1f320dd34 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -24,6 +24,8 @@ #define CHIP_FREQBASE fmFreqBase #define CHIP_DIVIDER fmDivBase +#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) + int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { return DivPlatformGenesis::dispatch(c); @@ -542,7 +544,7 @@ void DivPlatformGenesisExt::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)); + rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(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; From 53120edd9985a1a633b5b0e052cced711c254f89 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 14:41:23 -0500 Subject: [PATCH 091/194] disable MIDI clock --- src/engine/playback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 662e2ddb4..dd58b0d1a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -914,7 +914,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // MIDI clock if (output) if (!skipping && output->midiOut!=NULL) { - output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); + //output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); } while (!pendingNotes.empty()) { From 52c3b1037392ea92ef55236f09fc73aae5697d0c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 16:21:30 -0500 Subject: [PATCH 092/194] add getWantPreNote() currently only C64 system requires this --- src/engine/dispatch.h | 6 ++++++ src/engine/platform/abstract.cpp | 4 ++++ src/engine/platform/c64.cpp | 4 ++++ src/engine/platform/c64.h | 1 + src/engine/playback.cpp | 4 +++- 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 03bc23ca6..f1418e481 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -399,6 +399,12 @@ class DivDispatch { */ virtual bool getDCOffRequired(); + /** + * check whether PRE_NOTE command is desired. + * @return truth. + */ + virtual bool getWantPreNote(); + /** * get a description of a dispatch-specific effect. * @param effect the effect. diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 54366e1ba..5b732e7e9 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -90,6 +90,10 @@ bool DivDispatch::getDCOffRequired() { return false; } +bool DivDispatch::getWantPreNote() { + return false; +} + const char* DivDispatch::getEffectName(unsigned char effect) { return NULL; } diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 640df54e6..9049ffec4 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -513,6 +513,10 @@ bool DivPlatformC64::getDCOffRequired() { return true; } +bool DivPlatformC64::getWantPreNote() { + return true; +} + void DivPlatformC64::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index d9dc08040..ae1034a82 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -97,6 +97,7 @@ class DivPlatformC64: public DivDispatch { void setFlags(unsigned int flags); void notifyInsChange(int ins); bool getDCOffRequired(); + bool getWantPreNote(); DivMacroInt* getChanMacroInt(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index dd58b0d1a..6c4376573 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -866,7 +866,9 @@ void DivEngine::nextRow() { 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) { - dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + if (disCont[dispatchOfChan[i]].dispatch!=NULL) { + if (disCont[dispatchOfChan[i]].dispatch->getWantPreNote()) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + } if (song.oneTickCut) { bool doPrepareCut=true; From fce03717563eaf4fced702c0684b8af810c47f3a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 17:21:47 -0500 Subject: [PATCH 093/194] add "hint" commands --- src/engine/dispatch.h | 8 ++++++++ src/engine/playback.cpp | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index f1418e481..86b2e229e 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -55,6 +55,14 @@ enum DivDispatchCmds { DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide) DIV_CMD_PRE_NOTE, // used in C64 (note) + // these will be used in ROM export. + // do NOT implement! + DIV_CMD_HINT_VIBRATO, // (speed, depth) + DIV_CMD_HINT_VIBRATO_SHAPE, // (shape) + DIV_CMD_HINT_PITCH, // (pitch) + DIV_CMD_HINT_ARPEGGIO, // (note1, note2) + DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick) + DIV_CMD_SAMPLE_MODE, // (enabled) DIV_CMD_SAMPLE_FREQ, // (frequency) DIV_CMD_SAMPLE_BANK, // (bank) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6c4376573..b089b6f3e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -57,6 +57,12 @@ const char* cmdName[]={ "PRE_PORTA", "PRE_NOTE", + "HINT_VIBRATO", + "HINT_VIBRATO_SHAPE", + "HINT_PITCH", + "HINT_ARPEGGIO", + "HINT_VOL_SLIDE", + "SAMPLE_MODE", "SAMPLE_FREQ", "SAMPLE_BANK", From eafbf24290567d9ba3f0e0cd6da0c2a5fba112e3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 17:31:00 -0500 Subject: [PATCH 094/194] GUI: YM2423 patch names thanks freq-mod! --- src/gui/insEdit.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 958f662d4..f991b9e2a 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -89,24 +89,23 @@ const char* opllInsNames[4][17]={ "Noise", "Drums" }, - // help me get the names! /* YM2423 */ { "User", - "Violin", - "What is this", - "Some kind of space sound", - "Voice maybe", - "Clarinet", - "Slap something", + "Strings", + "Guitar", + "Electric Guitar", + "Electric Piano", + "Flute", + "Marimba", "Trumpet", - "AAAAAA", - "Wow!", - "Synth", - "Synth Key", + "Harmonica", + "Tuba", + "Synth Brass", + "Short Saw", "Vibraphone", - "Space Bass", + "Electric Guitar 2", "Synth Bass", - "Sound like the default Defle patch", + "Sitar", "Drums" }, // stolen from FamiTracker From 7ec4f7cb9e51c7016a753447d2b92ea7e9e8492a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 18:44:45 -0500 Subject: [PATCH 095/194] VGM export: add option to insert pattern change hi nts --- src/engine/engine.h | 4 +++- src/engine/vgmOps.cpp | 28 +++++++++++++++++++++++++++- src/gui/gui.cpp | 19 ++++++++++++++++++- src/gui/gui.h | 2 +- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 3030cfadb..8fd56ff91 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -472,7 +472,9 @@ class DivEngine { // specify system to build ROM for. SafeWriter* buildROM(int sys); // dump to VGM. - SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171); + SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false); + // dump command stream. + SafeWriter* saveCommand(bool binary=false); // export to an audio file bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0); // wait for audio export to finish diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index a500b4848..15de0156f 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -802,7 +802,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } } -SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { +SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) { if (version<0x150) { lastError="VGM version is too low"; return NULL; @@ -1795,6 +1795,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { playSub(false); size_t tickCount=0; bool writeLoop=false; + int ord=-1; + int exportChans=0; + for (int i=0; iwriteC(0x67); + w->writeC(0x66); + w->writeC(0xfe); + w->writeI(3+exportChans); + w->writeC(0x01); + w->writeC(prevOrder); + w->writeC(prevRow); + for (int i=0; iwriteC(curSubSong->orders.ord[i][prevOrder]); + } + } + } } // get register dumps for (int i=0; isong.systemLen; i++) { @@ -3468,7 +3484,7 @@ bool FurnaceGUI::loop() { } break; case GUI_FILE_EXPORT_VGM: { - SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); + SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints); if (w!=NULL) { FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { @@ -4431,6 +4447,7 @@ FurnaceGUI::FurnaceGUI(): displayError(false), displayExporting(false), vgmExportLoop(true), + vgmExportPatternHints(false), wantCaptureKeyboard(false), oldWantCaptureKeyboard(false), displayMacroMenu(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 30113953f..cb61d48dc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -952,7 +952,7 @@ class FurnaceGUI { String mmlString[32]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; bool displayPendingIns, pendingInsSingle; bool willExport[32]; From a0d10aa60be74a54c27e48ca7b3ed3d7027947c6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 3 Aug 2022 19:17:18 -0500 Subject: [PATCH 096/194] Game Boy: implement anti-click --- src/engine/platform/gb.cpp | 19 +++++++++++++++++-- src/engine/platform/gb.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 2b7dcf6df..4d10d7d22 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -97,10 +97,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformGB::updateWave() { rWrite(0x1a,0); for (int i=0; i<16; i++) { - int nibble1=15-ws.output[i<<1]; - int nibble2=15-ws.output[1+(i<<1)]; + int nibble1=15-ws.output[((i<<1)+antiClickWavePos)&31]; + int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos)&31]; rWrite(0x30+i,(nibble1<<4)|nibble2); } + antiClickWavePos&=31; } static unsigned char chanMuteMask[4]={ @@ -151,6 +152,12 @@ static unsigned char noiseTable[256]={ }; void DivPlatformGB::tick(bool sysTick) { + if (antiClickEnabled && sysTick && chan[2].freq>0) { + antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f)); + antiClickWavePos+=antiClickPeriodCount/chan[2].freq; + antiClickPeriodCount%=chan[2].freq; + } + for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.arp.had) { @@ -471,6 +478,9 @@ void DivPlatformGB::reset() { lastPan=0xff; immWrite(0x25,procMute()); immWrite(0x24,0x77); + + antiClickPeriodCount=0; + antiClickWavePos=0; } bool DivPlatformGB::isStereo() { @@ -507,6 +517,10 @@ void DivPlatformGB::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); } +void DivPlatformGB::setFlags(unsigned int flags) { + antiClickEnabled=!(flags&8); +} + int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { chipClock=4194304; rate=chipClock/16; @@ -519,6 +533,7 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl dumpWrites=false; skipRegisterWrites=false; gb=new GB_gameboy_t; + setFlags(flags); reset(); return 4; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index fe2f6e51b..b00458c2d 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -59,9 +59,12 @@ class DivPlatformGB: public DivDispatch { Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; + bool antiClickEnabled; unsigned char lastPan; DivWaveSynth ws; + int antiClickPeriodCount, antiClickWavePos; + GB_gameboy_t* gb; unsigned char regPool[128]; @@ -88,6 +91,7 @@ class DivPlatformGB: public DivDispatch { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformGB(); From d54d853ff83c14994151619b41f696a777b909e5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 00:51:47 -0500 Subject: [PATCH 097/194] add a command stream dump option --- src/engine/engine.cpp | 106 ++++++++++++++++++++++++++++++++++++++ src/engine/engine.h | 2 + src/engine/safeWriter.cpp | 3 ++ src/engine/safeWriter.h | 1 + src/gui/gui.cpp | 65 ++++++++++++++++++++++- src/gui/gui.h | 8 ++- 6 files changed, 182 insertions(+), 3 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2a000476d..9b532c0bf 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "dispatch.h" #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -234,6 +235,111 @@ double DivEngine::benchmarkSeek() { return tAvg; } +SafeWriter* DivEngine::saveCommand(bool binary) { + logI("implement! %d",binary); + stop(); + repeatPattern=false; + setOrder(0); + BUSY_BEGIN_SOFT; + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + walkSong(loopOrder,loopRow,loopEnd); + logI("loop point: %d %d",loopOrder,loopRow); + + SafeWriter* w=new SafeWriter; + w->init(); + + // write header + w->writeText("# Furnace Command Stream\n\n"); + + w->writeText("[Information]\n"); + w->writeText(fmt::sprintf("name: %s\n",song.name)); + w->writeText(fmt::sprintf("author: %s\n",song.author)); + w->writeText(fmt::sprintf("category: %s\n",song.category)); + w->writeText(fmt::sprintf("system: %s\n",song.systemName)); + + w->writeText("\n"); + + w->writeText("[SubSongInformation]\n"); + w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); + w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); + + w->writeText("\n"); + + w->writeText("[SysDefinition]\n"); + // TODO + + w->writeText("\n"); + + // play the song ourselves + bool done=false; + playSub(false); + + w->writeText("[Stream]\n"); + int tick=0; + bool oldCmdStreamEnabled=cmdStreamEnabled; + cmdStreamEnabled=true; + double curDivider=divider; + while (!done) { + if (nextTick(false,true) || !playing) { + done=true; + } + // get command stream + bool wroteTick=false; + if (curDivider!=divider) { + curDivider=divider; + if (!wroteTick) { + wroteTick=true; + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); + } + w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); + } + for (DivCommand& i: cmdStream) { + switch (i.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + default: + if (!wroteTick) { + wroteTick=true; + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); + } + w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); + break; + } + } + cmdStream.clear(); + tick++; + } + cmdStreamEnabled=oldCmdStreamEnabled; + + if (!playing) { + w->writeText(">> END\n"); + } else { + w->writeText(">> LOOP 0\n"); + } + + remainingLoops=-1; + playing=false; + freelance=false; + extValuePresent=false; + BUSY_END; + + return w; +} + void _runExportThread(DivEngine* caller) { caller->runExportThread(); } diff --git a/src/engine/engine.h b/src/engine/engine.h index 8fd56ff91..6f969efc9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -280,6 +280,8 @@ enum DivChanTypes { DIV_CH_OP=5 }; +extern const char* cmdName[]; + class DivEngine { DivDispatchContainer disCont[32]; TAAudio* output; diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index f29800a4a..e61380936 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -120,6 +120,9 @@ int SafeWriter::writeWString(WString val, bool pascal) { return 2+val.size()*2; } } +int SafeWriter::writeText(String val) { + return write(val.c_str(),val.size()); +} void SafeWriter::init() { if (operative) return; diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 9072c61d7..414417fd2 100644 --- a/src/engine/safeWriter.h +++ b/src/engine/safeWriter.h @@ -57,6 +57,7 @@ class SafeWriter { int writeD_BE(double val); int writeWString(WString val, bool pascal); int writeString(String val, bool pascal); + int writeText(String val); void init(); SafeReader* toReader(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c296ab2c3..28edd9e32 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1401,6 +1401,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_EXPORT_CMDSTREAM: + if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Command Stream", + {"text file", "*.txt", + "binary file", "*.bin"}, + "text file{.txt},binary file{.bin}", + workingDirROMExport, + dpiScale + ); + break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; @@ -2947,6 +2958,19 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("export command stream...")) { + ImGui::Text( + "this option exports a text or binary file which\n" + "contains a dump of the internal command stream\n" + "produced when playing the song.\n\n" + + "technical/development use only!" + ); + if (ImGui::Button("export")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + } + ImGui::EndMenu(); + } ImGui::Separator(); if (ImGui::BeginMenu("add system...")) { for (int j=0; availableSystems[j]; j++) { @@ -3258,9 +3282,12 @@ bool FurnaceGUI::loop() { workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_VGM: - case GUI_FILE_EXPORT_ROM: workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_EXPORT_ROM: + case GUI_FILE_EXPORT_CMDSTREAM: + workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; case GUI_FILE_LOAD_MAIN_FONT: case GUI_FILE_LOAD_PAT_FONT: workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; @@ -3325,6 +3352,11 @@ bool FurnaceGUI::loop() { if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); } + if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { + // we can't tell whether the user chose .txt or .bin in the system file picker + const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin"; + checkExtensionDual(".txt",".bin",fallbackExt); + } if (curFileDialog==GUI_FILE_EXPORT_COLORS) { checkExtension(".cfgc"); } @@ -3506,6 +3538,35 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; + case GUI_FILE_EXPORT_CMDSTREAM: { + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + bool isBinary=true; + if ((lowerCase.size()<4 || lowerCase.rfind(".txt")!=lowerCase.size()-4)) { + isBinary=false; + } + + SafeWriter* w=e->saveCommand(isBinary); + if (w!=NULL) { + FILE* f=ps_fopen(copyOfName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + showError("could not open file!"); + } + w->finish(); + delete w; + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + } else { + showError(fmt::sprintf("could not write command stream! (%s)",e->getLastError())); + } + break; + } case GUI_FILE_LOAD_MAIN_FONT: settings.mainFontPath=copyOfName; break; @@ -4099,6 +4160,7 @@ bool FurnaceGUI::init() { workingDirSample=e->getConfString("lastDirSample",workingDir); workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); + workingDirROMExport=e->getConfString("lastDirROMExport",workingDir); workingDirFont=e->getConfString("lastDirFont",workingDir); workingDirColors=e->getConfString("lastDirColors",workingDir); workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir); @@ -4339,6 +4401,7 @@ bool FurnaceGUI::finish() { e->setConf("lastDirSample",workingDirSample); e->setConf("lastDirAudioExport",workingDirAudioExport); e->setConf("lastDirVGMExport",workingDirVGMExport); + e->setConf("lastDirROMExport",workingDirROMExport); e->setConf("lastDirFont",workingDirFont); e->setConf("lastDirColors",workingDirColors); e->setConf("lastDirKeybinds",workingDirKeybinds); diff --git a/src/gui/gui.h b/src/gui/gui.h index cb61d48dc..0ce4afaee 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -266,6 +266,7 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_AUDIO_PER_SYS, GUI_FILE_EXPORT_AUDIO_PER_CHANNEL, GUI_FILE_EXPORT_VGM, + GUI_FILE_EXPORT_CMDSTREAM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, GUI_FILE_LOAD_PAT_FONT, @@ -948,11 +949,14 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM, workingDirTest; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport; + String workingDirVGMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds; + String workingDirLayout, workingDirROM, workingDirTest; String mmlString[32]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; + bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; bool displayPendingIns, pendingInsSingle; bool willExport[32]; From b030f8285d45c782c427b9ed8146fb47d6633d1e Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Thu, 4 Aug 2022 17:33:36 +1000 Subject: [PATCH 098/194] Bugfix for OPM file load - correctly handle AM-ENA where value is arbitrarily nonzero --- src/engine/fileOpsIns.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index b7ca22c1e..369b1238f 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -1262,7 +1262,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; newPatch = NULL; }; - auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) -> int { + auto readIntStrWithinRange = [](String&& input, int limitLow = INT_MIN, int limitHigh = INT_MAX) -> int { int x = std::stoi(input.c_str()); if (x > limitHigh || x < limitLow) { throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh)); @@ -1280,7 +1280,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15); op.dt = fmDtRegisterToFurnace(readIntStrWithinRange(reader.readStringToken(), 0, 7)); op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3); - op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1); + op.am = readIntStrWithinRange(reader.readStringToken(), 0) > 0 ? 1 : 0; }; auto seekGroupValStart = [](SafeReader& reader, int pos) { // Seek to position then move to next ':' character From edb0f51131215cfb0b3b53b54c566e7a6b5ffef7 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Thu, 4 Aug 2022 17:43:42 +1000 Subject: [PATCH 099/194] stdint required --- src/engine/fileOpsIns.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 369b1238f..e5ea58abc 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -21,6 +21,7 @@ #include "../ta-log.h" #include "../fileutils.h" #include +#include enum DivInsFormats { DIV_INSFORMAT_DMP, From 810eabca99851f6b769540c17169e0ee0bf79491 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Thu, 4 Aug 2022 17:50:33 +1000 Subject: [PATCH 100/194] derp limits --- src/engine/fileOpsIns.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index e5ea58abc..54e0eefc6 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -21,7 +21,7 @@ #include "../ta-log.h" #include "../fileutils.h" #include -#include +#include enum DivInsFormats { DIV_INSFORMAT_DMP, From 09e32c7050fba4ccd4c0b2a2dc82b66c28fb5d14 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 15:14:29 -0500 Subject: [PATCH 101/194] finish command dump hints --- src/engine/dispatch.h | 4 ++++ src/engine/playback.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 86b2e229e..fb9be5800 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -58,10 +58,14 @@ enum DivDispatchCmds { // these will be used in ROM export. // do NOT implement! DIV_CMD_HINT_VIBRATO, // (speed, depth) + DIV_CMD_HINT_VIBRATO_RANGE, // (range) DIV_CMD_HINT_VIBRATO_SHAPE, // (shape) DIV_CMD_HINT_PITCH, // (pitch) DIV_CMD_HINT_ARPEGGIO, // (note1, note2) + DIV_CMD_HINT_VOLUME, // (vol) DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick) + DIV_CMD_HINT_PORTA, // (target, speed) + DIV_CMD_HINT_LEGATO, // (note) DIV_CMD_SAMPLE_MODE, // (enabled) DIV_CMD_SAMPLE_FREQ, // (frequency) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index b089b6f3e..8fd20d5f9 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -58,10 +58,14 @@ const char* cmdName[]={ "PRE_NOTE", "HINT_VIBRATO", + "HINT_VIBRATO_RANGE", "HINT_VIBRATO_SHAPE", "HINT_PITCH", "HINT_ARPEGGIO", "HINT_VOL_SLIDE", + "HINT_VOLUME", + "HINT_PORTA", + "HINT_LEGATO", "SAMPLE_MODE", "SAMPLE_FREQ", @@ -344,6 +348,7 @@ void DivEngine::processRow(int i, bool afterDelay) { logV("forcing volume"); chan[i].volume=chan[i].volMax; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } } } @@ -356,11 +361,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -377,11 +384,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -398,6 +407,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (!chan[i].keyOn) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) { chan[i].arp=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); } } chan[i].doNote=true; @@ -414,6 +424,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } chan[i].volume=pat->data[whatRow][3]<<8; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } } @@ -458,11 +469,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { chan[i].portaNote=song.limitSlides?0x60:255; chan[i].portaSpeed=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=false; @@ -478,11 +491,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60; chan[i].portaSpeed=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=false; @@ -496,6 +511,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { @@ -509,6 +525,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=true; chan[i].wasShorthandPorta=false; } + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; if (chan[i].keyOn) chan[i].doNote=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -520,6 +537,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0x04: // vibrato chan[i].vibratoDepth=effectVal&15; chan[i].vibratoRate=effectVal>>4; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; case 0x07: // tremolo @@ -543,12 +561,14 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x00: // arpeggio chan[i].arp=effectVal; if (chan[i].arp==0 && song.arp0Reset) { chan[i].resetArp=true; } + dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); break; case 0x0c: // retrigger if (effectVal!=0) { @@ -580,6 +600,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xe1: // portamento up chan[i].portaNote=chan[i].note+(effectVal&15); chan[i].portaSpeed=(effectVal>>4)*4; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -598,6 +619,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xe2: // portamento down chan[i].portaNote=chan[i].note-(effectVal&15); chan[i].portaSpeed=(effectVal>>4)*4; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -615,9 +637,11 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xe3: // vibrato direction chan[i].vibratoDir=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoDir)); break; case 0xe4: // vibrato fine chan[i].vibratoFine=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_RANGE,i,chan[i].vibratoFine)); break; case 0xe5: // pitch chan[i].pitch=effectVal-0x80; @@ -628,6 +652,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } //chan[i].pitch+=globalPitch; dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch)); break; case 0xea: // legato mode chan[i].legato=effectVal; @@ -677,17 +702,21 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xf3: // fine volume ramp up chan[i].volSpeed=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xf4: // fine volume ramp down chan[i].volSpeed=-effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xf8: // single volume ramp up chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); break; case 0xf9: // single volume ramp down chan[i].volume=MAX(chan[i].volume-effectVal*256,0); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); break; case 0xfa: // fast volume ramp if (effectVal!=0) { @@ -699,6 +728,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xff: // stop song @@ -729,15 +759,18 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); if (chan[i].legato) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); } else { if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) { if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) { chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); chan[i].wasShorthandPorta=false; chan[i].inPorta=false; } else { chan[i].portaNote=chan[i].note; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); } } else if (!chan[i].noteOnInhibit) { dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8)); @@ -748,12 +781,14 @@ void DivEngine::processRow(int i, bool afterDelay) { if (!chan[i].keyOn && chan[i].scheduledSlideReset) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].scheduledSlideReset=false; chan[i].inPorta=false; } if (!chan[i].keyOn && chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } chan[i].keyOn=true; chan[i].keyOff=false; @@ -993,15 +1028,19 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); } else if (chan[i].volume<0) { chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); if (song.legacyVolumeSlides) { chan[i].volume=chan[i].volMax+1; } else { chan[i].volume=0; } dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } else { dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } @@ -1030,10 +1069,12 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { chan[i].portaSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].oldNote=chan[i].note; chan[i].note=chan[i].portaNote; chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); } } } @@ -1047,11 +1088,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -1065,6 +1108,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (chan[i].resetArp) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); chan[i].resetArp=false; } if (song.rowResetsArpPos && firstTick) { From 67e7e07048c30f781e3dce4433aa9d9e1bc985a4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 15:18:48 -0500 Subject: [PATCH 102/194] add -cmdout option --- src/main.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index f25d08e77..6e0173aba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,6 +52,7 @@ FurnaceCLI cli; String outName; String vgmOutName; +String cmdOutName; int loops=1; int benchMode=0; DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE; @@ -250,6 +251,12 @@ TAParamResult pVGMOut(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pCmdOut(String val) { + cmdOutName=val; + e.setAudio(DIV_AUDIO_DUMMY); + return TA_PARAM_SUCCESS; +} + bool needsValue(String param) { for (size_t i=0; i","output audio to file")); params.push_back(TAParam("O","vgmout",true,pVGMOut,"","output .vgm data")); + params.push_back(TAParam("C","cmdout",true,pCmdOut,"","output command stream")); params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)")); params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (pattern by default)")); params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); @@ -307,6 +315,7 @@ int main(int argc, char** argv) { #endif outName=""; vgmOutName=""; + cmdOutName=""; initParams(); @@ -443,7 +452,23 @@ int main(int argc, char** argv) { } return 0; } - if (outName!="" || vgmOutName!="") { + if (outName!="" || vgmOutName!="" || cmdOutName!="") { + if (cmdOutName!="") { + SafeWriter* w=e.saveCommand(false); + if (w!=NULL) { + FILE* f=fopen(cmdOutName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + reportError(fmt::sprintf("could not open file! (%s)",e.getLastError())); + } + w->finish(); + delete w; + } else { + reportError("could not write command stream!"); + } + } if (vgmOutName!="") { SafeWriter* w=e.saveVGM(); if (w!=NULL) { From 2e41d117d7b259ccb45c89a50602bdfdef368a67 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 17:47:59 -0500 Subject: [PATCH 103/194] fix some of these command hints --- src/engine/engine.cpp | 2 ++ src/engine/playback.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9b532c0bf..5d9224002 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -311,6 +311,8 @@ SafeWriter* DivEngine::saveCommand(bool binary) { break; case DIV_CMD_PITCH: break; + case DIV_CMD_PRE_NOTE: + break; default: if (!wroteTick) { wroteTick=true; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 8fd20d5f9..ee9115fd8 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -62,8 +62,8 @@ const char* cmdName[]={ "HINT_VIBRATO_SHAPE", "HINT_PITCH", "HINT_ARPEGGIO", - "HINT_VOL_SLIDE", "HINT_VOLUME", + "HINT_VOL_SLIDE", "HINT_PORTA", "HINT_LEGATO", @@ -340,8 +340,8 @@ void DivEngine::processRow(int i, bool afterDelay) { // instrument bool insChanged=false; if (pat->data[whatRow][2]!=-1) { - dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); if (chan[i].lastIns!=pat->data[whatRow][2]) { + dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); chan[i].lastIns=pat->data[whatRow][2]; insChanged=true; if (song.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) { From f2b6f854a99e12d6a8f026890f85d93d55c7dc7b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 17:48:10 -0500 Subject: [PATCH 104/194] add options to not install demo songs/ins --- CMakeLists.txt | 10 ++++++++-- README.md | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78792c983..e468d4073 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,8 @@ option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the ve option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF) option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT}) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) +option(WITH_DEMOS "Install demo songs" ON) +option(WITH_INSTRUMENTS "Install instruments" ON) set(DEPENDENCIES_INCLUDE_DIRS "") @@ -709,8 +711,12 @@ if (NOT ANDROID OR TERMUX) install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace) - install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) - install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + if (WITH_DEMOS) + install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + endif() + if (WITH_INSTRUMENTS) + install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + endif() foreach(num 16 32 64 128 256 512) set(res ${num}x${num}) install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps) diff --git a/README.md b/README.md index 1a2d88a7d..d2308dd75 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,8 @@ Available options: | `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one | | `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one | | `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors | +| `WITH_DEMOS` | `ON` | Install demo songs on `make install` | +| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` | ## console usage From 3a18e1e6fc4ef101b65a9d3ff5bc874fad430016 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 18:50:52 -0500 Subject: [PATCH 105/194] partially implement command stream binary mode --- papers/export-tech.md | 28 ++---- src/engine/engine.cpp | 192 ++++++++++++++++++++++++++++++++++++------ src/main.cpp | 9 +- 3 files changed, 181 insertions(+), 48 deletions(-) diff --git a/papers/export-tech.md b/papers/export-tech.md index 560aab324..dbfadece3 100644 --- a/papers/export-tech.md +++ b/papers/export-tech.md @@ -11,28 +11,16 @@ if it is a 2-byte macro, read a dummy byte. then read data. -## pattern data +## binary command stream -read sequentially. +read channel, command and values. -first byte determines what to read next: +if channel is 80 or higher, then it is a special command: ``` -NVI..EEE - -N: note -V: volume -I: instrument - -EEE: effect count (0-7) +fb xx xx xx xx: set tick rate +fc xx xx: wait xxxx ticks +fd xx: wait xx ticks +fe: wait one tick +ff: stop ``` - -if you read 0, end of pattern. -otherwise read in following order: - -1. note -2. volume -3. instrument -4. effect and effect value - -then read number of rows until next value, minus 1. diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 5d9224002..fd94e521a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -235,8 +235,131 @@ double DivEngine::benchmarkSeek() { return tAvg; } +#define WRITE_TICK \ + if (!wroteTick) { \ + wroteTick=true; \ + if (binary) { \ + if (tick-lastTick>255) { \ + w->writeC(0xfc); \ + w->writeS(tick-lastTick); \ + } else if (tick-lastTick>1) { \ + w->writeC(0xfd); \ + w->writeC(tick-lastTick); \ + } else { \ + w->writeC(0xfe); \ + } \ + } else { \ + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ + } \ + lastTick=tick; \ + } + +void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { + w->writeC(c.cmd); + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + case DIV_CMD_HINT_LEGATO: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xff); + } else { + w->writeC(c.value+60); + } + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + break; + case DIV_CMD_INSTRUMENT: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_VOLUME: + case DIV_CMD_SAMPLE_MODE: + case DIV_CMD_SAMPLE_FREQ: + case DIV_CMD_SAMPLE_BANK: + case DIV_CMD_SAMPLE_POS: + case DIV_CMD_SAMPLE_DIR: + case DIV_CMD_FM_HARD_RESET: + case DIV_CMD_FM_LFO: + case DIV_CMD_FM_LFO_WAVE: + case DIV_CMD_FM_FB: + case DIV_CMD_FM_EXTCH: + case DIV_CMD_FM_AM_DEPTH: + case DIV_CMD_FM_PM_DEPTH: + case DIV_CMD_STD_NOISE_FREQ: + case DIV_CMD_STD_NOISE_MODE: + case DIV_CMD_WAVE: + case DIV_CMD_GB_SWEEP_TIME: + case DIV_CMD_GB_SWEEP_DIR: + case DIV_CMD_PCE_LFO_MODE: + case DIV_CMD_PCE_LFO_SPEED: + case DIV_CMD_NES_DMC: + case DIV_CMD_C64_CUTOFF: + case DIV_CMD_C64_RESONANCE: + case DIV_CMD_C64_FILTER_MODE: + case DIV_CMD_C64_RESET_TIME: + case DIV_CMD_C64_RESET_MASK: + case DIV_CMD_C64_FILTER_RESET: + case DIV_CMD_C64_DUTY_RESET: + case DIV_CMD_C64_EXTENDED: + case DIV_CMD_AY_ENVELOPE_SET: + case DIV_CMD_AY_ENVELOPE_LOW: + case DIV_CMD_AY_ENVELOPE_HIGH: + case DIV_CMD_AY_ENVELOPE_SLIDE: + case DIV_CMD_AY_NOISE_MASK_AND: + case DIV_CMD_AY_NOISE_MASK_OR: + case DIV_CMD_AY_AUTO_ENVELOPE: + w->writeC(c.value); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + case DIV_CMD_FM_TL: + case DIV_CMD_FM_AM: + case DIV_CMD_FM_AR: + case DIV_CMD_FM_DR: + case DIV_CMD_FM_SL: + case DIV_CMD_FM_D2R: + case DIV_CMD_FM_RR: + case DIV_CMD_FM_DT: + case DIV_CMD_FM_DT2: + case DIV_CMD_FM_RS: + case DIV_CMD_FM_KSR: + case DIV_CMD_FM_VIB: + case DIV_CMD_FM_SUS: + case DIV_CMD_FM_WS: + case DIV_CMD_FM_SSG: + case DIV_CMD_FM_REV: + case DIV_CMD_FM_EG_SHIFT: + case DIV_CMD_FM_MULT: + case DIV_CMD_FM_FINE: + case DIV_CMD_AY_IO_WRITE: + case DIV_CMD_AY_AUTO_PWM: + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_PRE_PORTA: + w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); + break; + case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_C64_FINE_DUTY: + case DIV_CMD_C64_FINE_CUTOFF: + w->writeS(c.value); + break; + case DIV_CMD_FM_FIXFREQ: + w->writeS((c.value<<12)|(c.value2&0x7ff)); + break; + case DIV_CMD_NES_SWEEP: + w->writeC((c.value?8:0)|(c.value2&0x77)); + break; + default: + logW("unimplemented command %s!",cmdName[c.cmd]); + break; + } +} + SafeWriter* DivEngine::saveCommand(bool binary) { - logI("implement! %d",binary); stop(); repeatPattern=false; setOrder(0); @@ -252,36 +375,43 @@ SafeWriter* DivEngine::saveCommand(bool binary) { w->init(); // write header - w->writeText("# Furnace Command Stream\n\n"); + if (binary) { + w->write("FCS",4); + } else { + w->writeText("# Furnace Command Stream\n\n"); - w->writeText("[Information]\n"); - w->writeText(fmt::sprintf("name: %s\n",song.name)); - w->writeText(fmt::sprintf("author: %s\n",song.author)); - w->writeText(fmt::sprintf("category: %s\n",song.category)); - w->writeText(fmt::sprintf("system: %s\n",song.systemName)); + w->writeText("[Information]\n"); + w->writeText(fmt::sprintf("name: %s\n",song.name)); + w->writeText(fmt::sprintf("author: %s\n",song.author)); + w->writeText(fmt::sprintf("category: %s\n",song.category)); + w->writeText(fmt::sprintf("system: %s\n",song.systemName)); - w->writeText("\n"); + w->writeText("\n"); - w->writeText("[SubSongInformation]\n"); - w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); - w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); + w->writeText("[SubSongInformation]\n"); + w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); + w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); - w->writeText("\n"); + w->writeText("\n"); - w->writeText("[SysDefinition]\n"); - // TODO + w->writeText("[SysDefinition]\n"); + // TODO - w->writeText("\n"); + w->writeText("\n"); + } // play the song ourselves bool done=false; playSub(false); - w->writeText("[Stream]\n"); + if (!binary) { + w->writeText("[Stream]\n"); + } int tick=0; bool oldCmdStreamEnabled=cmdStreamEnabled; cmdStreamEnabled=true; double curDivider=divider; + int lastTick=0; while (!done) { if (nextTick(false,true) || !playing) { done=true; @@ -290,11 +420,13 @@ SafeWriter* DivEngine::saveCommand(bool binary) { bool wroteTick=false; if (curDivider!=divider) { curDivider=divider; - if (!wroteTick) { - wroteTick=true; - w->writeText(fmt::sprintf(">> TICK %d\n",tick)); + WRITE_TICK; + if (binary) { + w->writeC(0xfb); + w->writeI((int)(curDivider*65536)); + } else { + w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); } - w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); } for (DivCommand& i: cmdStream) { switch (i.cmd) { @@ -314,11 +446,13 @@ SafeWriter* DivEngine::saveCommand(bool binary) { case DIV_CMD_PRE_NOTE: break; default: - if (!wroteTick) { - wroteTick=true; - w->writeText(fmt::sprintf(">> TICK %d\n",tick)); + WRITE_TICK; + if (binary) { + w->writeC(i.chan); + writePackedCommandValues(w,i); + } else { + w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); } - w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); break; } } @@ -327,10 +461,14 @@ SafeWriter* DivEngine::saveCommand(bool binary) { } cmdStreamEnabled=oldCmdStreamEnabled; - if (!playing) { - w->writeText(">> END\n"); + if (binary) { + w->writeC(0xff); } else { - w->writeText(">> LOOP 0\n"); + if (!playing) { + w->writeText(">> END\n"); + } else { + w->writeText(">> LOOP 0\n"); + } } remainingLoops=-1; diff --git a/src/main.cpp b/src/main.cpp index 6e0173aba..39c9e6ca2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -64,6 +64,7 @@ bool consoleMode=true; #endif bool displayEngineFailError=false; +bool cmdOutBinary=false; std::vector params; @@ -115,6 +116,11 @@ TAParamResult pConsole(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pBinary(String val) { + cmdOutBinary=true; + return TA_PARAM_SUCCESS; +} + TAParamResult pLogLevel(String val) { if (val=="trace") { logLevel=LOGLEVEL_TRACE; @@ -273,6 +279,7 @@ void initParams() { params.push_back(TAParam("o","output",true,pOutput,"","output audio to file")); params.push_back(TAParam("O","vgmout",true,pVGMOut,"","output .vgm data")); params.push_back(TAParam("C","cmdout",true,pCmdOut,"","output command stream")); + params.push_back(TAParam("b","binary",false,pBinary,"","set command stream output format to binary")); params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)")); params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (pattern by default)")); params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); @@ -454,7 +461,7 @@ int main(int argc, char** argv) { } if (outName!="" || vgmOutName!="" || cmdOutName!="") { if (cmdOutName!="") { - SafeWriter* w=e.saveCommand(false); + SafeWriter* w=e.saveCommand(cmdOutBinary); if (w!=NULL) { FILE* f=fopen(cmdOutName.c_str(),"wb"); if (f!=NULL) { From 049ab065449d4237734ff6fb2214fe09482d255f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 4 Aug 2022 23:37:28 -0500 Subject: [PATCH 106/194] PCE: add option to pick A/non-A revision of chip --- src/engine/platform/pce.cpp | 13 +++++++++++-- src/gui/sysConf.cpp | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index a61b61836..d46e012ce 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -569,6 +569,12 @@ void DivPlatformPCE::setFlags(unsigned int flags) { for (int i=0; i<6; i++) { oscBuf[i]->rate=rate; } + + if (pce!=NULL) { + delete pce; + pce=NULL; + } + pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280); } void DivPlatformPCE::poke(unsigned int addr, unsigned short val) { @@ -587,8 +593,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } + pce=NULL; setFlags(flags); - pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A); reset(); return 6; } @@ -597,7 +603,10 @@ void DivPlatformPCE::quit() { for (int i=0; i<6; i++) { delete oscBuf[i]; } - delete pce; + if (pce!=NULL) { + delete pce; + pce=NULL; + } } DivPlatformPCE::~DivPlatformPCE() { diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 7c624f309..4cc1784a6 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -118,6 +118,13 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::Checkbox("Disable anti-click",&antiClick)) { copyOfFlags=(flags&(~8))|(antiClick<<3); } + ImGui::Text("Chip revision:"); + if (ImGui::RadioButton("HuC6280 (original)",(flags&4)==0)) { + copyOfFlags=(flags&(~4))|0; + } + if (ImGui::RadioButton("HuC6280A (SuperGrafx)",(flags&4)==4)) { + copyOfFlags=(flags&(~4))|4; + } break; } case DIV_SYSTEM_SOUND_UNIT: { From 827904d46e1999d421fd0b9917ce0266bef07fcf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 5 Aug 2022 00:05:36 -0500 Subject: [PATCH 107/194] what happens if I force arm64 on macOS CI? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e060924f..ed4710879 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -201,7 +201,7 @@ jobs: elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') elif [ '${{ runner.os }}' == 'macOS' ]; then - CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') + CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"' '-DCMAKE_OSX_ARCHITECTURES=arm64') fi cmake \ From a0968aed0719a032c131b24ce583abaaa2e25e00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 5 Aug 2022 03:27:35 -0500 Subject: [PATCH 108/194] GUI: fix text/binary command stream outs being swa --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 28edd9e32..c1cd7f076 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3544,7 +3544,7 @@ bool FurnaceGUI::loop() { if (i>='A' && i<='Z') i+='a'-'A'; } bool isBinary=true; - if ((lowerCase.size()<4 || lowerCase.rfind(".txt")!=lowerCase.size()-4)) { + if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) { isBinary=false; } From 1d30febff8497b2bfdc99c81591914de3480b2af Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 5 Aug 2022 15:39:13 -0500 Subject: [PATCH 109/194] oh yeah --- .github/workflows/build.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed4710879..5ee82f0da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,8 @@ jobs: - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - - { name: 'macOS', os: macos-latest } + - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } + - { name: 'macOS ARM', os: macos-latest, arch: arm64 } - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false @@ -201,7 +202,11 @@ jobs: elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') elif [ '${{ runner.os }}' == 'macOS' ]; then - CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"' '-DCMAKE_OSX_ARCHITECTURES=arm64') + if [ '${{ matrix.config.arch }}' == 'arm64' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64') + else + CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') + fi fi cmake \ From 3e70ed6d3eb22d42c51e030f367c5321796124bd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 5 Aug 2022 17:22:07 -0500 Subject: [PATCH 110/194] CI: fix artifact conflict --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ee82f0da..da0cfbfae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: package_name="${package_name}-${{ matrix.config.arch }}" package_ext="" # Directory, uploading will automatically zip it elif [ '${{ runner.os }}' == 'macOS' ]; then - package_name="${package_name}-macOS" + package_name="${package_name}-macOS-${{ matrix.config.arch }}" package_ext=".dmg" else package_name="${package_name}-Linux" From 6ec9cceb091dbd70966f099510f72d4be0db662e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 00:34:28 -0500 Subject: [PATCH 111/194] PCE: remove some sample playback clicking --- src/engine/platform/pce.cpp | 9 ++++++++- src/engine/platform/pce.h | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index d46e012ce..757cff159 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -133,6 +133,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformPCE::updateWave(int ch) { + if (chan[ch].pcm) { + chan[ch].deferredWaveUpdate=true; + return; + } chWrite(ch,0x04,0x5f); chWrite(ch,0x04,0x1f); for (int i=0; i<32; i++) { @@ -142,6 +146,9 @@ void DivPlatformPCE::updateWave(int ch) { if (chan[ch].active) { chWrite(ch,0x04,0x80|chan[ch].outVol); } + if (chan[ch].deferredWaveUpdate) { + chan[ch].deferredWaveUpdate=false; + } } // TODO: in octave 6 the noise table changes to a tonal one @@ -227,7 +234,7 @@ void DivPlatformPCE::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].active) { - if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) { + if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) { updateWave(i); } } diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 22a24ddf8..17e191d44 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -33,7 +33,7 @@ class DivPlatformPCE: public DivDispatch { unsigned int dacPos; int dacSample, ins; unsigned char pan; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate; signed char vol, outVol, wave; DivMacroInt std; DivWaveSynth ws; @@ -64,6 +64,7 @@ class DivPlatformPCE: public DivDispatch { noise(false), pcm(false), furnaceDac(false), + deferredWaveUpdate(false), vol(31), outVol(31), wave(-1) {} From 8a7d352ec6071c3d7f150953ba81f23c3b249f1d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 00:38:24 -0500 Subject: [PATCH 112/194] PCE: fix phase reset macro when anti-click is on --- src/engine/platform/pce.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 757cff159..5d9a0de88 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -233,6 +233,10 @@ void DivPlatformPCE::tick(bool sysTick) { } chan[i].freqChanged=true; } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + chan[i].antiClickWavePos=0; + chan[i].antiClickPeriodCount=0; + } if (chan[i].active) { if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) { updateWave(i); From 0946d2388301cd59407d448a1e16686fe76a72e9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 00:39:09 -0500 Subject: [PATCH 113/194] Game Boy: fix phase reset macro when anti-click is --- src/engine/platform/gb.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 4d10d7d22..4067e9f17 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -220,6 +220,10 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; + if (i==2) { + antiClickWavePos=0; + antiClickPeriodCount=0; + } } } if (i==2) { From 5534f55f7aabb7814c65693972558a5062de61cc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 04:04:18 -0500 Subject: [PATCH 114/194] dev104 - add "is sample" flag to Sound Unit ins --- papers/format.md | 3 +++ src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 10 ++++++++++ src/engine/instrument.h | 9 +++++++++ src/engine/platform/su.cpp | 6 +++--- src/gui/insEdit.cpp | 24 +++++++++++++++--------- 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/papers/format.md b/papers/format.md index ca8092a5a..97e22a4e5 100644 --- a/papers/format.md +++ b/papers/format.md @@ -810,6 +810,9 @@ size | description 1 | vib depth 1 | am depth 23 | reserved + --- | **Sound Unit data** (>=104) + 1 | use sample + 1 | switch roles of phase reset timer and frequency ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 6f969efc9..256b0c96d 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 "dev103" -#define DIV_ENGINE_VERSION 103 +#define DIV_VERSION "dev104" +#define DIV_ENGINE_VERSION 104 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index a76588561..4d274e18c 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -528,6 +528,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(0); } + // Sound Unit + w->writeC(su.useSample); + w->writeC(su.switchRoles); + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); @@ -1075,6 +1079,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { for (int k=0; k<23; k++) reader.readC(); } + // Sound Unit + if (version>=104) { + su.useSample=reader.readC(); + su.switchRoles=reader.readC(); + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 18bf2c5f7..1c25988e4 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -437,6 +437,14 @@ struct DivInstrumentWaveSynth { param4(0) {} }; +struct DivInstrumentSoundUnit { + bool useSample; + bool switchRoles; + DivInstrumentSoundUnit(): + useSample(false), + switchRoles(false) {} +}; + struct DivInstrument { String name; bool mode; @@ -450,6 +458,7 @@ struct DivInstrument { DivInstrumentFDS fds; DivInstrumentMultiPCM multipcm; DivInstrumentWaveSynth ws; + DivInstrumentSoundUnit su; /** * save the instrument to a SafeWriter. diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 2d1a38932..4320798ab 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -256,12 +256,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU); - if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) { - chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); + if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) { + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample); writeControl(c.chan); writeControlUpper(c.chan); } - chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f991b9e2a..8e405251e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3093,13 +3093,17 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } - if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Sample")) { + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SU) if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) { String sName; if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) { sName="none selected"; } else { sName=e->song.sample[ins->amiga.initSample]->name; } + if (ins->type==DIV_INS_SU) { + P(ImGui::Checkbox("Use sample",&ins->su.useSample)); + P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles)); + } if (ImGui::BeginCombo("Initial Sample",sName.c_str())) { String id; for (int i=0; isong.sampleLen; i++) { @@ -3110,14 +3114,16 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } - P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave)); - if (ins->amiga.useWave) { - int len=ins->amiga.waveLen+1; - if (ImGui::InputInt("Width",&len,2,16)) { - if (len<2) len=2; - if (len>256) len=256; - ins->amiga.waveLen=(len&(~1))-1; - PARAMETER + if (ins->type==DIV_INS_AMIGA) { + P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave)); + if (ins->amiga.useWave) { + int len=ins->amiga.waveLen+1; + if (ImGui::InputInt("Width",&len,2,16)) { + if (len<2) len=2; + if (len>256) len=256; + ins->amiga.waveLen=(len&(~1))-1; + PARAMETER + } } } ImGui::BeginDisabled(ins->amiga.useWave); From a84129621937389d444cf09fe61544df8194a627 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 6 Aug 2022 17:50:15 +0200 Subject: [PATCH 115/194] Y8950: PCM -> ADPCM also where the f is my write access --- src/engine/sysDef.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 740854071..c5541baa3 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1850,8 +1850,8 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef( "Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false, "like OPL but with an ADPCM channel.", - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"}, - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"}, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "ADPCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA}, {}, @@ -1862,8 +1862,8 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef( "Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false, "the Y8950 chip, in drums mode.", - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "PCM"}, - {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"}, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "ADPCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_AMIGA}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_NULL}, From f03123fd75a0543efc5d6af3c9709eeea342748f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 16:22:57 -0500 Subject: [PATCH 116/194] SoundUnit: implement missing input line emulation --- src/engine/platform/sound/su.cpp | 65 ++++++++++++++++++++++++++++++-- src/engine/platform/sound/su.h | 8 +++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp index 0dd6dcdc7..a2fd76534 100644 --- a/src/engine/platform/sound/su.cpp +++ b/src/engine/platform/sound/su.cpp @@ -5,9 +5,22 @@ #define minval(a,b) (((a)<(b))?(a):(b)) #define maxval(a,b) (((a)>(b))?(a):(b)) +#define FILVOL chan[4].special1C +#define ILCTRL chan[4].special1D +#define ILSIZE chan[5].special1C +#define FIL1 chan[5].special1D +#define IL1 chan[6].special1C +#define IL2 chan[6].special1D +#define IL0 chan[7].special1C +#define MVOL chan[7].special1D + void SoundUnit::NextSample(short* l, short* r) { + // run channels for (int i=0; i<8; i++) { - if (chan[i].vol==0 && !chan[i].flags.swvol) {fns[i]=0; continue;} + if (chan[i].vol==0 && !chan[i].flags.swvol) { + fns[i]=0; + continue; + } if (chan[i].flags.pcm) { ns[i]=pcm[chan[i].pcmpos]; } else switch (chan[i].flags.shape) { @@ -48,7 +61,6 @@ void SoundUnit::NextSample(short* l, short* r) { pcmdec[i]-=32768; if (chan[i].pcmpos>2; tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2; - + + // write input lines to sample memory + if (ILSIZE&64) { + if (++ilBufPeriod>=((1+(FIL1>>4))<<2)) { + ilBufPeriod=0; + unsigned short ilLowerBound=pcmSize-((1+(ILSIZE&63))<<7); + if (ilBufPos>4); + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + case 1: + ilFeedback0=ilFeedback1=pcm[ilBufPos]; + pcm[ilBufPos]=IL1+((pcm[ilBufPos]*(FIL1&15))>>4); + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + case 2: + ilFeedback0=ilFeedback1=pcm[ilBufPos]; + pcm[ilBufPos]=IL2+((pcm[ilBufPos]*(FIL1&15))>>4); + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + case 3: + ilFeedback0=pcm[ilBufPos]; + pcm[ilBufPos]=IL1+((pcm[ilBufPos]*(FIL1&15))>>4); + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + ilFeedback1=pcm[ilBufPos]; + pcm[ilBufPos]=IL2+((pcm[ilBufPos]*(FIL1&15))>>4); + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + } + } + if (ILSIZE&128) { + tnsL+=ilFeedback1*(signed char)FILVOL; + tnsR+=ilFeedback0*(signed char)FILVOL; + } else { + tnsL+=ilFeedback0*(signed char)FILVOL; + tnsR+=ilFeedback1*(signed char)FILVOL; + } + } + *l=minval(32767,maxval(-32767,tnsL)); *r=minval(32767,maxval(-32767,tnsR)); } @@ -272,6 +327,10 @@ void SoundUnit::Reset() { } tnsL=0; tnsR=0; + ilBufPos=0; + ilBufPeriod=0; + ilFeedback0=0; + ilFeedback1=0; memset(chan,0,sizeof(SUChannel)*8); } diff --git a/src/engine/platform/sound/su.h b/src/engine/platform/sound/su.h index 3b125f030..0ee843481 100644 --- a/src/engine/platform/sound/su.h +++ b/src/engine/platform/sound/su.h @@ -20,6 +20,10 @@ class SoundUnit { int nshigh[8]; int nsband[8]; int tnsL, tnsR; + unsigned char ilBufPeriod; + unsigned short ilBufPos; + signed char ilFeedback0; + signed char ilFeedback1; unsigned short oldfreq[8]; unsigned short oldflags[8]; unsigned int pcmSize; @@ -80,11 +84,13 @@ class SoundUnit { unsigned char dir: 1; unsigned char bound; } swcut; - unsigned short wc; + unsigned char special1C; + unsigned char special1D; unsigned short restimer; } chan[8]; signed char pcm[65536]; bool muted[8]; + void SetIL0(unsigned char addr); void Write(unsigned char addr, unsigned char data); void NextSample(short* l, short* r); inline int GetSample(int ch) { From 6934a499c10da650bf8cb67c70dc98d38f71194d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 18:23:11 -0500 Subject: [PATCH 117/194] SoundUnit: actually finish it --- src/engine/platform/sound/su.cpp | 43 +++++++++++++++++++++++-------- src/engine/platform/su.cpp | 16 +++++++++++- src/engine/platform/su.h | 3 +++ src/gui/sysConf.cpp | 44 +++++++++++++++++++++++++++++--- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp index a2fd76534..6637bfa7d 100644 --- a/src/engine/platform/sound/su.cpp +++ b/src/engine/platform/sound/su.cpp @@ -238,44 +238,65 @@ void SoundUnit::NextSample(short* l, short* r) { tnsL=(nsL[0]+nsL[1]+nsL[2]+nsL[3]+nsL[4]+nsL[5]+nsL[6]+nsL[7])>>2; tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2; + IL1=minval(32767,maxval(-32767,tnsL))>>8; + IL2=minval(32767,maxval(-32767,tnsR))>>8; + // write input lines to sample memory if (ILSIZE&64) { if (++ilBufPeriod>=((1+(FIL1>>4))<<2)) { ilBufPeriod=0; unsigned short ilLowerBound=pcmSize-((1+(ILSIZE&63))<<7); + short next; if (ilBufPos>4); + next=((signed char)IL0)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; break; case 1: ilFeedback0=ilFeedback1=pcm[ilBufPos]; - pcm[ilBufPos]=IL1+((pcm[ilBufPos]*(FIL1&15))>>4); + next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; break; case 2: ilFeedback0=ilFeedback1=pcm[ilBufPos]; - pcm[ilBufPos]=IL2+((pcm[ilBufPos]*(FIL1&15))>>4); + next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; break; case 3: ilFeedback0=pcm[ilBufPos]; - pcm[ilBufPos]=IL1+((pcm[ilBufPos]*(FIL1&15))>>4); + next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; ilFeedback1=pcm[ilBufPos]; - pcm[ilBufPos]=IL2+((pcm[ilBufPos]*(FIL1&15))>>4); + next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; break; } } - if (ILSIZE&128) { - tnsL+=ilFeedback1*(signed char)FILVOL; - tnsR+=ilFeedback0*(signed char)FILVOL; - } else { - tnsL+=ilFeedback0*(signed char)FILVOL; - tnsR+=ilFeedback1*(signed char)FILVOL; + if (ILCTRL&4) { + if (ILSIZE&128) { + tnsL+=ilFeedback1*(signed char)FILVOL; + tnsR+=ilFeedback0*(signed char)FILVOL; + } else { + tnsL+=ilFeedback0*(signed char)FILVOL; + tnsR+=ilFeedback1*(signed char)FILVOL; + } } } diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 4320798ab..6c6b0b329 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -529,6 +529,16 @@ void DivPlatformSoundUnit::reset() { lfoMode=0; lfoSpeed=255; delay=500; + + // set initial IL status + ilCtrl=initIlCtrl; + ilSize=initIlSize; + fil1=initFil1; + echoVol=initEchoVol; + rWrite(0x9c,echoVol); + rWrite(0x9d,ilCtrl); + rWrite(0xbc,ilSize); + rWrite(0xbd,fil1); } bool DivPlatformSoundUnit::isStereo() { @@ -555,6 +565,10 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) { for (int i=0; i<8; i++) { oscBuf[i]->rate=rate; } + initIlCtrl=3|(flags&4); + initIlSize=((flags>>8)&63)|((flags&4)?0x40:0)|((flags&8)?0x80:0); + initFil1=flags>>16; + initEchoVol=flags>>24; sampleMemSize=flags&16; @@ -575,7 +589,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) { } size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) { - return (index==0)?(sampleMemSize?65536:8192):0; + return (index==0)?((sampleMemSize?65536:8192)-((initIlSize&64)?((1+(initIlSize&63))<<7):0)):0; } size_t DivPlatformSoundUnit::getSampleMemUsage(int index) { diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index e882c398e..9972599d4 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -97,6 +97,9 @@ class DivPlatformSoundUnit: public DivDispatch { std::queue writes; unsigned char lastPan; bool sampleMemSize; + unsigned char ilCtrl, ilSize, fil1; + unsigned char initIlCtrl, initIlSize, initFil1; + signed char echoVol, initEchoVol; int cycles, curChan, delay; short tempL; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 4cc1784a6..3259f00c0 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -129,11 +129,11 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } case DIV_SYSTEM_SOUND_UNIT: { ImGui::Text("CPU rate:"); - if (ImGui::RadioButton("6.18MHz (NTSC)",(flags&15)==0)) { - copyOfFlags=(flags&(~15))|0; + if (ImGui::RadioButton("6.18MHz (NTSC)",(flags&3)==0)) { + copyOfFlags=(flags&(~3))|0; } - if (ImGui::RadioButton("5.95MHz (PAL)",(flags&15)==1)) { - copyOfFlags=(flags&(~15))|1; + if (ImGui::RadioButton("5.95MHz (PAL)",(flags&3)==1)) { + copyOfFlags=(flags&(~3))|1; } ImGui::Text("Chip revision (sample memory):"); if (ImGui::RadioButton("A/B/E (8K)",(flags&16)==0)) { @@ -142,6 +142,42 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::RadioButton("D/F (64K)",(flags&16)==16)) { copyOfFlags=(flags&(~16))|16; } + bool echo=flags&4; + if (ImGui::Checkbox("Enable echo",&echo)) { + copyOfFlags=(flags&(~4))|(echo<<2); + } + bool flipEcho=flags&8; + if (ImGui::Checkbox("Swap echo channels",&flipEcho)) { + copyOfFlags=(flags&(~8))|(flipEcho<<3); + } + ImGui::Text("Echo delay:"); + int echoBufSize=(flags&0x3f00)>>8; + if (CWSliderInt("##EchoBufSize",&echoBufSize,0,63)) { + if (echoBufSize<0) echoBufSize=0; + if (echoBufSize>63) echoBufSize=63; + copyOfFlags=(flags&~0x3f00)|(echoBufSize<<8); + } rightClickable + ImGui::Text("Echo resolution:"); + int echoResolution=(flags&0xf00000)>>20; + if (CWSliderInt("##EchoResolution",&echoResolution,0,15)) { + if (echoResolution<0) echoResolution=0; + if (echoResolution>15) echoResolution=15; + copyOfFlags=(flags&(~0xf00000))|(echoResolution<<20); + } rightClickable + ImGui::Text("Echo feedback:"); + int echoFeedback=(flags&0xf0000)>>16; + if (CWSliderInt("##EchoFeedback",&echoFeedback,0,15)) { + if (echoFeedback<0) echoFeedback=0; + if (echoFeedback>15) echoFeedback=15; + copyOfFlags=(flags&(~0xf0000))|(echoFeedback<<16); + } rightClickable + ImGui::Text("Echo volume:"); + int echoVolume=(signed char)((flags&0xff000000)>>24); + if (CWSliderInt("##EchoVolume",&echoVolume,-128,127)) { + if (echoVolume<-128) echoVolume=-128; + if (echoVolume>127) echoVolume=127; + copyOfFlags=(flags&(~0xff000000))|(((unsigned char)echoVolume)<<24); + } rightClickable break; } case DIV_SYSTEM_GB: { From f439bd2234b2d02de7b961bbbe31e01eb4c7ccf3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 19:22:00 -0500 Subject: [PATCH 118/194] CI: Cross-Linux-armhf trial, part 1 --- .github/workflows/build.yml | 48 ++++++++++++++++++++++++++------- scripts/Cross-Linux-armhf.cmake | 15 +++++++++++ 2 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 scripts/Cross-Linux-armhf.cmake diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da0cfbfae..f3132f1d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,8 @@ jobs: - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } - { name: 'macOS ARM', os: macos-latest, arch: arm64 } - - { name: 'Ubuntu', os: ubuntu-18.04 } + - { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 } + - { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf } fail-fast: false name: ${{ matrix.config.name }} @@ -80,7 +81,7 @@ jobs: package_name="${package_name}-macOS-${{ matrix.config.arch }}" package_ext=".dmg" else - package_name="${package_name}-Linux" + package_name="${package_name}-Linux-${{ matrix.config.arch }}" package_ext=".AppImage" fi @@ -117,8 +118,8 @@ jobs: mingw-w64 \ mingw-w64-tools - - name: Install Dependencies [Ubuntu] - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + - name: Install Dependencies [Linux x86_64] + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | sudo apt update sudo apt install \ @@ -132,8 +133,29 @@ jobs: wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" chmod +x appimagetool-x86_64.AppImage + - name: Install Dependencies [Linux armhf] + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }} + run: | + sudo sed -ri "s/^deb /deb [arch=amd64] /" /etc/apt/sources.list + echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" | sudo tee -a /etc/apt/sources.list + echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" | sudo tee -a /etc/apt/sources.list + sudo apt update + sudo dpkg --add-architecture armhf + sudo apt install \ + crossbuild-essential-armhf \ + libsdl2-dev:armhf \ + libfmt-dev:armhf \ + librtmidi-dev:armhf \ + libsndfile1-dev:armhf \ + zlib1g-dev:armhf \ + libjack-jackd2-dev:armhf \ + appstream + wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" + wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" + chmod +x appimagetool-x86_64.AppImage + - name: Configure (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() @@ -164,7 +186,7 @@ jobs: "${CMAKE_EXTRA_ARGS[@]}" - name: Build (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | cmake \ --build ${PWD}/build \ @@ -172,14 +194,14 @@ jobs: --parallel ${{ steps.build-cores.outputs.amount }} - name: Install (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | cmake \ --install ${PWD}/build \ --config ${{ env.BUILD_TYPE }} - name: Cleanup (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | rm -rf build/ target/ @@ -207,6 +229,8 @@ jobs: else CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') fi + elif [ '${{ runner.os }}' == 'Linux' && '${{ matrix.config.arch }}' == 'armhf' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-Linux-armhf.cmake') fi cmake \ @@ -260,7 +284,7 @@ jobs: mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }} popd - - name: Package [Ubuntu] + - name: Package [Linux] if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | #if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then @@ -278,7 +302,11 @@ jobs: cp -v ../../res/AppRun ./ popd - ../appimagetool-x86_64.AppImage furnace.AppDir + if [ '${{ matrix.config.arch }}' == 'armhf' ]; then + ../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir + else + ../appimagetool-x86_64.AppImage furnace.AppDir + fi mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }} popd diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake new file mode 100644 index 000000000..969be4da9 --- /dev/null +++ b/scripts/Cross-Linux-armhf.cmake @@ -0,0 +1,15 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(TARGET_PREFIX arm-linux-gnueabihf) + +set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++) +set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config) + +set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX}) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) From dc5fd54544e468630f8b22aff0a5050024b77fc2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 19:35:06 -0500 Subject: [PATCH 119/194] CI: Cross-Linux-armhf trial, part 2 --- .github/workflows/build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3132f1d4..042162533 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -139,17 +139,18 @@ jobs: sudo sed -ri "s/^deb /deb [arch=amd64] /" /etc/apt/sources.list echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" | sudo tee -a /etc/apt/sources.list echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" | sudo tee -a /etc/apt/sources.list - sudo apt update sudo dpkg --add-architecture armhf + sudo apt update sudo apt install \ crossbuild-essential-armhf \ + appstream + sudo apt install \ libsdl2-dev:armhf \ libfmt-dev:armhf \ librtmidi-dev:armhf \ libsndfile1-dev:armhf \ zlib1g-dev:armhf \ - libjack-jackd2-dev:armhf \ - appstream + libjack-jackd2-dev:armhf wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" chmod +x appimagetool-x86_64.AppImage From 6594fe41237099ef139a47bad6907737844891fe Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 19:39:07 -0500 Subject: [PATCH 120/194] CI: Cross-Linux-armhf trial, part 3 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 042162533..6994383d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -230,7 +230,7 @@ jobs: else CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') fi - elif [ '${{ runner.os }}' == 'Linux' && '${{ matrix.config.arch }}' == 'armhf' ]; then + elif [ '${{ runner.os }}' == 'Linux' ] && [ '${{ matrix.config.arch }}' == 'armhf' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-Linux-armhf.cmake') fi From 8f03763107c1c9ad1baf01099a9c1572c5574256 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 19:53:29 -0500 Subject: [PATCH 121/194] CI: Cross-Linux-armhf trial, part 4 --- scripts/Cross-Linux-armhf.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake index 969be4da9..0134be4af 100644 --- a/scripts/Cross-Linux-armhf.cmake +++ b/scripts/Cross-Linux-armhf.cmake @@ -12,4 +12,4 @@ set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) From 3c82e0abd4db40c7ff498e8518e86b40e192e7f1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 19:59:38 -0500 Subject: [PATCH 122/194] CI: Cross-Linux-armhf trial, part 5 --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6994383d0..3bccc91fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -150,10 +150,12 @@ jobs: librtmidi-dev:armhf \ libsndfile1-dev:armhf \ zlib1g-dev:armhf \ + cmake:armhf \ libjack-jackd2-dev:armhf wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" chmod +x appimagetool-x86_64.AppImage + ls /usr/arm-linux-gnueabihf/lib - name: Configure (System Libraries) if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} From 2c1390c0a11f75d01f126b7bbd748e9c474f97f5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 21:29:33 -0500 Subject: [PATCH 123/194] CI: Cross-Linux-armhf trial, part 6 --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bccc91fa..6d356bedb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -150,7 +150,6 @@ jobs: librtmidi-dev:armhf \ libsndfile1-dev:armhf \ zlib1g-dev:armhf \ - cmake:armhf \ libjack-jackd2-dev:armhf wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" From c4db8d514143f8aa13c9abdfe68209d0b7b52924 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 21:47:26 -0500 Subject: [PATCH 124/194] CI: Cross-Linux-armhf trial, part 7 --- scripts/Cross-Linux-armhf.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake index 0134be4af..fb494efc2 100644 --- a/scripts/Cross-Linux-armhf.cmake +++ b/scripts/Cross-Linux-armhf.cmake @@ -13,3 +13,5 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) + +set(CMAKE_SYSTEM_LIBRARY_PATH "/usr/lib/${TARGET_PREFIX};/usr/${TARGET_PREFIX}/lib") From 29d84f0affe7897a29f2f6b87cf8b262caf6efff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 22:01:14 -0500 Subject: [PATCH 125/194] CI: Cross-Linux-armhf trial, part 8 --- scripts/Cross-Linux-armhf.cmake | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake index fb494efc2..122e2c264 100644 --- a/scripts/Cross-Linux-armhf.cmake +++ b/scripts/Cross-Linux-armhf.cmake @@ -7,11 +7,9 @@ set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc) set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++) set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config) -set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX}) +set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX} /usr/lib/${TARGET_PREFIX}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) - -set(CMAKE_SYSTEM_LIBRARY_PATH "/usr/lib/${TARGET_PREFIX};/usr/${TARGET_PREFIX}/lib") From e2e0fd62a8610b73696f343901acaa88f05f4273 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 22:05:16 -0500 Subject: [PATCH 126/194] CI: Cross-Linux-armhf trial, part 9 --- scripts/Cross-Linux-armhf.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake index 122e2c264..3e490ae12 100644 --- a/scripts/Cross-Linux-armhf.cmake +++ b/scripts/Cross-Linux-armhf.cmake @@ -11,5 +11,5 @@ set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX} /usr/lib/${TARGET_PREFIX}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) From 42a0ee13b87f37028d5b5b44a4ba22b7546d0f16 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 22:26:18 -0500 Subject: [PATCH 127/194] add two demo songs by psdominator and LVintageNerd --- demos/Bullet_Hell.fur | Bin 0 -> 60233 bytes demos/home_wfl_opl3.fur | Bin 0 -> 6367 bytes src/gui/about.cpp | 2 ++ 3 files changed, 2 insertions(+) create mode 100644 demos/Bullet_Hell.fur create mode 100644 demos/home_wfl_opl3.fur diff --git a/demos/Bullet_Hell.fur b/demos/Bullet_Hell.fur new file mode 100644 index 0000000000000000000000000000000000000000..48c50f674d7d8a4c9cfbb2f632a1d1de602a617e GIT binary patch literal 60233 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

@Jkl=4Ns}LO9Yo9Q>HcR&Vv2yWz%NN& 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 VX2l>Ya^e5)ZZ<-~86)pg{s(PBp(X$T literal 0 HcmV?d00001 diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 00d899f81..e65413fce 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -80,10 +80,12 @@ const char* aboutLine[]={ "Laggy", "LovelyA72", "LunaMoth", + "LVintageNerd", "Mahbod Karamoozian", "Miker", "nicco1690", "NikonTeen", + "psdominator", "SuperJet Spade", "TheDuccinator", "theloredev", From 17dba66fa0ba5e7e49a4acea2092c38e87b71e54 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 22:52:39 -0500 Subject: [PATCH 128/194] MMC5: finally fix PCM linear pitch mode issues --- src/engine/platform/mmc5.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index bafbda3fb..2154e855f 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -171,7 +171,7 @@ void DivPlatformMMC5::tick(bool sysTick) { // PCM if (chan[2].freqChanged) { - chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false); + chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false,0,chan[2].pitch2,1,1); if (chan[2].furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { @@ -201,7 +201,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } dacPos=0; dacPeriod=0; - chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; @@ -282,7 +282,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_PERIODIC(c.value2); + int destFreq=(c.chan==2)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -315,7 +315,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + if (c.chan==2) { + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false); + } else { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + } chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; From 47ea8132b2a0e74719a76155b1cf5727ead4b683 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 6 Aug 2022 23:05:54 -0500 Subject: [PATCH 129/194] QSound: limit max frequency to $EFFF it appears $F000 and beyond cause glitches (#256) --- src/engine/platform/qsound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index faeaf6a9d..6bc88dbf0 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -358,7 +358,7 @@ void DivPlatformQSound::tick(bool sysTick) { } } chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0); - if (chan[i].freq>0xffff) chan[i].freq=0xffff; + if (chan[i].freq>0xefff) chan[i].freq=0xefff; if (chan[i].keyOn) { rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); rWrite(q1_reg_map[Q1V_END][i], qsound_end); From 9f8c96d45bea71aac19b4ded40e616d0f60039a3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 00:03:27 -0500 Subject: [PATCH 130/194] dev105 - prepare for Game Boy hardware sequences issue #27 --- papers/format.md | 33 +++++++++++++++++++++++++++++++++ src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 16 ++++++++++++++++ src/engine/instrument.h | 11 +++++++++-- src/engine/platform/gb.h | 7 ++++++- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/papers/format.md b/papers/format.md index 97e22a4e5..263955022 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 105: Furance dev105 +- 104: Furnace dev104 - 103: Furnace dev103 - 102: Furnace 0.6pre1 (dev102) - 101: Furnace 0.6pre1 (dev101) @@ -813,6 +815,37 @@ size | description --- | **Sound Unit data** (>=104) 1 | use sample 1 | switch roles of phase reset timer and frequency + --- | **Game Boy envelope sequence** (>=105) + 1 | length + ??? | hardware sequence data + | size is length*3: + | 1 byte: command + | - 0: set envelope + | - 1: set sweep + | - 2: wait + | - 3: wait for release + | - 4: loop + | - 5: loop until release + | 2 bytes: data + | - for set envelope: + | - 1 byte: parameter + | - bit 4-7: volume + | - bit 3: direction + | - bit 0-2: length + | - 1 byte: sound length + | - for set sweep: + | - 1 byte: parameter + | - bit 4-6: length + | - bit 3: direction + | - bit 0-2: shift + | - 1 byte: nothing + | - for wait: + | - 1 byte: length (in ticks) + | - 1 byte: nothing + | - for wait for release: + | - 2 bytes: nothing + | - for loop/loop until release: + | - 2 bytes: position ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 256b0c96d..f54224233 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 "dev104" -#define DIV_ENGINE_VERSION 104 +#define DIV_VERSION "dev105" +#define DIV_ENGINE_VERSION 105 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 4d274e18c..37c6a9b2a 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -532,6 +532,13 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(su.useSample); w->writeC(su.switchRoles); + // GB hardware sequence + w->writeC(gb.hwSeqLen); + for (int i=0; gb.hwSeqLen; i++) { + w->writeC(gb.hwSeq[i].cmd); + w->writeS(gb.hwSeq[i].data); + } + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); @@ -1085,6 +1092,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { su.switchRoles=reader.readC(); } + // GB hardware sequence + if (version>=105) { + gb.hwSeqLen=reader.readC(); + for (int i=0; i Date: Sun, 7 Aug 2022 00:22:03 -0500 Subject: [PATCH 131/194] Game Boy: make channel state independent of instru --- src/engine/platform/gb.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 4067e9f17..a239ea15b 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -187,9 +187,8 @@ void DivPlatformGB::tick(bool sysTick) { } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); if (i!=2) { - rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); } else { if (parent->song.waveDutyIsVol) { rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); @@ -241,7 +240,6 @@ void DivPlatformGB::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); if (i==3) { // noise int ntPos=chan[i].baseFreq; if (ntPos<0) ntPos=0; @@ -257,8 +255,8 @@ void DivPlatformGB::tick(bool sysTick) { rWrite(16+i*5,0x80); rWrite(16+i*5+2,gbVolMap[chan[i].vol]); } else { - rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); - rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); + rWrite(16+i*5+2,((chan[i].vol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); } } if (chan[i].keyOff) { @@ -270,10 +268,10 @@ void DivPlatformGB::tick(bool sysTick) { } if (i==3) { // noise rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0)); - rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<64)<<6)); + rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<64)<<6)); } else { rWrite(16+i*5+3,(2048-chan[i].freq)&0xff); - rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<63)<<6)); + rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6)); } if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; @@ -309,6 +307,11 @@ int DivPlatformGB::dispatch(DivCommand c) { ws.changeWave1(chan[c.chan].wave); } ws.init(ins,32,15,chan[c.chan].insChanged); + } else if (chan[c.chan].insChanged) { + chan[c.chan].envVol=ins->gb.envVol; + chan[c.chan].envLen=ins->gb.envLen; + chan[c.chan].envDir=ins->gb.envDir; + chan[c.chan].soundLen=ins->gb.soundLen; } chan[c.chan].insChanged=false; break; @@ -328,9 +331,13 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].insChanged=true; if (c.chan!=2) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); - chan[c.chan].vol=ins->gb.envVol; + chan[c.chan].envVol=ins->gb.envVol; + chan[c.chan].envLen=ins->gb.envLen; + chan[c.chan].envDir=ins->gb.envDir; + chan[c.chan].soundLen=ins->gb.soundLen; + chan[c.chan].vol=chan[c.chan].envVol; if (parent->song.gbInsAffectsEnvelope) { - rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); } } } From 7dad9098b60e69a2fc195b28b8813c2adc7ad6d5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 00:37:35 -0500 Subject: [PATCH 132/194] Game Boy: fix wave channel --- src/engine/platform/gb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index a239ea15b..2ec9bfdf9 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -307,7 +307,8 @@ int DivPlatformGB::dispatch(DivCommand c) { ws.changeWave1(chan[c.chan].wave); } ws.init(ins,32,15,chan[c.chan].insChanged); - } else if (chan[c.chan].insChanged) { + } + if (chan[c.chan].insChanged) { chan[c.chan].envVol=ins->gb.envVol; chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envDir=ins->gb.envDir; From 1721e1d03e5a164e2a688e8213ee50d4e398166d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 00:40:26 -0500 Subject: [PATCH 133/194] Game Boy: re-enable wave corruption bug emulation --- src/engine/platform/sound/gb/apu.c | 8 ++++---- src/engine/platform/sound/gb/gb.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/sound/gb/apu.c b/src/engine/platform/sound/gb/apu.c index 4c473997f..8836ddd29 100644 --- a/src/engine/platform/sound/gb/apu.c +++ b/src/engine/platform/sound/gb/apu.c @@ -1180,11 +1180,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((value & 0x80)) { /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU reads from it. */ - /*if (!CGB && + if (!CGB && gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.enable) { - unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;*/ + unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; /* This glitch varies between models and even specific instances: DMG-B: Most of them behave as emulated. A few behave differently. @@ -1193,7 +1193,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) Additionally, I believe DMGs, including those we behave differently than emulated, are all deterministic. */ - /*if (offset < 4) { + if (offset < 4) { gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2]; gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1]; @@ -1206,7 +1206,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.wave_form + (offset & ~3) * 2, 8); } - }*/ + } if (!gb->apu.is_active[GB_WAVE]) { gb->apu.is_active[GB_WAVE] = true; update_sample(gb, GB_WAVE, diff --git a/src/engine/platform/sound/gb/gb.h b/src/engine/platform/sound/gb/gb.h index ac817395f..ca3650852 100644 --- a/src/engine/platform/sound/gb/gb.h +++ b/src/engine/platform/sound/gb/gb.h @@ -16,7 +16,7 @@ extern "C" { #define GB_STRUCT_VERSION 13 -#define CGB 0 +#define CGB (gb->model&GB_MODEL_CGB_FAMILY) #define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_DMG_FAMILY 0x000 From 45196daf95ef5feeb5fec4116664f099243dc086 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 01:32:28 -0500 Subject: [PATCH 134/194] Game Boy: fix serious typo --- src/engine/instrument.cpp | 2 +- src/engine/instrument.h | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 37c6a9b2a..b1afc3953 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -534,7 +534,7 @@ void DivInstrument::putInsData(SafeWriter* w) { // GB hardware sequence w->writeC(gb.hwSeqLen); - for (int i=0; gb.hwSeqLen; i++) { + for (int i=0; iwriteC(gb.hwSeq[i].cmd); w->writeS(gb.hwSeq[i].data); } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 33e2856b2..eddf9f79e 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -262,6 +262,16 @@ struct DivInstrumentSTD { struct DivInstrumentGB { unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; + enum HWSeqCommands: unsigned char { + DIV_GB_HWCMD_ENVELOPE=0, + DIV_GB_HWCMD_SWEEP, + DIV_GB_HWCMD_WAIT, + DIV_GB_HWCMD_WAIT_REL, + DIV_GB_HWCMD_LOOP, + DIV_GB_HWCMD_LOOP_REL, + + DIV_GB_HWCMD_MAX + }; struct HWSeqCommand { unsigned char cmd; unsigned short data; From 800f08b0fdaac548091d3ef5745e915f1dc24d0a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 06:06:14 -0500 Subject: [PATCH 135/194] Game Boy: hardware sequences, part 1 still not working! just the UI for it --- src/gui/insEdit.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8e405251e..e0c18b6c1 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -284,6 +284,15 @@ const char* dualWSEffects[9]={ "Phase Modulation" }; +const char* gbHWSeqCmdTypes[6]={ + "Envelope", + "Sweep", + "Wait", + "Wait for Release", + "Loop", + "Loop until Release" +}; + const char* macroAbsoluteMode="Fixed"; const char* macroRelativeMode="Relative"; const char* macroQSoundMode="QSound"; @@ -2974,6 +2983,152 @@ void FurnaceGUI::drawInsEdit() { } drawGBEnv(ins->gb.envVol,ins->gb.envLen,ins->gb.soundLen,ins->gb.envDir,ImVec2(ImGui::GetContentRegionAvail().x,100.0f*dpiScale)); + + if (ImGui::BeginChild("HWSeq",ImGui::GetContentRegionAvail(),true,ImGuiWindowFlags_MenuBar)) { + ImGui::BeginMenuBar(); + ImGui::Text("Hardware Sequence"); + ImGui::EndMenuBar(); + + if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + int curFrame=0; + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Tick"); + ImGui::TableNextColumn(); + ImGui::Text("Command"); + for (int i=0; igb.hwSeqLen; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d (#%d)",curFrame,i); + ImGui::TableNextColumn(); + ImGui::PushID(i); + if (ins->gb.hwSeq[i].cmd>=DivInstrumentGB::DIV_GB_HWCMD_MAX) { + ins->gb.hwSeq[i].cmd=0; + } + int cmd=ins->gb.hwSeq[i].cmd; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##HWSeqCmd",&cmd,gbHWSeqCmdTypes,DivInstrumentGB::DIV_GB_HWCMD_MAX)) { + if (ins->gb.hwSeq[i].cmd!=cmd) { + ins->gb.hwSeq[i].cmd=cmd; + ins->gb.hwSeq[i].data=0; + } + } + bool somethingChanged=false; + switch (ins->gb.hwSeq[i].cmd) { + case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: { + int hwsVol=(ins->gb.hwSeq[i].data&0xf0)>>4; + bool hwsDir=ins->gb.hwSeq[i].data&8; + int hwsLen=ins->gb.hwSeq[i].data&7; + int hwsSoundLen=ins->gb.hwSeq[i].data>>8; + + if (CWSliderInt("Volume",&hwsVol,0,15)) { + somethingChanged=true; + } + if (CWSliderInt("Env Length",&hwsLen,0,7)) { + somethingChanged=true; + } + if (CWSliderInt("Sound Length",&hwsSoundLen,0,64,hwsSoundLen>63?"Infinity":"%d")) { + somethingChanged=true; + } + if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER + hwsDir=true; + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER + hwsDir=false; + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=(hwsLen&7)|(hwsDir?8:0)|(hwsVol<<4)|(hwsSoundLen<<8); + PARAMETER; + } + break; + } + case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: { + int hwsShift=ins->gb.hwSeq[i].data&7; + int hwsSpeed=(ins->gb.hwSeq[i].data&0x70)>>4; + bool hwsDir=ins->gb.hwSeq[i].data&8; + + if (CWSliderInt("Shift",&hwsShift,0,7)) { + somethingChanged=true; + } + if (CWSliderInt("Speed",&hwsSpeed,0,7)) { + somethingChanged=true; + } + + if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER + hwsDir=true; + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER + hwsDir=false; + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=(hwsShift&7)|(hwsDir?8:0)|(hwsSpeed<<4); + PARAMETER; + } + break; + } + case DivInstrumentGB::DIV_GB_HWCMD_WAIT: { + int len=ins->gb.hwSeq[i].data+1; + curFrame+=ins->gb.hwSeq[i].data+1; + + if (ImGui::InputInt("Ticks",&len)) { + if (len<1) len=1; + if (len>255) len=256; + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=len-1; + PARAMETER; + } + break; + } + case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL: + curFrame++; + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP: + case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL: { + int pos=ins->gb.hwSeq[i].data; + + if (ImGui::InputInt("Position",&pos)) { + if (pos<0) pos=0; + if (pos>(ins->gb.hwSeqLen-1)) pos=(ins->gb.hwSeqLen-1); + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=pos; + PARAMETER; + } + break; + } + default: + break; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + + if (ImGui::Button(ICON_FA_PLUS "##HWCmdAdd")) { + if (ins->gb.hwSeqLen<255) { + ins->gb.hwSeq[ins->gb.hwSeqLen].cmd=0; + ins->gb.hwSeq[ins->gb.hwSeqLen].data=0; + ins->gb.hwSeqLen++; + } + } + + ImGui::EndChild(); + } ImGui::EndTabItem(); } if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { From 829db187df2a70c13a700dc3eb98e3a222c82435 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 06:24:48 -0500 Subject: [PATCH 136/194] Y8950: fix ADPCM per-chan osc I think --- src/engine/platform/opl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 36dd2c66b..a32777c33 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -293,7 +293,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (!isMuted[adpcmChan]) { os[0]-=aOut.data[0]>>3; os[1]-=aOut.data[0]>>3; - oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0]; } else { oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0; } From f80488d9b0905fe90b12aa2f18b45f0f959ed173 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 17:32:39 -0500 Subject: [PATCH 137/194] minimize allocations in nextBuf --- src/engine/engine.h | 6 +++++- src/engine/playback.cpp | 39 ++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index f54224233..001ea1941 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -164,7 +164,7 @@ struct DivNoteEvent { struct DivDispatchContainer { DivDispatch* dispatch; blip_buffer_t* bb[2]; - size_t bbInLen; + size_t bbInLen, runtotal, runLeft, runPos, lastAvail; int temp[2], prevSample[2]; short* bbIn[2]; short* bbOut[2]; @@ -182,6 +182,10 @@ struct DivDispatchContainer { dispatch(NULL), bb{NULL,NULL}, bbInLen(0), + runtotal(0), + runLeft(0), + runPos(0), + lastAvail(0), temp{0,0}, prevSample{0,0}, bbIn{NULL,NULL}, diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ee9115fd8..00ae942ee 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1308,25 +1308,22 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } // logic starts here - size_t runtotal[32]; - size_t runLeft[32]; - size_t runPos[32]; - size_t lastAvail[32]; for (int i=0; i0) { - disCont[i].flush(lastAvail[i]); + disCont[i].lastAvail=blip_samples_avail(disCont[i].bb[0]); + if (disCont[i].lastAvail>0) { + disCont[i].flush(disCont[i].lastAvail); } - runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]); - if (runtotal[i]>disCont[i].bbInLen) { + disCont[i].runtotal=blip_clocks_needed(disCont[i].bb[0],size-disCont[i].lastAvail); + if (disCont[i].runtotal>disCont[i].bbInLen) { + logV("growing dispatch %d bbIn to %d",i,disCont[i].runtotal+256); delete[] disCont[i].bbIn[0]; delete[] disCont[i].bbIn[1]; - disCont[i].bbIn[0]=new short[runtotal[i]+256]; - disCont[i].bbIn[1]=new short[runtotal[i]+256]; - disCont[i].bbInLen=runtotal[i]+256; + disCont[i].bbIn[0]=new short[disCont[i].runtotal+256]; + disCont[i].bbIn[1]=new short[disCont[i].runtotal+256]; + disCont[i].bbInLen=disCont[i].runtotal+256; } - runLeft[i]=runtotal[i]; - runPos[i]=0; + disCont[i].runLeft=disCont[i].runtotal; + disCont[i].runPos=0; } if (metroTickLen>MASTER_CLOCK_PREC); for (int i=0; i Date: Sun, 7 Aug 2022 17:37:07 -0500 Subject: [PATCH 138/194] fix possible crash when closing Furnace --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index fd94e521a..bf55d9d6e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3339,6 +3339,7 @@ bool DivEngine::initAudioBackend() { bool DivEngine::deinitAudioBackend() { if (output!=NULL) { + output->quit(); if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { logI("closing MIDI input."); @@ -3352,7 +3353,6 @@ bool DivEngine::deinitAudioBackend() { } } output->quitMidi(); - output->quit(); delete output; output=NULL; //audioEngine=DIV_AUDIO_NULL; From 1c92d23d27965a230de25328a373ce811a312f33 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Aug 2022 17:40:01 -0500 Subject: [PATCH 139/194] commands view now only displays useful commands --- src/engine/playback.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 00ae942ee..3ba4f1417 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -211,7 +211,27 @@ const char* formatNote(unsigned char note, unsigned char octave) { int DivEngine::dispatchCmd(DivCommand c) { if (view==DIV_STATUS_COMMANDS) { - if (!skipping) printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2); + if (!skipping) { + switch (c.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + case DIV_CMD_PRE_NOTE: + break; + default: + printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2); + } + } } totalCmds++; if (cmdStreamEnabled && cmdStream.size()<2000) { From 2af4992e9b785d4944c766317ea077b5533af027 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 8 Aug 2022 00:25:05 -0500 Subject: [PATCH 140/194] JACK: fix crash when changing buffer size --- src/audio/jack.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/audio/jack.cpp b/src/audio/jack.cpp index 6e7b7bf1f..f9502cf4f 100644 --- a/src/audio/jack.cpp +++ b/src/audio/jack.cpp @@ -52,17 +52,30 @@ void TAAudioJACK::onBufferSize(jack_nframes_t bufsize) { } void TAAudioJACK::onProcess(jack_nframes_t nframes) { - if (audioProcCallback!=NULL) { - if (midiIn!=NULL) midiIn->gather(); - audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); - } for (int i=0; idesc.bufsize) { + delete[] inBufs[i]; + inBufs[i]=new float[nframes]; + } + memcpy(iInBufs[i],inBufs[i],nframes*sizeof(float)); + } + for (int i=0; idesc.bufsize) { + delete[] outBufs[i]; + outBufs[i]=new float[nframes]; + } + } + if (audioProcCallback!=NULL) { + if (midiIn!=NULL) midiIn->gather(); + audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,nframes); } for (int i=0; i Date: Tue, 9 Aug 2022 14:53:31 -0500 Subject: [PATCH 141/194] i guess to anybody who bothers reading the contents of this commit: who cares? you promised me C163 would become the name but nobody has bothered to call it C163 for an entire week. there's no point on pushing a dead idea forward! --- papers/doc/7-systems/README.md | 2 +- papers/doc/7-systems/n163.md | 22 +--------------------- src/engine/platform/gb.h | 5 ++++- src/gui/about.cpp | 2 +- src/gui/guiConst.cpp | 2 +- src/gui/presets.cpp | 4 ++-- src/gui/settings.cpp | 3 +-- 7 files changed, 11 insertions(+), 29 deletions(-) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index e4b6cf5b9..c4dc6d493 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -26,7 +26,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) -- [Namco C163](n163.md) +- [Namco 163](n163.md) - [Namco WSG](namco.md) - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) diff --git a/papers/doc/7-systems/n163.md b/papers/doc/7-systems/n163.md index d3ece830f..3c2f389f9 100644 --- a/papers/doc/7-systems/n163.md +++ b/papers/doc/7-systems/n163.md @@ -1,24 +1,4 @@ -# - ANNOUNCEMENT - - -Start calling it C163! The TRUE name of the chip! - -The line will be consistent with your help: -- Namco C15 -- Namco C30 -- Namco C140 -- **Namco C163** -- Namco C219 -- Namco C352 - -The C names are official as indicated by: - -- MAME -- VGMPlay -- Korg × Bandai Namco (from Kamata info page) - -C stands for Custom! Call it C163! - -# Namco 163 (also called Namco C163, 106, 160 or 129) +# Namco 163 (also called N163, Namco C163, Namco 106 (sic), Namco 160 or Namco 129) This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 128 byte of internal RAM, and both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. But waveform RAM area becomes smaller as more channels are activated; as channel registers consumes 8 bytes for each channel. You must avoid conflict with channel register area and waveform for avoid broken channel playback. diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index d4f6ca536..58cce8805 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -32,6 +32,7 @@ class DivPlatformGB: public DivDispatch { bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; signed char vol, outVol, wave; unsigned char envVol, envDir, envLen, soundLen; + unsigned short hwSeqPos, hwSeqDelay; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -59,7 +60,9 @@ class DivPlatformGB: public DivDispatch { envVol(0), envDir(0), envLen(0), - soundLen(0) {} + soundLen(0), + hwSeqPos(0), + hwSeqDelay(0) {} }; Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; diff --git a/src/gui/about.cpp b/src/gui/about.cpp index e65413fce..587aba84d 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -135,7 +135,7 @@ const char* aboutLine[]={ "VICE VIC-20 sound core by Rami Rasanen and viznut", "VERA sound core by Frank van den Hoef", "K005289 emulator by cam900", - "Namco C163 emulator by cam900", + "Namco 163 emulator by cam900", "Seta X1-010 emulator by cam900", "Konami VRC6 emulator by cam900", "Konami SCC emulator by cam900", diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 6c0bb891b..59c917ae0 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -97,7 +97,7 @@ const char* insTypes[DIV_INS_MAX+1]={ "FM (OPL)", "FDS", "Virtual Boy", - "Namco C163", + "Namco 163", "Konami SCC/Bubble System WSG", "FM (OPZ)", "POKEY", diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index f8e7a522c..571cda441 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -409,7 +409,7 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Namco C163", { + "Namco 163", { DIV_SYSTEM_N163, 64, 0, 0, 0 } @@ -617,7 +617,7 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Famicom with Namco C163", { + "Famicom with Namco 163", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_N163, 64, 0, 112, 0 diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 2ad3ef23c..73e9d2333 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1201,7 +1201,7 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); - ImGui::Text("N163/C163 chip name"); + ImGui::Text("Namco 163 chip name"); ImGui::SameLine(); ImGui::InputTextWithHint("##C163Name",DIV_C163_DEFAULT_NAME,&settings.c163Name); @@ -2028,7 +2028,6 @@ void FurnaceGUI::syncSettings() { settings.audioDevice=e->getConfString("audioDevice",""); settings.midiInDevice=e->getConfString("midiInDevice",""); settings.midiOutDevice=e->getConfString("midiOutDevice",""); - // I'm sorry, but the C163 education program has failed... settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME); settings.audioQuality=e->getConfInt("audioQuality",0); settings.audioBufSize=e->getConfInt("audioBufSize",1024); From 28698beaf3cad3abb11c77c84da28acf7354921d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 01:55:44 -0500 Subject: [PATCH 142/194] dev106 - Game Boy: implement hw seq and prepare for software envelope maybe --- TODO.md | 1 - papers/format.md | 6 +++- src/engine/engine.h | 4 +-- src/engine/instrument.cpp | 10 +++++++ src/engine/instrument.h | 5 +++- src/engine/platform/gb.cpp | 57 +++++++++++++++++++++++++++++++++++++- src/engine/platform/gb.h | 6 ++-- src/gui/insEdit.cpp | 17 ++++++++---- 8 files changed, 93 insertions(+), 13 deletions(-) diff --git a/TODO.md b/TODO.md index 46773705a..13f31300e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ # to-do for 0.6pre1.5-0.6pre2 -- Game Boy envelope macro/sequence - volume commands should work on Game Boy - ability to customize `OFF`, `===` and `REL` - stereo separation control for AY diff --git a/papers/format.md b/papers/format.md index 263955022..1a8cd7219 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,7 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: -- 105: Furance dev105 +- 106: Furnace dev106 +- 105: Furnace dev105 - 104: Furnace dev104 - 103: Furnace dev103 - 102: Furnace 0.6pre1 (dev102) @@ -846,6 +847,9 @@ size | description | - 2 bytes: nothing | - for loop/loop until release: | - 2 bytes: position + --- | **Game Boy extra flags** (>=106) + 1 | use software envelope + 1 | always init hard env on new note ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 001ea1941..483ebcf0d 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 "dev105" -#define DIV_ENGINE_VERSION 105 +#define DIV_VERSION "dev106" +#define DIV_ENGINE_VERSION 106 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index b1afc3953..555d7d17f 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -539,6 +539,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeS(gb.hwSeq[i].data); } + // GB additional flags + w->writeC(gb.softEnv); + w->writeC(gb.alwaysInit); + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); @@ -1101,6 +1105,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } } + // GB additional flags + if (version>=106) { + gb.softEnv=reader.readC(); + gb.alwaysInit=reader.readC(); + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index eddf9f79e..df7a6b361 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -262,6 +262,7 @@ struct DivInstrumentSTD { struct DivInstrumentGB { unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; + bool softEnv, alwaysInit; enum HWSeqCommands: unsigned char { DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_SWEEP, @@ -281,7 +282,9 @@ struct DivInstrumentGB { envDir(0), envLen(2), soundLen(64), - hwSeqLen(0) { + hwSeqLen(0), + softEnv(false), + alwaysInit(false) { memset(hwSeq,0,256*sizeof(int)); } }; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 2ec9bfdf9..5b7393ef3 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -233,12 +233,61 @@ void DivPlatformGB::tick(bool sysTick) { } } } + // run hardware sequence + if (chan[i].active) { + if (--chan[i].hwSeqDelay<=0) { + chan[i].hwSeqDelay=0; + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); + int hwSeqCount=0; + while (chan[i].hwSeqPosgb.hwSeqLen && hwSeqCount<4) { + bool leave=false; + unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data; + switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) { + case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: + chan[i].envLen=data&7; + chan[i].envDir=(data&8)?1:0; + chan[i].envVol=(data>>4)&15; + chan[i].soundLen=data>>8; + chan[i].keyOn=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: + chan[i].sweep=data; + chan[i].sweepChanged=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_WAIT: + chan[i].hwSeqDelay=data+1; + leave=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL: + if (!chan[i].released) { + chan[i].hwSeqPos--; + leave=true; + } + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP: + chan[i].hwSeqPos=data-1; + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL: + if (!chan[i].released) { + chan[i].hwSeqPos=data-1; + } + break; + } + + chan[i].hwSeqPos++; + if (leave) break; + hwSeqCount++; + } + } + } + if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { rWrite(16+i*5,chan[i].sweep); } } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==3) { // noise int ntPos=chan[i].baseFreq; @@ -300,6 +349,9 @@ int DivPlatformGB::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; + chan[c.chan].hwSeqPos=0; + chan[c.chan].hwSeqDelay=0; + chan[c.chan].released=false; chan[c.chan].macroInit(ins); if (c.chan==2) { if (chan[c.chan].wave<0) { @@ -308,7 +360,7 @@ int DivPlatformGB::dispatch(DivCommand c) { } ws.init(ins,32,15,chan[c.chan].insChanged); } - if (chan[c.chan].insChanged) { + if (chan[c.chan].insChanged || ins->gb.alwaysInit) { chan[c.chan].envVol=ins->gb.envVol; chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envDir=ins->gb.envDir; @@ -320,11 +372,14 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; + chan[c.chan].hwSeqPos=0; + chan[c.chan].hwSeqDelay=0; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: chan[c.chan].std.release(); + chan[c.chan].released=true; break; case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 58cce8805..a4432f1e7 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -29,10 +29,11 @@ class DivPlatformGB: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; unsigned char duty, sweep; - bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released; signed char vol, outVol, wave; unsigned char envVol, envDir, envLen, soundLen; - unsigned short hwSeqPos, hwSeqDelay; + unsigned short hwSeqPos; + short hwSeqDelay; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -54,6 +55,7 @@ class DivPlatformGB: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + released(false), vol(15), outVol(15), wave(-1), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e0c18b6c1..ce9406ba1 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2965,6 +2965,10 @@ void FurnaceGUI::drawInsEdit() { } } if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { + P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv)); + P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit)); + + ImGui::BeginDisabled(ins->gb.softEnv); P(CWSliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable P(CWSliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable @@ -3060,13 +3064,13 @@ void FurnaceGUI::drawInsEdit() { somethingChanged=true; } - if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER - hwsDir=true; + if (ImGui::RadioButton("Up",!hwsDir)) { PARAMETER + hwsDir=false; somethingChanged=true; } ImGui::SameLine(); - if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER - hwsDir=false; + if (ImGui::RadioButton("Down",hwsDir)) { PARAMETER + hwsDir=true; somethingChanged=true; } @@ -3128,6 +3132,7 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndChild(); + ImGui::EndDisabled(); } ImGui::EndTabItem(); } @@ -3689,8 +3694,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { volMax=127; } - if (ins->type==DIV_INS_GB) { + if (ins->type==DIV_INS_GB && !ins->gb.softEnv) { volMax=0; + } else { + volMax=15; } if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { volMax=1; From df10b6cc5952926e4a1c81dcd87fc58d0b074392 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 14:16:26 -0500 Subject: [PATCH 143/194] Game Boy: hardware sequences, part 3 the previous commit was part 2 --- src/gui/insEdit.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index ce9406ba1..0d90e3733 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2993,15 +2993,18 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Hardware Sequence"); ImGui::EndMenuBar(); - if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",2)) { + if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); int curFrame=0; ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::Text("Tick"); ImGui::TableNextColumn(); ImGui::Text("Command"); + ImGui::TableNextColumn(); + ImGui::Text("Move/Remove"); for (int i=0; igb.hwSeqLen; i++) { ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -3119,6 +3122,46 @@ void FurnaceGUI::drawInsEdit() { break; } ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID(i+512); + if (ImGui::Button(ICON_FA_CHEVRON_UP "##HWCmdUp")) { + if (i>0) { + e->lockEngine([ins,i]() { + ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd; + ins->gb.hwSeq[i].cmd^=ins->gb.hwSeq[i-1].cmd; + ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd; + + ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data; + ins->gb.hwSeq[i].data^=ins->gb.hwSeq[i-1].data; + ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data; + }); + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CHEVRON_DOWN "##HWCmdDown")) { + if (igb.hwSeqLen-1) { + e->lockEngine([ins,i]() { + ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd; + ins->gb.hwSeq[i].cmd^=ins->gb.hwSeq[i-1].cmd; + ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd; + + ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data; + ins->gb.hwSeq[i].data^=ins->gb.hwSeq[i-1].data; + ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data; + }); + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##HWCmdDel")) { + for (int j=i; jgb.hwSeqLen-1; j++) { + ins->gb.hwSeq[j].cmd=ins->gb.hwSeq[j+1].cmd; + ins->gb.hwSeq[j].data=ins->gb.hwSeq[j+1].data; + } + ins->gb.hwSeqLen--; + } + ImGui::PopID(); } ImGui::EndTable(); } From 6bcb3063a59f40df4cfbe3d350c67d57b016ad71 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 15:41:52 -0500 Subject: [PATCH 144/194] add OPZ disclaimer in docs --- papers/doc/7-systems/opz.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/papers/doc/7-systems/opz.md b/papers/doc/7-systems/opz.md index 61e931394..c7952a55b 100644 --- a/papers/doc/7-systems/opz.md +++ b/papers/doc/7-systems/opz.md @@ -1,5 +1,7 @@ # Yamaha OPZ (YM2414) +**disclaimer: despite the name, this has nothing to do with teenage engineering's OP-Z synth!** + this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too. it adds these features on top of the YM2151: From bccecc4c07ba59eee23507590ba627e720795588 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 16:27:29 -0500 Subject: [PATCH 145/194] Game Boy: software envelopes, part 1 --- src/engine/fileOps.cpp | 5 +++-- src/engine/platform/gb.cpp | 41 ++++++++++++++++++++++++++------------ src/engine/platform/gb.h | 3 ++- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8dcd55d22..db2305c65 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -590,7 +590,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); } else if (ds.system[0]==DIV_SYSTEM_GB) { - // try to convert macro to envelope + // set software envelope flag + ins->gb.softEnv=true; + // try to convert macro to envelope in case the user decides to switch to them if (ins->std.volMacro.len>0) { ins->gb.envVol=ins->std.volMacro.val[0]; if (ins->std.volMacro.val[0]std.volMacro.val[1]) { @@ -600,7 +602,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->gb.soundLen=ins->std.volMacro.len*2; } } - addWarning("Game Boy volume macros converted to envelopes. may not be perfect!"); } } diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 5b7393ef3..aa95d6cbd 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -160,6 +160,16 @@ void DivPlatformGB::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); + if (chan[i].softEnv) { + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); + if (chan[i].outVol<0) chan[i].outVol=0; + + // temporary until zombie mode is implemented + chan[i].vol=chan[i].outVol; + chan[i].keyOn=true; + } + } if (chan[i].std.arp.had) { if (i==3) { // noise if (chan[i].std.arp.mode) { @@ -189,7 +199,7 @@ void DivPlatformGB::tick(bool sysTick) { chan[i].duty=chan[i].std.duty.val; if (i!=2) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); - } else { + } else if (!chan[i].softEnv) { if (parent->song.waveDutyIsVol) { rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); } @@ -244,11 +254,13 @@ void DivPlatformGB::tick(bool sysTick) { unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data; switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) { case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: - chan[i].envLen=data&7; - chan[i].envDir=(data&8)?1:0; - chan[i].envVol=(data>>4)&15; - chan[i].soundLen=data>>8; - chan[i].keyOn=true; + if (!chan[i].softEnv) { + chan[i].envLen=data&7; + chan[i].envDir=(data&8)?1:0; + chan[i].envVol=(data>>4)&15; + chan[i].soundLen=data>>8; + chan[i].keyOn=true; + } break; case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: chan[i].sweep=data; @@ -352,6 +364,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].hwSeqPos=0; chan[c.chan].hwSeqDelay=0; chan[c.chan].released=false; + chan[c.chan].softEnv=ins->gb.softEnv; chan[c.chan].macroInit(ins); if (c.chan==2) { if (chan[c.chan].wave<0) { @@ -387,13 +400,15 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].insChanged=true; if (c.chan!=2) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); - chan[c.chan].envVol=ins->gb.envVol; - chan[c.chan].envLen=ins->gb.envLen; - chan[c.chan].envDir=ins->gb.envDir; - chan[c.chan].soundLen=ins->gb.soundLen; - chan[c.chan].vol=chan[c.chan].envVol; - if (parent->song.gbInsAffectsEnvelope) { - rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); + if (!ins->gb.softEnv) { + chan[c.chan].envVol=ins->gb.envVol; + chan[c.chan].envLen=ins->gb.envLen; + chan[c.chan].envDir=ins->gb.envDir; + chan[c.chan].soundLen=ins->gb.soundLen; + chan[c.chan].vol=chan[c.chan].envVol; + if (parent->song.gbInsAffectsEnvelope) { + rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); + } } } } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index a4432f1e7..082f33ba9 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -29,7 +29,7 @@ class DivPlatformGB: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; unsigned char duty, sweep; - bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv; signed char vol, outVol, wave; unsigned char envVol, envDir, envLen, soundLen; unsigned short hwSeqPos; @@ -56,6 +56,7 @@ class DivPlatformGB: public DivDispatch { keyOff(false), inPorta(false), released(false), + softEnv(false), vol(15), outVol(15), wave(-1), From 4b18d0920bbde8e0887beab27c302a24026bcfd0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 17:02:45 -0500 Subject: [PATCH 146/194] Game Boy: software envelopes, part 2 --- src/engine/platform/gb.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index aa95d6cbd..875304023 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -166,7 +166,10 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].outVol<0) chan[i].outVol=0; // temporary until zombie mode is implemented - chan[i].vol=chan[i].outVol; + chan[i].envLen=0; + chan[i].envDir=0; + chan[i].envVol=chan[i].outVol; + chan[i].soundLen=64; chan[i].keyOn=true; } } @@ -314,10 +317,10 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].keyOn) { if (i==2) { // wave rWrite(16+i*5,0x80); - rWrite(16+i*5+2,gbVolMap[chan[i].vol]); + rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); } else { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); - rWrite(16+i*5+2,((chan[i].vol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); + rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); } } if (chan[i].keyOff) { @@ -406,6 +409,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].envDir=ins->gb.envDir; chan[c.chan].soundLen=ins->gb.soundLen; chan[c.chan].vol=chan[c.chan].envVol; + chan[c.chan].outVol=chan[c.chan].vol; if (parent->song.gbInsAffectsEnvelope) { rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); } @@ -415,8 +419,9 @@ int DivPlatformGB::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: chan[c.chan].vol=c.value; + chan[c.chan].outVol=c.value; if (c.chan==2) { - rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].vol]); + rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]); } break; case DIV_CMD_GET_VOLUME: From 51db06298bfcdbcb4e197407e00d6510e2585d43 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 23:53:47 -0500 Subject: [PATCH 147/194] Game Boy: fix volume regression --- src/engine/platform/gb.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 875304023..abbb19336 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -423,6 +423,9 @@ int DivPlatformGB::dispatch(DivCommand c) { if (c.chan==2) { rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]); } + if (!chan[c.chan].softEnv) { + chan[c.chan].envVol=chan[c.chan].vol; + } break; case DIV_CMD_GET_VOLUME: return chan[c.chan].vol; From 92f40774e4c87675a659c931cb74001bafc9ef01 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 10 Aug 2022 23:56:25 -0500 Subject: [PATCH 148/194] Game Boy: I hate your artificial limitations fixes a DefleMask demo module --- src/engine/platform/gb.cpp | 4 ++++ src/engine/platform/gb.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index abbb19336..31ce24240 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -573,6 +573,10 @@ void DivPlatformGB::reset() { antiClickWavePos=0; } +int DivPlatformGB::getPortaFloor(int ch) { + return 24; +} + bool DivPlatformGB::isStereo() { return true; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 082f33ba9..e4b8568b1 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -94,6 +94,7 @@ class DivPlatformGB: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + int getPortaFloor(int ch); bool isStereo(); void notifyInsChange(int ins); void notifyWaveChange(int wave); From 340052cf0a50f6a848d5c545df4ada5a6c077283 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 00:46:15 -0500 Subject: [PATCH 149/194] Game Boy: add chip revision flag --- src/engine/platform/gb.cpp | 35 ++++++++++++++++++++++++++++++----- src/engine/platform/gb.h | 9 +++++++++ src/gui/sysConf.cpp | 13 +++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 31ce24240..39ccaf56c 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } -#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER 16 @@ -84,6 +84,12 @@ const char* DivPlatformGB::getEffectName(unsigned char effect) { void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; iapu_output.final_sample.left; bufR[i]=gb->apu_output.final_sample.right; @@ -97,8 +103,8 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformGB::updateWave() { rWrite(0x1a,0); for (int i=0; i<16; i++) { - int nibble1=15-ws.output[((i<<1)+antiClickWavePos)&31]; - int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos)&31]; + int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31]; + int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31]; rWrite(0x30+i,(nibble1<<4)|nibble2); } antiClickWavePos&=31; @@ -559,7 +565,7 @@ void DivPlatformGB::reset() { } memset(gb,0,sizeof(GB_gameboy_t)); memset(regPool,0,128); - gb->model=GB_MODEL_DMG_B; + gb->model=model; GB_apu_init(gb); GB_set_sample_rate(gb,rate); // enable all channels @@ -581,6 +587,10 @@ bool DivPlatformGB::isStereo() { return true; } +bool DivPlatformGB::getDCOffRequired() { + return (model==GB_MODEL_AGB); +} + void DivPlatformGB::notifyInsChange(int ins) { for (int i=0; i<4; i++) { if (chan[i].ins==ins) { @@ -613,6 +623,20 @@ void DivPlatformGB::poke(std::vector& wlist) { void DivPlatformGB::setFlags(unsigned int flags) { antiClickEnabled=!(flags&8); + switch (flags&3) { + case 0: + model=GB_MODEL_DMG_B; + break; + case 1: + model=GB_MODEL_CGB_C; + break; + case 2: + model=GB_MODEL_CGB_E; + break; + case 3: + model=GB_MODEL_AGB; + break; + } } int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -626,6 +650,7 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl parent=p; dumpWrites=false; skipRegisterWrites=false; + model=GB_MODEL_DMG_B; gb=new GB_gameboy_t; setFlags(flags); reset(); diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index e4b8568b1..ce582979f 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -24,6 +24,7 @@ #include "../macroInt.h" #include "../waveSynth.h" #include "sound/gb/gb.h" +#include class DivPlatformGB: public DivDispatch { struct Channel { @@ -73,10 +74,17 @@ class DivPlatformGB: public DivDispatch { bool antiClickEnabled; unsigned char lastPan; DivWaveSynth ws; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; int antiClickPeriodCount, antiClickWavePos; GB_gameboy_t* gb; + GB_model_t model; unsigned char regPool[128]; unsigned char procMute(); @@ -96,6 +104,7 @@ class DivPlatformGB: public DivDispatch { void muteChannel(int ch, bool mute); int getPortaFloor(int ch); bool isStereo(); + bool getDCOffRequired(); void notifyInsChange(int ins); void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 3259f00c0..484490348 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -185,6 +185,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::Checkbox("Disable anti-click",&antiClick)) { copyOfFlags=(flags&(~8))|(antiClick<<3); } + ImGui::Text("Chip revision:"); + if (ImGui::RadioButton("Original (DMG)",(flags&7)==0)) { + copyOfFlags=(flags&(~7))|0; + } + if (ImGui::RadioButton("Game Boy Color (rev C)",(flags&7)==1)) { + copyOfFlags=(flags&(~7))|1; + } + if (ImGui::RadioButton("Game Boy Color (rev E)",(flags&7)==2)) { + copyOfFlags=(flags&(~7))|2; + } + if (ImGui::RadioButton("Game Boy Advance",(flags&7)==3)) { + copyOfFlags=(flags&(~7))|3; + } break; } case DIV_SYSTEM_OPLL: From d30f9bc8a02f80d337ba6239b7d1a38d8152c96b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 01:24:53 -0500 Subject: [PATCH 150/194] Game Boy: software envelopes, part 3 zombie mode --- src/engine/platform/gb.cpp | 29 +++++++++++++++++++++++++---- src/engine/platform/gb.h | 6 ++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 39ccaf56c..2fad226ae 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -171,12 +171,13 @@ void DivPlatformGB::tick(bool sysTick) { chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); if (chan[i].outVol<0) chan[i].outVol=0; - // temporary until zombie mode is implemented chan[i].envLen=0; - chan[i].envDir=0; + chan[i].envDir=1; chan[i].envVol=chan[i].outVol; chan[i].soundLen=64; - chan[i].keyOn=true; + + if (!chan[i].keyOn) chan[i].killIt=true; + chan[i].freqChanged=true; } } if (chan[i].std.arp.had) { @@ -327,6 +328,7 @@ void DivPlatformGB::tick(bool sysTick) { } else { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); + chan[i].lastKill=chan[i].envVol; } } if (chan[i].keyOff) { @@ -346,6 +348,25 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; chan[i].freqChanged=false; + + if (chan[i].killIt) { + if (i!=2) { + //rWrite(16+i*5+2,8); + int killDelta=chan[i].lastKill-chan[i].outVol+1; + if (killDelta<0) killDelta+=16; + chan[i].lastKill=chan[i].outVol; + + if (killDelta!=1) { + rWrite(16+i*5+2,((chan[i].envVol<<4))|8); + for (int j=0; jgb.alwaysInit) { + if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) { chan[c.chan].envVol=ins->gb.envVol; chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envDir=ins->gb.envDir; diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index ce582979f..9498317bd 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -30,8 +30,8 @@ class DivPlatformGB: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; unsigned char duty, sweep; - bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv; - signed char vol, outVol, wave; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt; + signed char vol, outVol, wave, lastKill; unsigned char envVol, envDir, envLen, soundLen; unsigned short hwSeqPos; short hwSeqDelay; @@ -58,9 +58,11 @@ class DivPlatformGB: public DivDispatch { inPorta(false), released(false), softEnv(false), + killIt(false), vol(15), outVol(15), wave(-1), + lastKill(0), envVol(0), envDir(0), envLen(0), From 7e7a5a8e3037cdf0415a1db3adb512ad04a4fc9e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 01:34:18 -0500 Subject: [PATCH 151/194] Game Boy: software envelopes, part 4 fixes --- src/engine/platform/gb.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 2fad226ae..3797d5bca 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -171,13 +171,17 @@ void DivPlatformGB::tick(bool sysTick) { chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); if (chan[i].outVol<0) chan[i].outVol=0; - chan[i].envLen=0; - chan[i].envDir=1; - chan[i].envVol=chan[i].outVol; - chan[i].soundLen=64; + if (i==2) { + rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + } else { + chan[i].envLen=0; + chan[i].envDir=1; + chan[i].envVol=chan[i].outVol; + chan[i].soundLen=64; - if (!chan[i].keyOn) chan[i].killIt=true; - chan[i].freqChanged=true; + if (!chan[i].keyOn) chan[i].killIt=true; + chan[i].freqChanged=true; + } } } if (chan[i].std.arp.had) { @@ -452,6 +456,10 @@ int DivPlatformGB::dispatch(DivCommand c) { } if (!chan[c.chan].softEnv) { chan[c.chan].envVol=chan[c.chan].vol; + } else if (c.chan!=2) { + chan[c.chan].envVol=chan[c.chan].vol; + if (!chan[c.chan].keyOn) chan[c.chan].killIt=true; + chan[c.chan].freqChanged=true; } break; case DIV_CMD_GET_VOLUME: From ed98df91d284b0c9d37515ac54d80c6fa8eb7ee4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 02:05:05 -0500 Subject: [PATCH 152/194] turn on proper noise layout by default --- src/engine/song.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/song.h b/src/engine/song.h index 8375fc561..8b7eaa59f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -562,7 +562,7 @@ struct DivSong { linearPitch(2), pitchSlideSpeed(4), loopModality(0), - properNoiseLayout(false), + properNoiseLayout(true), waveDutyIsVol(false), resetMacroOnPorta(false), legacyVolumeSlides(false), From 762b3b292845cea80acbdb8b22c1bef2bc2a1ce5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 02:08:24 -0500 Subject: [PATCH 153/194] PCE: per-chan osc DAC mode overflow fix --- src/engine/platform/pce.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 5d9a0de88..3fc7a05bd 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -115,7 +115,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) pce->ResetTS(0); for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767); } tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); From 81482c2f2be021b58f605c49060500e9646c6613 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 04:50:16 -0500 Subject: [PATCH 154/194] QSound: SAMPLE LOOP BUG DEBUG BEGIN --- src/engine/platform/qsound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 6bc88dbf0..ca6a794cd 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -365,7 +365,7 @@ void DivPlatformQSound::tick(bool sysTick) { rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop); rWrite(q1_reg_map[Q1V_START][i], qsound_addr); rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000); - //logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); + logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); // Write sample address. Enable volume if (!chan[i].std.vol.had) { rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4); From 0528f4e7bde0d04609d37ed0d6a3f9205de7f2d8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 05:04:35 -0500 Subject: [PATCH 155/194] Game Boy: possibly fix wave soft env --- src/engine/platform/gb.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 3797d5bca..0a8b624c9 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -173,6 +173,7 @@ void DivPlatformGB::tick(bool sysTick) { if (i==2) { rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + chan[i].soundLen=64; } else { chan[i].envLen=0; chan[i].envDir=1; @@ -413,6 +414,9 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].envDir=ins->gb.envDir; chan[c.chan].soundLen=ins->gb.soundLen; } + if (c.chan==2 && chan[c.chan].softEnv) { + chan[c.chan].soundLen=64; + } chan[c.chan].insChanged=false; break; } From 01d1556fb496675927a8c4b1fd5e1ee694b792e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 05:38:31 -0500 Subject: [PATCH 156/194] GUI: rename "system" to "chip" "system" made sense when Furnace was a .dmf tracker and had compound setups like Genesis (YM2612+SN) however, it doesn't make too much sense now when compared to "chip" --- src/gui/debug.cpp | 2 +- src/gui/debugWindow.cpp | 4 ++-- src/gui/gui.cpp | 22 +++++++++++----------- src/gui/settings.cpp | 11 +++-------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index f1c974e8b..5ae596bcd 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -334,7 +334,7 @@ void putDispatchChan(void* data, int chanNum, int type) { break; } default: - ImGui::Text("Unknown system! Help!"); + ImGui::Text("Unimplemented chip! Help!"); break; } } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 6ad2ddddf..8fe02b116 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -297,7 +297,7 @@ void FurnaceGUI::drawDebug() { } if (ImGui::TreeNode("Playground")) { if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; - if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { + if (ImGui::BeginCombo("Chip",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { for (int i=0; isong.systemLen; i++) { if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { pgSys=i; @@ -358,7 +358,7 @@ void FurnaceGUI::drawDebug() { if (ImGui::TreeNode("Register Cheatsheet")) { const char** sheet=e->getRegisterSheet(pgSys); if (sheet==NULL) { - ImGui::Text("no cheatsheet available for this system."); + ImGui::Text("no cheatsheet available for this chip."); } else { if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { ImGui::TableNextRow(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c1cd7f076..8ee6c60b9 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1879,7 +1879,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { #define sysAddOption(x) \ if (ImGui::MenuItem(getSystemName(x))) { \ if (!e->addSystem(x)) { \ - showError("cannot add system! ("+e->getLastError()+")"); \ + showError("cannot add chip! ("+e->getLastError()+")"); \ } \ updateWindowTitle(); \ } @@ -2887,7 +2887,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("one file")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); } - if (ImGui::MenuItem("multiple files (one per system)")) { + if (ImGui::MenuItem("multiple files (one per chip)")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); } if (ImGui::MenuItem("multiple files (one per channel)")) { @@ -2928,7 +2928,7 @@ bool FurnaceGUI::loop() { "pattern indexes are ordered as they appear in the song." ); } - ImGui::Text("systems to export:"); + ImGui::Text("chips to export:"); bool hasOneAtLeast=false; for (int i=0; isong.systemLen; i++) { int minVersion=e->minVGMVersion(e->song.system[i]); @@ -2937,17 +2937,17 @@ bool FurnaceGUI::loop() { ImGui::EndDisabled(); if (minVersion>vgmExportVersion) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this system is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); } } else if (minVersion==0) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this system is not supported by the VGM format!"); + ImGui::SetTooltip("this chip is not supported by the VGM format!"); } } else { if (willExport[i]) hasOneAtLeast=true; } } - ImGui::Text("select the systems you wish to export,"); + ImGui::Text("select the chip you wish to export,"); ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); if (hasOneAtLeast) { if (ImGui::MenuItem("click to export")) { @@ -2972,14 +2972,14 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::BeginMenu("add system...")) { + if (ImGui::BeginMenu("add chip...")) { for (int j=0; availableSystems[j]; j++) { if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY)) continue; sysAddOption((DivSystem)availableSystems[j]); } ImGui::EndMenu(); } - if (ImGui::BeginMenu("configure system...")) { + if (ImGui::BeginMenu("configure chip...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); @@ -2988,7 +2988,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("change system...")) { + if (ImGui::BeginMenu("change chip...")) { ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { @@ -3001,12 +3001,12 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("remove system...")) { + if (ImGui::BeginMenu("remove chip...")) { ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (!e->removeSystem(i,preserveChanPos)) { - showError("cannot remove system! ("+e->getLastError()+")"); + showError("cannot remove chip! ("+e->getLastError()+")"); } } } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 73e9d2333..37d4bfecf 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -468,7 +468,7 @@ void FurnaceGUI::drawSettings() { } bool restartOnFlagChangeB=settings.restartOnFlagChange; - if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { + if (ImGui::Checkbox("Restart song when changing chip properties",&restartOnFlagChangeB)) { settings.restartOnFlagChange=restartOnFlagChangeB; } @@ -1232,11 +1232,6 @@ void FurnaceGUI::drawSettings() { } ImGui::EndDisabled(); - bool chipNamesB=settings.chipNames; - if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) { - settings.chipNames=chipNamesB; - } - bool oplStandardWaveNamesB=settings.oplStandardWaveNames; if (ImGui::Checkbox("Use standard OPL waveform names",&oplStandardWaveNamesB)) { settings.oplStandardWaveNames=oplStandardWaveNamesB; @@ -1567,8 +1562,8 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SONG,"Song effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_TIME,"Time effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SPEED,"Speed effect"); - UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary system effect"); - UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary system effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary specific effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary specific effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_MISC,"Miscellaneous"); UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); ImGui::TreePop(); From 00ae5b4142589d37d5e038e1b8afc287561b0d37 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 11:30:45 -0500 Subject: [PATCH 157/194] GUI: fix volume macro always being 15 issue #629 --- src/gui/insEdit.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0d90e3733..55094252f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3737,10 +3737,12 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { volMax=127; } - if (ins->type==DIV_INS_GB && !ins->gb.softEnv) { - volMax=0; - } else { - volMax=15; + if (ins->type==DIV_INS_GB) { + if (ins->gb.softEnv) { + volMax=15; + } else { + volMax=0; + } } if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { volMax=1; From b156336216a7f55c04dcc67cbf3c1cfd39bec2bc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Aug 2022 14:27:33 -0500 Subject: [PATCH 158/194] GUI: fix Game Boy ins edit crashes --- src/gui/insEdit.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 55094252f..1c29c5d9b 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3173,10 +3173,9 @@ void FurnaceGUI::drawInsEdit() { ins->gb.hwSeqLen++; } } - - ImGui::EndChild(); - ImGui::EndDisabled(); } + ImGui::EndChild(); + ImGui::EndDisabled(); ImGui::EndTabItem(); } if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { From 39feda54acb0a75d32dfe7c1e6e4da6e178fb9c5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 12 Aug 2022 04:11:17 -0500 Subject: [PATCH 159/194] OPZ: volume macro should go to 127 --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1c29c5d9b..d4e76611f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3733,7 +3733,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AMIGA) { volMax=64; } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ) { volMax=127; } if (ins->type==DIV_INS_GB) { From 2743c60cf3f6021b6271af9eb9afc9c250bd535a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 12 Aug 2022 23:09:34 -0500 Subject: [PATCH 160/194] Game Boy: fix wave channel auto-enable on wave cha --- src/engine/platform/gb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 0a8b624c9..a7b310935 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -636,7 +636,7 @@ void DivPlatformGB::notifyWaveChange(int wave) { if (chan[2].wave==wave) { ws.changeWave1(wave); updateWave(); - if (!chan[2].keyOff) chan[2].keyOn=true; + if (!chan[2].keyOff && chan[2].active) chan[2].keyOn=true; } } From 5506b87b4073bc4e22772c5c7606b472ce4e7dcd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 02:48:34 -0500 Subject: [PATCH 161/194] update sample doc --- papers/doc/6-sample/README.md | 72 ++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 0c038fdb9..035a58cc6 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -4,29 +4,54 @@ In the context of Furnace, a sound sample (usually just referred to as a sample) In Furnace, these samples can be generated by importing a .wav (think of it as an higher quality MP3) file. -## supported systems +## supported chips -As of Furnace 0.6, the following sound chips have sample support: - - NES/Ricoh 2A03 (with DPCM support and only on channel 5) - - Sega Genesis/YM2612 (channel 6 only; but only if there exists a `1701` effect that gets played on or before a trigger for a sample, or if you are using an instrument with Sample type) - - PC Engine/TurboGrafx 16/Huc6280 (same conditions as above) - - Amiga/Paula (on all channels) - - Arcade/SEGA PCM (same as above) - - Neo Geo/Neo Geo CD (on the last 7 channels (6 if you are using Neo Geo CD) only and can be resampled the same way as above) - - Seta/Allumer X1-010 (same as YM2612) - - Atari Lynx - - MSM6258 and MSM6295 - - YMU759/MA-2 (last channel only) - - QSound - - ZX Spectrum 48k - - RF5C68 - - WonderSwan - - Tildearrow Sound Unit - - VERA (last channel only) - - Y8590 (last channel only) - - And a few more that I've forgotten to mention. +as of Furnace 0.6, the following sound chips have sample support: -Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module. +- NES/Ricoh 2A03 (with DPCM support and only on channel 5) +- Sega Genesis/YM2612 (channel 6 only) +- PC Engine/TurboGrafx-16/HuC6280 +- Amiga/Paula +- SegaPCM +- Neo Geo/Neo Geo CD/YM2610 (ADPCM channels only) +- Seta/Allumer X1-010 +- Atari Lynx +- MSM6258 and MSM6295 +- YMU759/MA-2 (last channel only) +- QSound +- ZX Spectrum 48k (1-bit) +- RF5C68 +- WonderSwan +- tildearrow Sound Unit +- VERA (last channel only) +- Y8950 (last channel only) +- a few more that I've forgotten to mention + +## compatible sample mode + +effect `17xx` enables/disables compatible sample mode whether supported (e.g. on Sega Genesis or PC Engine). + +in this mode, samples are mapped to notes in an octave from C to B, allowing you to use up to 12 samples. +if you need to use more samples, you may change the sample bank using effect `EBxx`. + +use of this mode is discouraged in favor of Sample type instruments. + +## notes + +due to limitations in some of those sound chips, some restrictions exist: + +- Amiga: 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). +- 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. +- Neo Geo (ADPCM-A): no looping supported. your samples will play at ~18.5KHz. +- Neo Geo (ADPCM-B): no loop position supported (only entire sample), and the maximum frequency is ~55KHz. +- YM2608: the maximum frequency is ~55KHz. +- MSM6258/MSM6295: no arbitrary frequency. +- ZX Spectrum Beeper: your sample can't be longer than 2048. +- Seta/Allumer X1-010: frequency resolution is terrible in the lower end. your sample can't be longer than 131072. + +furthermore, many of these chips have a limited amount of sample memory. check memory usage in window > statistics. # the sample editor @@ -34,11 +59,12 @@ You can actually tweak your samples in Furnace's sample editor, which can be acc In there, you can modify certain data pertaining to your sample, such as the: - volume of the sample in percentage, where 100% is the current level of the sample (note that you can distort it if you put it too high) - - the sample rate, from 0Hz (no sample movement) to 65535Hz (65.5kHz). + - the sample rate. - what frequencies to filter, along with filter level/sweep and resonance options (much like the C64) - and many more. The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text. # tips -If you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file. You can do this in Audacity by going to the bottom left of the screen (If you see "Project Rate (Hz)" you are there), change the project rate to 32000Hz and save the file to wav in Audacity using "File -> Export -> Export as WAV". + +if you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file, From 4707eb7002d07747a6bfc3d9d0397a4ce0faa6e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 04:07:50 -0500 Subject: [PATCH 162/194] update Namco 163 doc --- papers/doc/4-instrument/n163.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md index 0bd914453..ca4dd2e7c 100644 --- a/papers/doc/4-instrument/n163.md +++ b/papers/doc/4-instrument/n163.md @@ -2,7 +2,7 @@ Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros. -## N163 +## Namco 163 - [Initial Waveform] - Determines the initial waveform for playing. - [Initial Waveform position in RAM] - Determines the initial waveform position will be load to RAM. - [Initial Waveform length in RAM] - Determines the initial waveform length will be load to RAM. From ce2d322e474911440cc4ca8544b27db91a55e235 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 04:17:32 -0500 Subject: [PATCH 163/194] GUI: add replace for wave/sample and prepare for raw sample import --- src/engine/engine.cpp | 112 +++++++++++++++++++++---------------- src/engine/engine.h | 17 ++++-- src/gui/dataList.cpp | 19 +++++++ src/gui/doAction.cpp | 12 ++++ src/gui/gui.cpp | 126 +++++++++++++++++++++++++++++++++++++++--- src/gui/gui.h | 12 +++- src/gui/guiConst.cpp | 4 ++ src/gui/waveEdit.cpp | 3 +- 8 files changed, 242 insertions(+), 63 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index bf55d9d6e..9b2696c17 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2196,7 +2196,10 @@ void DivEngine::delInstrument(int index) { } int DivEngine::addWave() { - if (song.wave.size()>=256) return -1; + if (song.wave.size()>=256) { + lastError="too many wavetables!"; + return -1; + } BUSY_BEGIN; saveLock.lock(); DivWavetable* wave=new DivWavetable; @@ -2208,50 +2211,62 @@ int DivEngine::addWave() { return waveCount; } -bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { +int DivEngine::addWavePtr(DivWavetable* which) { if (song.wave.size()>=256) { lastError="too many wavetables!"; - return false; + delete which; + return -1; } + BUSY_BEGIN; + saveLock.lock(); + int waveCount=(int)song.wave.size(); + song.wave.push_back(which); + song.waveLen=waveCount+1; + saveLock.unlock(); + BUSY_END; + return song.waveLen; +} + +DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) { FILE* f=ps_fopen(path,"rb"); if (f==NULL) { lastError=fmt::sprintf("%s",strerror(errno)); - return false; + return NULL; } 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; + return NULL; } len=ftell(f); if (len<0) { fclose(f); lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); - return false; + return NULL; } if (len==(SIZE_MAX>>1)) { fclose(f); lastError="file size is invalid!"; - return false; + return NULL; } if (len==0) { fclose(f); lastError="file is empty"; - return false; + return NULL; } if (fseek(f,0,SEEK_SET)!=0) { fclose(f); lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); - return false; + return NULL; } 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; + return NULL; } fclose(f); @@ -2279,7 +2294,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { lastError="invalid wavetable header/data!"; delete wave; delete[] buf; - return false; + return NULL; } } else { try { @@ -2320,7 +2335,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { } else { delete wave; delete[] buf; - return false; + return NULL; } } } catch (EndOfFileException& e) { @@ -2338,7 +2353,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { } else { delete wave; delete[] buf; - return false; + return NULL; } } } @@ -2346,17 +2361,10 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { delete wave; delete[] buf; lastError="premature end of file"; - return false; + return NULL; } - BUSY_BEGIN; - saveLock.lock(); - int waveCount=(int)song.wave.size(); - song.wave.push_back(wave); - song.waveLen=waveCount+1; - saveLock.unlock(); - BUSY_END; - return true; + return wave; } void DivEngine::delWave(int index) { @@ -2372,7 +2380,10 @@ void DivEngine::delWave(int index) { } int DivEngine::addSample() { - if (song.sample.size()>=256) return -1; + if (song.sample.size()>=256) { + lastError="too many samples!"; + return -1; + } BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; @@ -2388,11 +2399,28 @@ int DivEngine::addSample() { return sampleCount; } -int DivEngine::addSampleFromFile(const char* path) { +int DivEngine::addSamplePtr(DivSample* which) { if (song.sample.size()>=256) { lastError="too many samples!"; + delete which; return -1; } + int sampleCount=(int)song.sample.size(); + BUSY_BEGIN; + saveLock.lock(); + song.sample.push_back(which); + song.sampleLen=sampleCount+1; + saveLock.unlock(); + renderSamples(); + BUSY_END; + return sampleCount; +} + +DivSample* DivEngine::sampleFromFile(const char* path) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return NULL; + } BUSY_BEGIN; warnings=""; @@ -2425,7 +2453,6 @@ int DivEngine::addSampleFromFile(const char* path) { if (extS==".dmc") { // read as .dmc size_t len=0; DivSample* sample=new DivSample; - int sampleCount=(int)song.sample.size(); sample->name=stripPath; FILE* f=ps_fopen(path,"rb"); @@ -2433,7 +2460,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } if (fseek(f,0,SEEK_END)<0) { @@ -2441,7 +2468,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } len=ftell(f); @@ -2451,7 +2478,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError="file is empty!"; delete sample; - return -1; + return NULL; } if (len==(SIZE_MAX>>1)) { @@ -2459,7 +2486,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError="file is invalid!"; delete sample; - return -1; + return NULL; } if (fseek(f,0,SEEK_SET)<0) { @@ -2467,7 +2494,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } sample->rate=33144; @@ -2480,22 +2507,16 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } - - saveLock.lock(); - song.sample.push_back(sample); - song.sampleLen=sampleCount+1; - saveLock.unlock(); - renderSamples(); BUSY_END; - return sampleCount; + return sample; } } #ifndef HAVE_SNDFILE lastError="Furnace was not compiled with libsndfile!"; - return -1; + return NULL; #else SF_INFO si; SFWrapper sfWrap; @@ -2507,15 +2528,15 @@ int DivEngine::addSampleFromFile(const char* path) { if (err==SF_ERR_SYSTEM) { lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); } else { - lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(err)); + lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err)); } - return -1; + return NULL; } if (si.frames>16777215) { lastError="this sample is too big! max sample size is 16777215."; sfWrap.doClose(); BUSY_END; - return -1; + return NULL; } void* buf=NULL; sf_count_t sampleLen=sizeof(short); @@ -2613,13 +2634,8 @@ int DivEngine::addSampleFromFile(const char* path) { if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate>64000) sample->centerRate=64000; sfWrap.doClose(); - saveLock.lock(); - song.sample.push_back(sample); - song.sampleLen=sampleCount+1; - saveLock.unlock(); - renderSamples(); BUSY_END; - return sampleCount; + return sample; #endif } diff --git a/src/engine/engine.h b/src/engine/engine.h index 483ebcf0d..a03c92432 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -706,8 +706,11 @@ class DivEngine { // add wavetable int addWave(); - // add wavetable from file - bool addWaveFromFile(const char* path, bool loadRaw=true); + // add wavetable from pointer + int addWavePtr(DivWavetable* which); + + // get wavetable from file + DivWavetable* waveFromFile(const char* path, bool loadRaw=true); // delete wavetable void delWave(int index); @@ -715,8 +718,14 @@ class DivEngine { // add sample int addSample(); - // add sample from file - int addSampleFromFile(const char* path); + // add sample from pointer + int addSamplePtr(DivSample* which); + + // get sample from file + DivSample* sampleFromFile(const char* path); + + // get raw sample + DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian); // delete sample void delSample(int index); diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 3a536951f..10dda6aaf 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -420,6 +420,12 @@ void FurnaceGUI::drawWaveList() { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { doAction(GUI_ACTION_WAVE_LIST_OPEN); } + if (ImGui::BeginPopupContextItem("WaveOpenOpt")) { + if (ImGui::MenuItem("replace...")) { + doAction((curWave>=0 && curWave<(int)e->song.wave.size())?GUI_ACTION_WAVE_LIST_OPEN_REPLACE:GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); @@ -470,6 +476,19 @@ void FurnaceGUI::drawSampleList() { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN); } + if (ImGui::BeginPopupContextItem("SampleOpenOpt")) { + if (ImGui::MenuItem("replace...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE:GUI_ACTION_SAMPLE_LIST_OPEN); + } + ImGui::Separator(); + if (ImGui::MenuItem("import raw...")) { + doAction(GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + if (ImGui::MenuItem("import raw (replace)...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW:GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index ca640d627..931de26dc 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -648,6 +648,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_OPEN: openFileDialog(GUI_FILE_WAVE_OPEN); break; + case GUI_ACTION_WAVE_LIST_OPEN_REPLACE: + openFileDialog(GUI_FILE_WAVE_OPEN_REPLACE); + break; case GUI_ACTION_WAVE_LIST_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); break; @@ -728,6 +731,15 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_OPEN: openFileDialog(GUI_FILE_SAMPLE_OPEN); break; + case GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE: + openFileDialog(GUI_FILE_SAMPLE_OPEN_REPLACE); + break; + case GUI_ACTION_SAMPLE_LIST_OPEN_RAW: + openFileDialog(GUI_FILE_SAMPLE_OPEN_RAW); + break; + case GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW: + openFileDialog(GUI_FILE_SAMPLE_OPEN_REPLACE_RAW); + break; case GUI_ACTION_SAMPLE_LIST_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8ee6c60b9..022dfe83f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1320,6 +1320,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { ); break; case GUI_FILE_WAVE_OPEN: + case GUI_FILE_WAVE_OPEN_REPLACE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Wavetable", @@ -1341,6 +1342,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { ); break; case GUI_FILE_SAMPLE_OPEN: + case GUI_FILE_SAMPLE_OPEN_REPLACE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Sample", @@ -1351,6 +1353,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_SAMPLE_OPEN_RAW: + case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Load Raw Sample", + {"all files", ".*"}, + ".*", + workingDirSample, + dpiScale + ); + break; case GUI_FILE_SAMPLE_SAVE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openSave( @@ -2630,6 +2643,8 @@ bool FurnaceGUI::loop() { case SDL_DROPFILE: if (ev.drop.file!=NULL) { std::vector instruments=e->instrumentFromFile(ev.drop.file); + DivWavetable* droppedWave=NULL; + DivSample* droppedSample=NULL;; if (!instruments.empty()) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); @@ -2639,10 +2654,12 @@ bool FurnaceGUI::loop() { } nextWindow=GUI_WINDOW_INS_LIST; MARK_MODIFIED; - } else if (e->addWaveFromFile(ev.drop.file,false)) { + } else if ((droppedWave=e->waveFromFile(ev.drop.file,false))!=NULL) { + e->addWavePtr(droppedWave); nextWindow=GUI_WINDOW_WAVE_LIST; MARK_MODIFIED; - } else if (e->addSampleFromFile(ev.drop.file)!=-1) { + } else if ((droppedSample=e->sampleFromFile(ev.drop.file))!=NULL) { + e->addSamplePtr(droppedSample); nextWindow=GUI_WINDOW_SAMPLE_LIST; MARK_MODIFIED; } else if (modified) { @@ -3269,10 +3286,14 @@ bool FurnaceGUI::loop() { workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: + case GUI_FILE_WAVE_OPEN_REPLACE: case GUI_FILE_WAVE_SAVE: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: + case GUI_FILE_SAMPLE_OPEN_RAW: + case GUI_FILE_SAMPLE_OPEN_REPLACE: + case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: case GUI_FILE_SAMPLE_SAVE: workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; @@ -3438,13 +3459,45 @@ bool FurnaceGUI::loop() { e->song.wave[curWave]->save(copyOfName.c_str()); } break; - case GUI_FILE_SAMPLE_OPEN: - if (e->addSampleFromFile(copyOfName.c_str())==-1) { + case GUI_FILE_SAMPLE_OPEN: { + DivSample* s=e->sampleFromFile(copyOfName.c_str()); + if (s==NULL) { showError(e->getLastError()); } else { - MARK_MODIFIED; + if (e->addSamplePtr(s)==-1) { + showError(e->getLastError()); + } else { + MARK_MODIFIED; + } } break; + } + case GUI_FILE_SAMPLE_OPEN_REPLACE: { + DivSample* s=e->sampleFromFile(copyOfName.c_str()); + if (s==NULL) { + showError(e->getLastError()); + } else { + if (curSample>=0 && curSample<(int)e->song.sample.size()) { + e->lockEngine([this,s]() { + // if it crashes here please tell me... + DivSample* oldSample=e->song.sample[curSample]; + e->song.sample[curSample]=s; + delete oldSample; + e->renderSamples(); + MARK_MODIFIED; + }); + } else { + showError("...but you haven't selected a sample!"); + delete s; + } + } + break; + } + case GUI_FILE_SAMPLE_OPEN_RAW: + case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: + pendingRawSample=copyOfName; + displayPendingRawSample=true; + break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { e->song.sample[curSample]->save(copyOfName.c_str()); @@ -3508,13 +3561,36 @@ bool FurnaceGUI::loop() { } break; } - case GUI_FILE_WAVE_OPEN: - if (!e->addWaveFromFile(copyOfName.c_str())) { + case GUI_FILE_WAVE_OPEN: { + DivWavetable* wave=e->waveFromFile(copyOfName.c_str()); + if (wave==NULL) { showError("cannot load wavetable! ("+e->getLastError()+")"); } else { - MARK_MODIFIED; + if (e->addWavePtr(wave)==-1) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + MARK_MODIFIED; + } } break; + } + case GUI_FILE_WAVE_OPEN_REPLACE: { + DivWavetable* wave=e->waveFromFile(copyOfName.c_str()); + if (wave==NULL) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->lockEngine([this,wave]() { + *e->song.wave[curWave]=*wave; + MARK_MODIFIED; + }); + } else { + showError("...but you haven't selected a wavetable!"); + } + delete wave; + } + break; + } case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints); if (w!=NULL) { @@ -3641,6 +3717,11 @@ bool FurnaceGUI::loop() { ImGui::OpenPopup("Select Instrument"); } + if (displayPendingRawSample) { + displayPendingRawSample=false; + ImGui::OpenPopup("Import Raw Sample"); + } + if (displayExporting) { displayExporting=false; ImGui::OpenPopup("Rendering..."); @@ -4071,6 +4152,34 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + bool doRespond=false; + if (ImGui::BeginPopupModal("Import Raw Sample",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Work In Progress - sorry"); + if (ImGui::Button("Oh... really?")) { + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Why are you so hostile? I'm just trying to import a raw sample.")) { + doRespond=true; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + if (doRespond) { + doRespond=false; + ImGui::OpenPopup("Fatal Alert"); + } + + if (ImGui::BeginPopupModal("Fatal Alert",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Well, I'd rather you didn't. So, good night."); + if (ImGui::Button("Fine")) { + abort(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + layoutTimeEnd=SDL_GetPerformanceCounter(); // backup trigger @@ -4521,6 +4630,7 @@ FurnaceGUI::FurnaceGUI(): noteInputPoly(true), displayPendingIns(false), pendingInsSingle(false), + displayPendingRawSample(false), vgmExportVersion(0x171), drawHalt(10), macroPointSize(16), diff --git a/src/gui/gui.h b/src/gui/gui.h index 0ce4afaee..3b148924e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -259,8 +259,12 @@ enum FurnaceGUIFileDialogs { GUI_FILE_INS_OPEN_REPLACE, GUI_FILE_INS_SAVE, GUI_FILE_WAVE_OPEN, + GUI_FILE_WAVE_OPEN_REPLACE, GUI_FILE_WAVE_SAVE, GUI_FILE_SAMPLE_OPEN, + GUI_FILE_SAMPLE_OPEN_RAW, + GUI_FILE_SAMPLE_OPEN_REPLACE, + GUI_FILE_SAMPLE_OPEN_REPLACE_RAW, GUI_FILE_SAMPLE_SAVE, GUI_FILE_EXPORT_AUDIO_ONE, GUI_FILE_EXPORT_AUDIO_PER_SYS, @@ -451,6 +455,7 @@ enum FurnaceGUIActions { GUI_ACTION_WAVE_LIST_ADD, GUI_ACTION_WAVE_LIST_DUPLICATE, GUI_ACTION_WAVE_LIST_OPEN, + GUI_ACTION_WAVE_LIST_OPEN_REPLACE, GUI_ACTION_WAVE_LIST_SAVE, GUI_ACTION_WAVE_LIST_MOVE_UP, GUI_ACTION_WAVE_LIST_MOVE_DOWN, @@ -464,6 +469,9 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_ADD, GUI_ACTION_SAMPLE_LIST_DUPLICATE, GUI_ACTION_SAMPLE_LIST_OPEN, + GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE, + GUI_ACTION_SAMPLE_LIST_OPEN_RAW, + GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW, GUI_ACTION_SAMPLE_LIST_SAVE, GUI_ACTION_SAMPLE_LIST_MOVE_UP, GUI_ACTION_SAMPLE_LIST_MOVE_DOWN, @@ -958,13 +966,15 @@ class FurnaceGUI { bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; - bool displayPendingIns, pendingInsSingle; + bool displayPendingIns, pendingInsSingle, displayPendingRawSample; bool willExport[32]; int vgmExportVersion; int drawHalt; int macroPointSize; int waveEditStyle; + String pendingRawSample; + ImGuiWindowFlags globalWinFlags; FurnaceGUIFileDialogs curFileDialog; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 59c917ae0..5fabb1c72 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -581,6 +581,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WAVE_LIST_ADD", "Add", SDLK_INSERT), D("WAVE_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), D("WAVE_LIST_OPEN", "Open", 0), + D("WAVE_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("WAVE_LIST_SAVE", "Save", 0), D("WAVE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("WAVE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), @@ -594,6 +595,9 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_LIST_ADD", "Add", SDLK_INSERT), D("SAMPLE_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), D("SAMPLE_LIST_OPEN", "Open", 0), + D("SAMPLE_LIST_OPEN_REPLACE", "Open (replace current)", 0), + D("SAMPLE_LIST_OPEN_RAW", "Import raw data", 0), + D("SAMPLE_LIST_OPEN_REPLACE_RAW", "Import raw data (replace current)", 0), D("SAMPLE_LIST_SAVE", "Save", 0), D("SAMPLE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("SAMPLE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index ac06bcb7c..7e453ef58 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -131,9 +131,8 @@ void FurnaceGUI::drawWaveEdit() { if (curWave>=(int)e->song.wave.size()) curWave=e->song.wave.size()-1; } ImGui::SameLine(); - // TODO: load replace if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WELoad")) { - doAction(GUI_ACTION_WAVE_LIST_OPEN); + doAction(GUI_ACTION_WAVE_LIST_OPEN_REPLACE); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { From 91f9352eafbc516e97100950cac47636f81f316c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 05:50:36 -0500 Subject: [PATCH 164/194] implement raw sample import untested --- src/engine/engine.cpp | 166 ++++++++++++++++++++++++++++++++++++++++++ src/gui/gui.cpp | 56 +++++++++----- src/gui/gui.h | 2 + 3 files changed, 205 insertions(+), 19 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9b2696c17..ffe2d6aa3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2639,6 +2639,172 @@ DivSample* DivEngine::sampleFromFile(const char* path) { #endif } +DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return NULL; + } + if (channels<1) { + lastError="invalid channel count"; + return NULL; + } + if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) { + if (channels!=1) { + lastError="channel count has to be 1 for non-8/16-bit format"; + return NULL; + } + } + BUSY_BEGIN; + warnings=""; + + const char* pathRedux=strrchr(path,DIR_SEPARATOR); + if (pathRedux==NULL) { + pathRedux=path; + } else { + pathRedux++; + } + String stripPath; + const char* pathReduxEnd=strrchr(pathRedux,'.'); + if (pathReduxEnd==NULL) { + stripPath=pathRedux; + } else { + for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { + stripPath+=*i; + } + } + + size_t len=0; + size_t lenDivided=0; + DivSample* sample=new DivSample; + sample->name=stripPath; + + FILE* f=ps_fopen(path,"rb"); + if (f==NULL) { + BUSY_END; + lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_END)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + len=ftell(f); + + if (len==0) { + fclose(f); + BUSY_END; + lastError="file is empty!"; + delete sample; + return NULL; + } + + if (len==(SIZE_MAX>>1)) { + fclose(f); + BUSY_END; + lastError="file is invalid!"; + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_SET)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + lenDivided=len/channels; + + unsigned int samples=lenDivided; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + samples=lenDivided*8; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + case DIV_SAMPLE_DEPTH_ADPCM_A: + case DIV_SAMPLE_DEPTH_ADPCM_B: + case DIV_SAMPLE_DEPTH_VOX: + samples=lenDivided*2; + break; + case DIV_SAMPLE_DEPTH_8BIT: + samples=lenDivided; + break; + case DIV_SAMPLE_DEPTH_BRR: + samples=16*((lenDivided+8)/9); + break; + case DIV_SAMPLE_DEPTH_16BIT: + samples=(lenDivided+1)/2; + break; + default: + break; + } + + if (samples>16777215) { + fclose(f); + BUSY_END; + lastError="this sample is too big! max sample size is 16777215."; + delete sample; + return NULL; + } + + sample->rate=32000; + sample->centerRate=32000; + sample->depth=depth; + sample->init(samples); + + unsigned char* buf=new unsigned char[len]; + if (fread(buf,1,len,f)==0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); + delete[] buf; + delete sample; + return NULL; + } + + fclose(f); + + // import sample + size_t pos=0; + if (depth==DIV_SAMPLE_DEPTH_16BIT) { + for (unsigned int i=0; i=len) break; + accum+=((short*)buf)[pos>>1]; + pos+=2; + } + accum/=channels; + sample->data16[i]=accum; + } + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { + for (unsigned int i=0; i=len) break; + accum+=(signed char)buf[pos++]; + } + accum/=channels; + sample->data8[i]=accum; + } + } else { + memcpy(sample->getCurBuf(),buf,len); + } + delete[] buf; + + BUSY_END; + return sample; +} + void DivEngine::delSample(int index) { BUSY_BEGIN; sPreview.sample=-1; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 022dfe83f..4d01f60ff 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4152,29 +4152,47 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } - bool doRespond=false; if (ImGui::BeginPopupModal("Import Raw Sample",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Work In Progress - sorry"); - if (ImGui::Button("Oh... really?")) { + ImGui::Text("Data type:"); + for (int i=0; isampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian); + if (s==NULL) { + showError(e->getLastError()); + } else { + if (e->addSamplePtr(s)==-1) { + showError(e->getLastError()); + } else { + MARK_MODIFIED; + } + } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button("Why are you so hostile? I'm just trying to import a raw sample.")) { - doRespond=true; - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - if (doRespond) { - doRespond=false; - ImGui::OpenPopup("Fatal Alert"); - } - - if (ImGui::BeginPopupModal("Fatal Alert",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Well, I'd rather you didn't. So, good night."); - if (ImGui::Button("Fine")) { - abort(); + if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 3b148924e..e178efc9d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -974,6 +974,8 @@ class FurnaceGUI { int waveEditStyle; String pendingRawSample; + int pendingRawSampleDepth, pendingRawSampleChannels; + bool pendingRawSampleBigEndian; ImGuiWindowFlags globalWinFlags; From 041a76ad816159436ea309bf44a5e437bc8488f1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 06:25:11 -0500 Subject: [PATCH 165/194] raw sample import fixes --- src/engine/engine.cpp | 10 +++++++--- src/engine/engine.h | 2 +- src/gui/gui.cpp | 7 ++++++- src/gui/gui.h | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ffe2d6aa3..4c51f367f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2639,7 +2639,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) { #endif } -DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian) { +DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign) { if (song.sample.size()>=256) { lastError="too many samples!"; return NULL; @@ -2780,7 +2780,11 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int accum=0; for (int j=0; j=len) break; - accum+=((short*)buf)[pos>>1]; + if (bigEndian) { + accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0)); + } else { + accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0)); + } pos+=2; } accum/=channels; @@ -2791,7 +2795,7 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int accum=0; for (int j=0; j=len) break; - accum+=(signed char)buf[pos++]; + accum+=(signed char)(buf[pos++]^(unsign?0x80:0)); } accum/=channels; sample->data8[i]=accum; diff --git a/src/engine/engine.h b/src/engine/engine.h index a03c92432..3a73d7d24 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -725,7 +725,7 @@ class DivEngine { DivSample* sampleFromFile(const char* path); // get raw sample - DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian); + DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign); // delete sample void delSample(int index); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4d01f60ff..0ed0ffbe1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4172,6 +4172,7 @@ bool FurnaceGUI::loop() { if (ImGui::InputInt("##RSChans",&pendingRawSampleChannels)) { } ImGui::Text("(will be mixed down to mono)"); + ImGui::Checkbox("Unsigned",&pendingRawSampleUnsigned); ImGui::EndDisabled(); ImGui::BeginDisabled(pendingRawSampleDepth!=DIV_SAMPLE_DEPTH_16BIT); @@ -4179,7 +4180,7 @@ bool FurnaceGUI::loop() { ImGui::EndDisabled(); if (ImGui::Button("OK")) { - DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian); + DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned); if (s==NULL) { showError(e->getLastError()); } else { @@ -4653,6 +4654,10 @@ FurnaceGUI::FurnaceGUI(): drawHalt(10), macroPointSize(16), waveEditStyle(0), + pendingRawSampleDepth(8), + pendingRawSampleChannels(1), + pendingRawSampleUnsigned(false), + pendingRawSampleBigEndian(false), globalWinFlags(0), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), diff --git a/src/gui/gui.h b/src/gui/gui.h index e178efc9d..ff6dd93bb 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -975,7 +975,7 @@ class FurnaceGUI { String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; - bool pendingRawSampleBigEndian; + bool pendingRawSampleUnsigned, pendingRawSampleBigEndian; ImGuiWindowFlags globalWinFlags; From 02fb5abc021c241e34606790ba1591fdbd6c49db Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 15:43:13 -0500 Subject: [PATCH 166/194] add ability to save ins/wave as .dmp/.dmw also saving wavetables as raw data --- src/engine/instrument.cpp | 145 ++++++++++++++++++++++++++++++++++++++ src/engine/instrument.h | 7 ++ src/engine/wavetable.cpp | 62 ++++++++++++++++ src/engine/wavetable.h | 16 ++++- src/gui/gui.cpp | 61 +++++++++++++--- 5 files changed, 282 insertions(+), 9 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 555d7d17f..2836e6f95 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1150,3 +1150,148 @@ bool DivInstrument::save(const char* path) { w->finish(); return true; } + +bool DivInstrument::saveDMP(const char* path) { + SafeWriter* w=new SafeWriter(); + w->init(); + + // write version + w->writeC(11); + + // guess the system + switch (type) { + case DIV_INS_FM: + // we can't tell between Genesis, Neo Geo and Arcade ins type yet + w->writeC(0x02); + w->writeC(1); + break; + case DIV_INS_STD: + // we can't tell between SMS and NES ins type yet + w->writeC(0x03); + w->writeC(0); + break; + case DIV_INS_GB: + w->writeC(0x04); + w->writeC(0); + break; + case DIV_INS_C64: + w->writeC(0x07); + w->writeC(0); + break; + case DIV_INS_PCE: + w->writeC(0x06); + w->writeC(0); + break; + case DIV_INS_OPLL: + // ??? + w->writeC(0x13); + w->writeC(1); + break; + case DIV_INS_OPZ: + // data will be lost + w->writeC(0x08); + w->writeC(1); + break; + case DIV_INS_FDS: + // ??? + w->writeC(0x06); + w->writeC(0); + break; + default: + // not supported by .dmp + w->finish(); + return false; + } + + if (type==DIV_INS_FM || type==DIV_INS_OPLL || type==DIV_INS_OPZ) { + w->writeC(fm.fms); + w->writeC(fm.fb); + w->writeC(fm.alg); + w->writeC(fm.ams); + + // TODO: OPLL params + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=fm.op[i]; + w->writeC(op.mult); + w->writeC(op.tl); + w->writeC(op.ar); + w->writeC(op.dr); + w->writeC(op.sl); + w->writeC(op.rr); + w->writeC(op.am); + w->writeC(op.rs); + w->writeC(op.dt|(op.dt2<<4)); + w->writeC(op.d2r); + w->writeC(op.ssgEnv); + } + } else { + if (type!=DIV_INS_GB) { + w->writeC(std.volMacro.len); + for (int i=0; iwriteI(std.volMacro.val[i]); + } + if (std.volMacro.len>0) w->writeC(std.volMacro.loop); + } + + w->writeC(std.arpMacro.len); + for (int i=0; iwriteI(std.arpMacro.val[i]+12); + } + if (std.arpMacro.len>0) w->writeC(std.arpMacro.loop); + w->writeC(std.arpMacro.mode); + + w->writeC(std.dutyMacro.len); + for (int i=0; iwriteI(std.dutyMacro.val[i]+12); + } + if (std.dutyMacro.len>0) w->writeC(std.dutyMacro.loop); + + w->writeC(std.waveMacro.len); + for (int i=0; iwriteI(std.waveMacro.val[i]+12); + } + if (std.waveMacro.len>0) w->writeC(std.waveMacro.loop); + + if (type==DIV_INS_C64) { + w->writeC(c64.triOn); + w->writeC(c64.sawOn); + w->writeC(c64.pulseOn); + w->writeC(c64.noiseOn); + w->writeC(c64.a); + w->writeC(c64.d); + w->writeC(c64.s); + w->writeC(c64.r); + w->writeC((c64.duty*100)/4095); + w->writeC(c64.ringMod); + w->writeC(c64.oscSync); + w->writeC(c64.toFilter); + w->writeC(c64.volIsCutoff); + w->writeC(c64.initFilter); + w->writeC(c64.res); + w->writeC((c64.cut*100)/2047); + w->writeC(c64.hp); + w->writeC(c64.lp); + w->writeC(c64.bp); + w->writeC(c64.ch3off); + } + if (type==DIV_INS_GB) { + w->writeC(gb.envVol); + w->writeC(gb.envDir); + w->writeC(gb.envLen); + w->writeC(gb.soundLen); + } + } + + FILE* outFile=ps_fopen(path,"wb"); + if (outFile==NULL) { + logE("could not save instrument: %s!",strerror(errno)); + w->finish(); + return false; + } + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write entire instrument!"); + } + fclose(outFile); + w->finish(); + return true; +} diff --git a/src/engine/instrument.h b/src/engine/instrument.h index df7a6b361..9f49a627a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -500,6 +500,13 @@ struct DivInstrument { * @return whether it was successful. */ bool save(const char* path); + + /** + * save this instrument to a file in .dmp format. + * @param path file path. + * @return whether it was successful. + */ + bool saveDMP(const char* path); DivInstrument(): name(""), type(DIV_INS_FM) { diff --git a/src/engine/wavetable.cpp b/src/engine/wavetable.cpp index 953400ee1..0b13497ef 100644 --- a/src/engine/wavetable.cpp +++ b/src/engine/wavetable.cpp @@ -92,3 +92,65 @@ bool DivWavetable::save(const char* path) { w->finish(); return true; } + +bool DivWavetable::saveDMW(const char* path) { + SafeWriter* w=new SafeWriter(); + w->init(); + + // write width + w->writeI(len); + + // check height + w->writeC(max); + if (max==255) { + // write as new format (because 0xff means that) + w->writeC(1); // format version + w->writeC(max); // actual height + + // waveform data + for (int i=0; iwriteI(data[i]&0xff); + } + } else { + // write as old format + for (int i=0; iwriteC(data[i]); + } + } + + FILE* outFile=ps_fopen(path,"wb"); + if (outFile==NULL) { + logE("could not save wavetable: %s!",strerror(errno)); + w->finish(); + return false; + } + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write entire wavetable!"); + } + fclose(outFile); + w->finish(); + return true; +} + +bool DivWavetable::saveRaw(const char* path) { + SafeWriter* w=new SafeWriter(); + w->init(); + + // waveform data + for (int i=0; iwriteC(data[i]); + } + + FILE* outFile=ps_fopen(path,"wb"); + if (outFile==NULL) { + logE("could not save wavetable: %s!",strerror(errno)); + w->finish(); + return false; + } + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write entire wavetable!"); + } + fclose(outFile); + w->finish(); + return true; +} diff --git a/src/engine/wavetable.h b/src/engine/wavetable.h index 616b048cf..0f518ab53 100644 --- a/src/engine/wavetable.h +++ b/src/engine/wavetable.h @@ -46,6 +46,20 @@ struct DivWavetable { * @return whether it was successful. */ bool save(const char* path); + + /** + * save this wavetable to a file in .dmw format. + * @param path file path. + * @return whether it was successful. + */ + bool saveDMW(const char* path); + + /** + * save this wavetable to a file in raw format. + * @param path file path. + * @return whether it was successful. + */ + bool saveRaw(const char* path); DivWavetable(): len(32), min(0), @@ -56,4 +70,4 @@ struct DivWavetable { } }; -#endif \ No newline at end of file +#endif diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0ed0ffbe1..f065dfe34 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1313,8 +1313,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSave( "Save Instrument", - {"Furnace instrument", "*.fui"}, - "Furnace instrument{.fui}", + {"Furnace instrument", "*.fui", + "DefleMask preset", "*.dmp"}, + "Furnace instrument{.fui},DefleMask preset{.dmp}", workingDirIns, dpiScale ); @@ -1335,8 +1336,10 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSave( "Save Wavetable", - {"Furnace wavetable", ".fuw"}, - "Furnace wavetable{.fuw}", + {"Furnace wavetable", ".fuw", + "DefleMask wavetable", ".dmw", + "raw data", ".raw"}, + "Furnace wavetable{.fuw},DefleMask wavetable{.dmw},raw data{.raw}", workingDirWave, dpiScale ); @@ -1921,6 +1924,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { fileName+=fallback; \ } +#define checkExtensionTriple(x,y,z,fallback) \ + String lowerCase=fileName; \ + for (char& i: lowerCase) { \ + if (i>='A' && i<='Z') i+='a'-'A'; \ + } \ + if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4 && lowerCase.rfind(z)!=lowerCase.size()-4)) { \ + fileName+=fallback; \ + } + #define drawOpMask(m) \ ImGui::PushFont(patFont); \ ImGui::PushID("om_" #m); \ @@ -3365,10 +3377,21 @@ bool FurnaceGUI::loop() { checkExtension(".wav"); } if (curFileDialog==GUI_FILE_INS_SAVE) { - checkExtension(".fui"); + // we can't tell whether the user chose .fui or .dmp in the system file picker + const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace instrument")?".fui":".dmp"; + checkExtensionDual(".fui",".dmp",fallbackExt); } if (curFileDialog==GUI_FILE_WAVE_SAVE) { - checkExtension(".fuw"); + // same thing here + const char* fallbackExt=".fuw"; + if (!settings.sysFileDialog) { + if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="raw data") { + fallbackExt=".raw"; + } else if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="DefleMask wavetable") { + fallbackExt=".dmw"; + } + } + checkExtensionTriple(".fuw",".dmw",".raw",fallbackExt); } if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); @@ -3451,12 +3474,34 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_INS_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { - e->song.ins[curIns]->save(copyOfName.c_str()); + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + if ((lowerCase.size()<4 || lowerCase.rfind(".dmp")!=lowerCase.size()-4)) { + e->song.ins[curIns]->save(copyOfName.c_str()); + } else { + if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { + showError("error while saving instrument! make sure your instrument is compatible."); + } + } } break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { - e->song.wave[curWave]->save(copyOfName.c_str()); + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + if (lowerCase.size()<4) { + e->song.wave[curWave]->save(copyOfName.c_str()); + } else if (lowerCase.rfind(".dmw")==lowerCase.size()-4) { + e->song.wave[curWave]->saveDMW(copyOfName.c_str()); + } else if (lowerCase.rfind(".raw")==lowerCase.size()-4) { + e->song.wave[curWave]->saveRaw(copyOfName.c_str()); + } else { + e->song.wave[curWave]->save(copyOfName.c_str()); + } } break; case GUI_FILE_SAMPLE_OPEN: { From ee16d20047ee44ac606c9dd4e2908a77524590be Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 15:53:47 -0500 Subject: [PATCH 167/194] add demo song by brickblock369 --- demos/Egyptian_Rule.fur | Bin 0 -> 11774 bytes src/gui/about.cpp | 1 + src/gui/gui.cpp | 1 + 3 files changed, 2 insertions(+) create mode 100644 demos/Egyptian_Rule.fur diff --git a/demos/Egyptian_Rule.fur b/demos/Egyptian_Rule.fur new file mode 100644 index 0000000000000000000000000000000000000000..1715906ec3376d6ee757af34530ff6832ee34c02 GIT binary patch literal 11774 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 literal 0 HcmV?d00001 diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 587aba84d..ae0792aef 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -67,6 +67,7 @@ const char* aboutLine[]={ "AURORA*FIELDS", "BlueElectric05", "breakthetargets", + "brickblock369", "CaptainMalware", "DeMOSic", "DevEd", diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f065dfe34..add68d769 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1267,6 +1267,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { hasOpened=fileDialog->openLoad( "Load Instrument", // TODO supply loadable formats in a dynamic, scalable, "DRY" way. + // thank the author of IGFD for making things impossible {"all compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn", "Furnace instrument", "*.fui", "DefleMask preset", "*.dmp", From bb5cee4a662c3cd54fa4b3e4739cd55cc0186ab0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 18:00:29 -0500 Subject: [PATCH 168/194] GUI: add pattern label customization settings --- src/gui/editing.cpp | 2 +- src/gui/findReplace.cpp | 24 ++++++++++++------------ src/gui/gui.cpp | 20 ++++++++++++++++---- src/gui/gui.h | 19 ++++++++++++++++++- src/gui/pattern.cpp | 26 +++++++++++++------------- src/gui/settings.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/utfutils.cpp | 8 ++++---- src/utfutils.h | 2 ++ 8 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 9bd827951..21b1a72eb 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -24,7 +24,7 @@ #include "actionUtil.h" -const char* noteNameNormal(short note, short octave) { +const char* FurnaceGUI::noteNameNormal(short note, short octave) { if (note==100) { // note cut return "OFF"; } else if (note==101) { // note off and envelope release diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp index 559d7f7ff..428fce5a4 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -593,11 +593,11 @@ void FurnaceGUI::drawFindReplace() { i.note=0; } if (i.note==130) { - snprintf(tempID,1024,"REL"); + snprintf(tempID,1024,"%s##MREL",macroRelLabel); } else if (i.note==129) { - snprintf(tempID,1024,"==="); + snprintf(tempID,1024,"%s##NREL",noteRelLabel); } else if (i.note==128) { - snprintf(tempID,1024,"OFF"); + snprintf(tempID,1024,"%s##NOFF",noteOffLabel); } else if (i.note>=-60 && i.note<120) { snprintf(tempID,1024,"%s",noteNames[i.note+60]); } else { @@ -613,13 +613,13 @@ void FurnaceGUI::drawFindReplace() { } } if (i.noteMode!=GUI_QUERY_RANGE && i.noteMode!=GUI_QUERY_RANGE_NOT) { - if (ImGui::Selectable("OFF",i.note==128)) { + if (ImGui::Selectable(noteOffLabel,i.note==128)) { i.note=128; } - if (ImGui::Selectable("===",i.note==129)) { + if (ImGui::Selectable(noteRelLabel,i.note==129)) { i.note=129; } - if (ImGui::Selectable("REL",i.note==130)) { + if (ImGui::Selectable(macroRelLabel,i.note==130)) { i.note=130; } } @@ -916,11 +916,11 @@ void FurnaceGUI::drawFindReplace() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SET) { if (queryReplaceNote==130) { - snprintf(tempID,1024,"REL"); + snprintf(tempID,1024,"%s##MREL",macroRelLabel); } else if (queryReplaceNote==129) { - snprintf(tempID,1024,"==="); + snprintf(tempID,1024,"%s##NREL",noteRelLabel); } else if (queryReplaceNote==128) { - snprintf(tempID,1024,"OFF"); + snprintf(tempID,1024,"%s##NOFF",noteOffLabel); } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { snprintf(tempID,1024,"%s",noteNames[queryReplaceNote+60]); } else { @@ -934,13 +934,13 @@ void FurnaceGUI::drawFindReplace() { queryReplaceNote=j-60; } } - if (ImGui::Selectable("OFF",queryReplaceNote==128)) { + if (ImGui::Selectable(noteOffLabel,queryReplaceNote==128)) { queryReplaceNote=128; } - if (ImGui::Selectable("===",queryReplaceNote==129)) { + if (ImGui::Selectable(noteRelLabel,queryReplaceNote==129)) { queryReplaceNote=129; } - if (ImGui::Selectable("REL",queryReplaceNote==130)) { + if (ImGui::Selectable(macroRelLabel,queryReplaceNote==130)) { queryReplaceNote=130; } ImGui::EndCombo(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index add68d769..66649cbae 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -84,13 +84,13 @@ void FurnaceGUI::bindEngine(DivEngine* eng) { const char* FurnaceGUI::noteName(short note, short octave) { if (note==100) { - return "OFF"; + return noteOffLabel; } else if (note==101) { // note off and envelope release - return "==="; + return noteRelLabel; } else if (note==102) { // envelope release only - return "REL"; + return macroRelLabel; } else if (octave==0 && note==0) { - return "..."; + return emptyLabel; } else if (note==0 && octave!=0) { return "BUG"; } @@ -5067,4 +5067,16 @@ FurnaceGUI::FurnaceGUI(): memset(queryReplaceEffectValDo,0,sizeof(bool)*8); chanOscGrad.bgColor=ImVec4(0.0f,0.0f,0.0f,1.0f); + + memset(noteOffLabel,0,32); + memset(noteRelLabel,0,32); + memset(macroRelLabel,0,32); + memset(emptyLabel,0,32); + memset(emptyLabel2,0,32); + + strncat(noteOffLabel,"OFF",32); + strncat(noteRelLabel,"===",32); + strncat(macroRelLabel,"REL",32); + strncat(emptyLabel,"...",32); + strncat(emptyLabel2,"..",32); } diff --git a/src/gui/gui.h b/src/gui/gui.h index ff6dd93bb..1c7355060 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1017,6 +1017,12 @@ class FurnaceGUI { ImU32 sysCmd1Grad[256]; ImU32 sysCmd2Grad[256]; + char noteOffLabel[32]; + char noteRelLabel[32]; + char macroRelLabel[32]; + char emptyLabel[32]; + char emptyLabel2[32]; + struct Settings { int mainFontSize, patFontSize, iconSize; int audioEngine; @@ -1119,6 +1125,11 @@ class FurnaceGUI { String midiOutDevice; String c163Name; String initialSysName; + String noteOffLabel; + String noteRelLabel; + String macroRelLabel; + String emptyLabel; + String emptyLabel2; std::vector initialSys; Settings(): @@ -1224,7 +1235,12 @@ class FurnaceGUI { midiInDevice(""), midiOutDevice(""), c163Name(""), - initialSysName("Sega Genesis/Mega Drive") {} + initialSysName("Sega Genesis/Mega Drive"), + noteOffLabel("OFF"), + noteRelLabel("==="), + macroRelLabel("REL"), + emptyLabel("..."), + emptyLabel2("..") {} } settings; char finalLayoutPath[4096]; @@ -1657,6 +1673,7 @@ class FurnaceGUI { public: void showWarning(String what, FurnaceGUIWarnings type); void showError(String what); + const char* noteNameNormal(short note, short octave); const char* noteName(short note, short octave); bool decodeNote(const char* what, short& note, short& octave); void bindEngine(DivEngine* eng); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index b7940a9e8..153944797 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -56,7 +56,7 @@ void FurnaceGUI::popPartBlend() { // draw a pattern row inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel) { - static char id[32]; + static char id[64]; bool selectedRow=(i>=sel1.y && i<=sel2.y && !inhibitSel); ImGui::TableNextRow(0,lineHeight); ImGui::TableNextColumn(); @@ -114,9 +114,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Text,rowIndexColor); if (settings.patRowsBase==1) { - snprintf(id,31," %.2X ##PR_%d",i,i); + snprintf(id,63," %.2X ##PR_%d",i,i); } else { - snprintf(id,31,"%3d ##PR_%d",i,i); + snprintf(id,63,"%3d ##PR_%d",i,i); } ImGui::Selectable(id,false,ImGuiSelectableFlags_NoPadWithHalfSpacing,fourChars); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { @@ -151,7 +151,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2 && curWindowLast==GUI_WINDOW_PATTERN); // note - sprintf(id,"%s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j); + snprintf(id,63,"%.31s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j); if (pat->data[i][0]==0 && pat->data[i][1]==0) { ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { @@ -182,7 +182,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // instrument if (pat->data[i][2]==-1) { ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); - sprintf(id,"..##PI_%d_%d",i,j); + snprintf(id,63,"%.31s##PI_%d_%d",emptyLabel2,i,j); } else { if (pat->data[i][2]<0 || pat->data[i][2]>=e->song.insLen) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS_ERROR]); @@ -194,7 +194,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); } } - sprintf(id,"%.2X##PI_%d_%d",pat->data[i][2],i,j); + snprintf(id,63,"%.2X##PI_%d_%d",pat->data[i][2],i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorIns) { @@ -221,13 +221,13 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int if (e->curSubSong->chanCollapse[j]<2) { // volume if (pat->data[i][3]==-1) { - sprintf(id,"..##PV_%d_%d",i,j); + snprintf(id,63,"%.31s##PV_%d_%d",emptyLabel2,i,j); ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { int volColor=(pat->data[i][3]*127)/chanVolMax; if (volColor>127) volColor=127; if (volColor<0) volColor=0; - sprintf(id,"%.2X##PV_%d_%d",pat->data[i][3],i,j); + snprintf(id,63,"%.2X##PV_%d_%d",pat->data[i][3],i,j); ImGui::PushStyleColor(ImGuiCol_Text,volColors[volColor]); } ImGui::SameLine(0.0f,0.0f); @@ -263,15 +263,15 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect if (pat->data[i][index]==-1) { - sprintf(id,"..##PE%d_%d_%d",k,i,j); + snprintf(id,63,"%.31s##PE%d_%d_%d",emptyLabel2,k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { if (pat->data[i][index]>0xff) { - sprintf(id,"??##PE%d_%d_%d",k,i,j); + snprintf(id,63,"??##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); } else { const unsigned char data=pat->data[i][index]; - sprintf(id,"%.2X##PE%d_%d_%d",data,k,i,j); + snprintf(id,63,"%.2X##PE%d_%d_%d",data,k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } } @@ -297,9 +297,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect value if (pat->data[i][index+1]==-1) { - sprintf(id,"..##PF%d_%d_%d",k,i,j); + snprintf(id,63,"%.31s##PF%d_%d_%d",emptyLabel2,k,i,j); } else { - sprintf(id,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j); + snprintf(id,63,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorEffectVal) { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 37d4bfecf..6c300d0a4 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -21,6 +21,7 @@ #include "fonts.h" #include "../ta-log.h" #include "../fileutils.h" +#include "../utfutils.h" #include "util.h" #include "guiConst.h" #include "intConst.h" @@ -1081,6 +1082,15 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + ImGui::Text("Pattern view labels:"); + ImGui::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel); + ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel); + ImGui::InputTextWithHint("Macro release (3-char)","REL",&settings.macroRelLabel); + ImGui::InputTextWithHint("Empty field (3-char)","...",&settings.emptyLabel); + ImGui::InputTextWithHint("Empty field (2-char)","..",&settings.emptyLabel2); + + ImGui::Separator(); + ImGui::Text("Orders row number format:"); if (ImGui::RadioButton("Decimal##orbD",settings.orderRowsBase==0)) { settings.orderRowsBase=0; @@ -2118,6 +2128,11 @@ void FurnaceGUI::syncSettings() { settings.noThreadedInput=e->getConfInt("noThreadedInput",0); settings.initialSysName=e->getConfString("initialSysName",""); settings.clampSamples=e->getConfInt("clampSamples",0); + settings.noteOffLabel=e->getConfString("noteOffLabel","OFF"); + settings.noteRelLabel=e->getConfString("noteRelLabel","==="); + settings.macroRelLabel=e->getConfString("macroRelLabel","REL"); + settings.emptyLabel=e->getConfString("emptyLabel","..."); + settings.emptyLabel2=e->getConfString("emptyLabel2",".."); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2345,6 +2360,11 @@ void FurnaceGUI::commitSettings() { e->setConf("unsignedDetune",settings.unsignedDetune); e->setConf("noThreadedInput",settings.noThreadedInput); e->setConf("clampSamples",settings.clampSamples); + e->setConf("noteOffLabel",settings.noteOffLabel); + e->setConf("noteRelLabel",settings.noteRelLabel); + e->setConf("macroRelLabel",settings.macroRelLabel); + e->setConf("emptyLabel",settings.emptyLabel); + e->setConf("emptyLabel2",settings.emptyLabel2); // colors for (int i=0; i=0.5f) dpiScale=settings.dpiScale; // colors diff --git a/src/utfutils.cpp b/src/utfutils.cpp index c99528986..4c727777b 100644 --- a/src/utfutils.cpp +++ b/src/utfutils.cpp @@ -19,7 +19,7 @@ #include "utfutils.h" -int decodeUTF8(const unsigned char* data, char& len) { +int decodeUTF8(const unsigned char* data, signed char& len) { int ret=0xfffd; if (data[0]<0x80) { ret=data[0]; @@ -66,7 +66,7 @@ int decodeUTF8(const unsigned char* data, char& len) { size_t utf8len(const char* s) { size_t p=0; size_t r=0; - char cl; + signed char cl; while (s[p]!=0) { r++; decodeUTF8((const unsigned char*)&s[p],cl); @@ -76,7 +76,7 @@ size_t utf8len(const char* s) { } char utf8csize(const unsigned char* c) { - char ret; + signed char ret; decodeUTF8(c,ret); return ret; } @@ -84,7 +84,7 @@ char utf8csize(const unsigned char* c) { WString utf8To16(const char* s) { WString ret; int ch, p; - char chs; + signed char chs; p=0; while (s[p]!=0) { ch=decodeUTF8((const unsigned char*)&s[p],chs); diff --git a/src/utfutils.h b/src/utfutils.h index 087913a43..76c894708 100644 --- a/src/utfutils.h +++ b/src/utfutils.h @@ -21,6 +21,8 @@ #define _UTFUTILS_H #include "ta-utils.h" +int decodeUTF8(const unsigned char* data, signed char& len); + size_t utf8len(const char* s); size_t utf8clen(const char* s); size_t utf8pos(const char* s, size_t inpos); From 774a949ccaebbbd7ddc82f46abf168efb2e203ed Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Aug 2022 18:16:24 -0500 Subject: [PATCH 169/194] GUI: fix labels being empty --- src/gui/gui.cpp | 10 +++++----- src/gui/settings.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 66649cbae..2b4367c91 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5074,9 +5074,9 @@ FurnaceGUI::FurnaceGUI(): memset(emptyLabel,0,32); memset(emptyLabel2,0,32); - strncat(noteOffLabel,"OFF",32); - strncat(noteRelLabel,"===",32); - strncat(macroRelLabel,"REL",32); - strncat(emptyLabel,"...",32); - strncat(emptyLabel2,"..",32); + strncpy(noteOffLabel,"OFF",32); + strncpy(noteRelLabel,"===",32); + strncpy(macroRelLabel,"REL",32); + strncpy(emptyLabel,"...",32); + strncpy(emptyLabel2,"..",32); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 6c300d0a4..ee35fd86c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2781,7 +2781,7 @@ void setupLabel(const char* lStr, char* label, int len) { memset(label,0,32); for (int i=0, p=0; i Date: Sun, 14 Aug 2022 16:09:31 +1000 Subject: [PATCH 170/194] Reported bug - 2nd 2op pair did not read ALG and FB registers to instrument patch --- src/engine/fileOpsIns.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 54e0eefc6..62c3a1d8a 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -733,6 +733,8 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ins = new DivInstrument; ins->type = DIV_INS_OPL; ins->name = fmt::sprintf("%s (2)", insName); + ins->fm.alg = (feedConnect2nd & 0x1); + ins->fm.fb = ((feedConnect2nd >> 1) & 0xF); for (int i : {1,0}) { readOpliOp(reader, ins->fm.op[i]); } @@ -1498,6 +1500,8 @@ void DivEngine::loadWOPL(SafeReader& reader, std::vector& ret, S ins = new DivInstrument; ins->type = DIV_INS_OPL; ins->name = fmt::sprintf("%s (2)", insName); + ins->fm.alg = (feedConnect2nd & 0x1); + ins->fm.fb = ((feedConnect2nd >> 1) & 0xF); for (int i : {1,0}) { patchSum += readWoplOp(reader, ins->fm.op[i]); } From 1b10c547e38daebb1951c7fa2eadeead6d15c069 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 14 Aug 2022 14:19:30 -0500 Subject: [PATCH 171/194] SoundUnit: implement switch roles flag --- src/engine/platform/su.cpp | 48 ++++++++++++++++++++++++++++---------- src/engine/platform/su.h | 4 +++- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 6c6b0b329..ef06e4bf6 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -26,6 +26,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(((c)<<5)|(a),v); +#define CHIP_DIVIDER 8 #define CHIP_FREQBASE 524288 const char** DivPlatformSoundUnit::getRegisterSheet() { @@ -98,6 +99,13 @@ const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) { return NULL; } +double DivPlatformSoundUnit::NOTE_SU(int ch, int note) { + if (chan[ch].switchRoles) { + return NOTE_PERIODIC(note); + } + return NOTE_FREQUENCY(note); +} + void DivPlatformSoundUnit::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; h0); - chWrite(i,0x1e,chan[i].syncTimer&0xff); - chWrite(i,0x1f,chan[i].syncTimer>>8); + if (chan[i].switchRoles) { + chWrite(i,0x00,chan[i].syncTimer&0xff); + chWrite(i,0x01,chan[i].syncTimer>>8); + } else { + chWrite(i,0x1e,chan[i].syncTimer&0xff); + chWrite(i,0x1f,chan[i].syncTimer>>8); + } writeControlUpper(i); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE); if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); // TODO: sample map? @@ -213,8 +226,13 @@ void DivPlatformSoundUnit::tick(bool sysTick) { } if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; - chWrite(i,0x00,chan[i].freq&0xff); - chWrite(i,0x01,chan[i].freq>>8); + if (chan[i].switchRoles) { + chWrite(i,0x1e,chan[i].freq&0xff); + chWrite(i,0x1f,chan[i].freq>>8); + } else { + chWrite(i,0x00,chan[i].freq&0xff); + chWrite(i,0x01,chan[i].freq>>8); + } if (chan[i].keyOn) { if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); @@ -256,6 +274,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU); + chan[c.chan].switchRoles=ins->su.switchRoles; if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) { chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample); writeControl(c.chan); @@ -263,7 +282,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { } chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample); if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } @@ -421,7 +440,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { } break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + int destFreq=NOTE_SU(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); @@ -453,7 +472,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { chan[c.chan].keyOn=true; break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; @@ -461,7 +480,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SU)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_SU(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -485,6 +504,11 @@ void DivPlatformSoundUnit::forceIns() { for (int i=0; i<8; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; + + // restore channel attributes + chWrite(i,0x03,chan[i].pan); + writeControl(i); + writeControlUpper(i); } } diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index 9972599d4..2392624f6 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -31,7 +31,7 @@ class DivPlatformSoundUnit: public DivDispatch { int ins, cutoff, baseCutoff, res, control, hasOffset; signed char pan; unsigned char duty; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset, switchRoles; bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep; unsigned short freqSweepP, volSweepP, cutSweepP; unsigned char freqSweepB, volSweepB, cutSweepB; @@ -67,6 +67,7 @@ class DivPlatformSoundUnit: public DivDispatch { pcm(false), phaseReset(false), filterPhaseReset(false), + switchRoles(false), pcmLoop(false), timerSync(false), freqSweep(false), @@ -108,6 +109,7 @@ class DivPlatformSoundUnit: public DivDispatch { SoundUnit* su; size_t sampleMemLen; unsigned char regPool[128]; + double NOTE_SU(int ch, int note); void writeControl(int ch); void writeControlUpper(int ch); From cb4417824d383a9041d3476317d52ba915f6de0c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 14 Aug 2022 14:26:55 -0500 Subject: [PATCH 172/194] YMZ280B: restore panning in forceIns --- src/engine/platform/ymz280b.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index b690d30cb..0543a815c 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -332,6 +332,8 @@ void DivPlatformYMZ280B::forceIns() { chan[i].insChanged=true; chan[i].freqChanged=true; chan[i].sample=-1; + + rWrite(0x03+i*4,chan[i].panning); } } From 9b6730607e2ae72ba5f32614d663badb90445fdd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 14 Aug 2022 19:17:03 -0500 Subject: [PATCH 173/194] OPL: finally fix the carnival night zone bug TODO: RUN TEST SUITE! --- src/engine/platform/opl.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index a32777c33..8b76db213 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -872,6 +872,13 @@ int DivPlatformOPL::dispatch(DivCommand c) { int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; chan[c.chan].fourOp=(ops==4); if (chan[c.chan].fourOp) { + /* + if (chan[c.chan+1].active) { + chan[c.chan+1].keyOff=true; + chan[c.chan+1].keyOn=false; + chan[c.chan+1].active=false; + }*/ + chan[c.chan+1].insChanged=true; chan[c.chan+1].macroInit(NULL); } update4OpMask=true; From f8b3c089a4386c8f40f7f80670956cf72edb919c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 14 Aug 2022 23:07:19 -0500 Subject: [PATCH 174/194] Game Boy: fix volume column --- src/engine/platform/gb.cpp | 11 ++++++++++- src/engine/platform/gb.h | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index a7b310935..9680d0ff9 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -373,6 +373,8 @@ void DivPlatformGB::tick(bool sysTick) { chan[i].killIt=false; } } + + chan[i].soManyHacksToMakeItDefleCompatible=false; } } @@ -409,10 +411,16 @@ int DivPlatformGB::dispatch(DivCommand c) { ws.init(ins,32,15,chan[c.chan].insChanged); } if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) { - chan[c.chan].envVol=ins->gb.envVol; + if (!chan[c.chan].soManyHacksToMakeItDefleCompatible) { + chan[c.chan].envVol=ins->gb.envVol; + } chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envDir=ins->gb.envDir; chan[c.chan].soundLen=ins->gb.soundLen; + if (!chan[c.chan].soManyHacksToMakeItDefleCompatible) { + chan[c.chan].vol=chan[c.chan].envVol; + chan[c.chan].outVol=chan[c.chan].envVol; + } } if (c.chan==2 && chan[c.chan].softEnv) { chan[c.chan].soundLen=64; @@ -460,6 +468,7 @@ int DivPlatformGB::dispatch(DivCommand c) { } if (!chan[c.chan].softEnv) { chan[c.chan].envVol=chan[c.chan].vol; + chan[c.chan].soManyHacksToMakeItDefleCompatible=true; } else if (c.chan!=2) { chan[c.chan].envVol=chan[c.chan].vol; if (!chan[c.chan].keyOn) chan[c.chan].killIt=true; diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 9498317bd..00dcca036 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -31,6 +31,7 @@ class DivPlatformGB: public DivDispatch { int freq, baseFreq, pitch, pitch2, note, ins; unsigned char duty, sweep; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt; + bool soManyHacksToMakeItDefleCompatible; signed char vol, outVol, wave, lastKill; unsigned char envVol, envDir, envLen, soundLen; unsigned short hwSeqPos; @@ -59,6 +60,7 @@ class DivPlatformGB: public DivDispatch { released(false), softEnv(false), killIt(false), + soManyHacksToMakeItDefleCompatible(false), vol(15), outVol(15), wave(-1), From 23276211f5dfb037e1d487e696324e8b473b93dc Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 15 Aug 2022 13:25:31 +0900 Subject: [PATCH 175/194] Fix incorrect info on ES5506 --- src/engine/sysDef.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c5541baa3..73964089a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1840,7 +1840,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false, - "a sample chip used in the Gravis Ultrasound, popular in the PC (DOS) demoscene.", + "a sample chip used in the Ensoniq's unique transwave synthesizers, \nand SoundScape series PC ISA soundcards...\nthat's just yet another (partially)SB compatible one with emulated OPL3 and MIDI ROMpler.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, From a8d3803efcb98243f44cf493ccb29cc15b2ffbb6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 14 Aug 2022 23:28:15 -0500 Subject: [PATCH 176/194] Game Boy: now fix wave channel volume column --- src/engine/platform/gb.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 9680d0ff9..27d2ff839 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -84,7 +84,7 @@ const char* DivPlatformGB::getEffectName(unsigned char effect) { void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; igb.alwaysInit) && !chan[c.chan].softEnv) { - if (!chan[c.chan].soManyHacksToMakeItDefleCompatible) { + if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { chan[c.chan].envVol=ins->gb.envVol; } chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envDir=ins->gb.envDir; chan[c.chan].soundLen=ins->gb.soundLen; - if (!chan[c.chan].soManyHacksToMakeItDefleCompatible) { + if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { chan[c.chan].vol=chan[c.chan].envVol; chan[c.chan].outVol=chan[c.chan].envVol; } From a34c9806cb847ff6ec91a186095b52cbf63680c9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 14 Aug 2022 23:30:36 -0500 Subject: [PATCH 177/194] Game Boy: whoops --- src/engine/platform/gb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 27d2ff839..b6a00c0f1 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -84,7 +84,7 @@ const char* DivPlatformGB::getEffectName(unsigned char effect) { void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i Date: Sun, 14 Aug 2022 23:34:57 -0500 Subject: [PATCH 178/194] fix system definition for ES5506 - again --- src/engine/sysDef.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 73964089a..e3c356cf7 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1840,7 +1840,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false, - "a sample chip used in the Ensoniq's unique transwave synthesizers, \nand SoundScape series PC ISA soundcards...\nthat's just yet another (partially)SB compatible one with emulated OPL3 and MIDI ROMpler.", + "a sample chip used in the Ensoniq's unique TransWave synthesizers, and SoundScape series PC ISA soundcards (which are yet another (partially) Sound Blaster compatible ones with emulated OPL3 and MIDI ROMpler).", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, From 15b42945323fa3f591f48c95dec8003b0ef77111 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 02:01:08 -0500 Subject: [PATCH 179/194] FC loader: fix wave, sample and freq seq loading --- src/engine/fileOps.cpp | 64 +++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index db2305c65..c5084252d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2272,8 +2272,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr, wavePtr; unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen; - unsigned char waveLen[40]; - unsigned char waveLoopLen[40]; + unsigned char waveLen[80]; + //unsigned char waveLoopLen[40]; struct FCSequence { unsigned char pat[4]; @@ -2363,24 +2363,24 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { wavePtr=0; } - logD("patPtr: %d",patPtr); + logD("patPtr: %x",patPtr); logD("patLen: %d",patLen); - logD("freqMacroPtr: %d",freqMacroPtr); + logD("freqMacroPtr: %x",freqMacroPtr); logD("freqMacroLen: %d",freqMacroLen); - logD("volMacroPtr: %d",volMacroPtr); + logD("volMacroPtr: %x",volMacroPtr); logD("volMacroLen: %d",volMacroLen); - logD("samplePtr: %d",samplePtr); + logD("samplePtr: %x",samplePtr); if (isFC14) { - logD("wavePtr: %d",wavePtr); + logD("wavePtr: %x",wavePtr); } else { logD("sampleLen: %d",sampleLen); } // sample info - logD("samples:"); + logD("samples: (%x)",reader.tell()); for (int i=0; i<10; i++) { - sample[i].loopLen=reader.readS_BE(); sample[i].len=reader.readS_BE(); + sample[i].loopLen=reader.readS_BE(); sample[i].loopStart=reader.readS_BE(); logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen); @@ -2389,11 +2389,10 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { // wavetable lengths if (isFC14) { logD("wavetables:"); - for (int i=0; i<40; i++) { - waveLen[i]=reader.readC(); - waveLoopLen[i]=reader.readC(); + for (int i=0; i<80; i++) { + waveLen[i]=(unsigned char)reader.readC(); - logD("- %d: %.4x (%.4x)",i,waveLen[i],waveLoopLen[i]); + logD("- %d: %.4x",i,waveLen[i]); } } @@ -2482,11 +2481,12 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { DivSample* s=new DivSample; s->depth=DIV_SAMPLE_DEPTH_8BIT; if (sample[i].len>0) { - s->init(sample[i].len); + s->init(sample[i].len*2); } + s->name=fmt::sprintf("Sample %d",i+1); s->loopStart=sample[i].loopStart*2; s->loopEnd=(sample[i].loopStart+sample[i].loopLen)*2; - reader.read(s->data8,sample[i].len); + reader.read(s->data8,sample[i].len*2); ds.sample.push_back(s); } ds.sampleLen=(int)ds.sample.size(); @@ -2500,11 +2500,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { return false; } logD("reading wavetables..."); - for (int i=0; i<40; i++) { + for (int i=0; i<80; i++) { DivWavetable* w=new DivWavetable; w->min=0; w->max=255; - w->len=MIN(256,waveLoopLen[i]*2); + w->len=MIN(256,waveLen[i]*2); for (int i=0; i<256; i++) { w->data[i]=128; @@ -2513,13 +2513,14 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { if (waveLen[i]>0) { signed char* waveArray=new signed char[waveLen[i]*2]; reader.read(waveArray,waveLen[i]*2); - int howMany=MIN(waveLen[i]*2,waveLoopLen[i]*2); + int howMany=waveLen[i]*2; if (howMany>256) howMany=256; for (int i=0; idata[i]=waveArray[i]+128; } delete[] waveArray; } else { + logV("empty wave %d",i); w->len=32; for (int i=0; i<32; i++) { w->data[i]=(i*255)/31; @@ -2600,9 +2601,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } } else { - p->data[k][2]=fp.val[k]&0x3f; + p->data[k][2]=(fp.val[k]+seq[i].offsetIns[j])&0x3f; } } + } else if (fp.note[k]>0) { + p->data[k][2]=seq[i].offsetIns[j]; } } } @@ -2627,6 +2630,9 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { signed char loopMap[64]; memset(loopMap,-1,64); + signed char loopMapFreq[64]; + memset(loopMapFreq,-1,64); + // volume sequence ins->std.volMacro.len=0; for (int j=5; j<64; j++) { @@ -2678,7 +2684,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { if (freqMacrostd.arpMacro.len; if (fm.val[j]==0xe1) { + if (ins->std.arpMacro.mode) { + ins->std.arpMacro.loop=(signed int)ins->std.arpMacro.len-1; + } break; } else if (fm.val[j]==0xe2 || fm.val[j]==0xe4) { if (++j>=64) break; @@ -2692,11 +2702,12 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ins->std.waveMacro.val[ins->std.waveMacro.len]=wave-10; ins->std.waveMacro.open=true; lastVal=wave; - if (++ins->std.waveMacro.len>=128) break; if (++ins->std.arpMacro.len>=128) break; } } else if (fm.val[j]==0xe0) { - logV("unhandled loop!"); + if (++j>=64) break; + ins->std.arpMacro.loop=loopMapFreq[fm.val[j]&63]; + break; } else if (fm.val[j]==0xe3) { logV("unhandled vibrato!"); } else if (fm.val[j]==0xe8) { @@ -2708,9 +2719,18 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } else if (fm.val[j]==0xea) { logV("unhandled pitch!"); } else { - ins->std.arpMacro.val[ins->std.arpMacro.len]=(signed char)fm.val[j]; + if (fm.val[j]>0x80) { + ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j]-0x80+24; + ins->std.arpMacro.mode=1; // TODO: variable fixed/relative mode + } else { + ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j]; + } + if (lastVal>=10) { + ins->std.waveMacro.val[ins->std.waveMacro.len]=lastVal-10; + } ins->std.arpMacro.open=true; if (++ins->std.arpMacro.len>=128) break; + if (++ins->std.waveMacro.len>=128) break; } } } From 4663534fa3565a49ab8c3acf6098d0dd06efa9ea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 03:18:54 -0500 Subject: [PATCH 180/194] FC loader: preset waveforms, vibrato and stuff --- src/engine/fileOps.cpp | 189 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 175 insertions(+), 14 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index c5084252d..2ac810c69 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2262,6 +2262,95 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { return success; } +unsigned char fcXORTriangle[32]={ + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8 +}; + +unsigned char fcCustom1[32]={ + 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4d, 0x2c, 0x20, 0x18, 0x12, + 0x04, 0xdb, 0xd3, 0xcd, 0xc6, 0xbc, 0xb5, 0xae, 0xa8, 0xa3, 0x9d, 0x99, 0x93, 0x8e, 0x8b, 0x8a +}; + +unsigned char fcCustom2[32]={ + 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x5b, 0x4b, 0x43, 0x37, 0x2c, 0x20, 0x18, 0x12, + 0x04, 0xf8, 0xe8, 0xdb, 0xcf, 0xc6, 0xbe, 0xb0, 0xa8, 0xa4, 0x9e, 0x9a, 0x95, 0x94, 0x8d, 0x83 +}; + +unsigned char fcTinyTriangle[16]={ + 0x00, 0x00, 0x40, 0x60, 0x7f, 0x60, 0x40, 0x20, 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0xa0, 0xc0, 0xe0 +}; + +void generateFCPresetWave(int index, DivWavetable* wave) { + wave->max=255; + wave->len=32; + + switch (index) { + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04: case 0x05: case 0x06: case 0x07: + case 0x08: case 0x09: case 0x0a: case 0x0b: + case 0x0c: case 0x0d: case 0x0e: case 0x0f: + // XOR triangle + for (int i=0; i<32; i++) { + wave->data[i]=(unsigned char)((fcXORTriangle[i]^0x80)^(((index+15)data[i]=(index>i)?0x01:0xff; + } + break; + case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24: case 0x25: case 0x26: case 0x27: + // tiny pulse + for (int i=0; i<32; i++) { + wave->data[i]=((index-0x18)>(i&15))?0x01:0xff; + } + break; + case 0x28: + case 0x2e: + // saw + for (int i=0; i<32; i++) { + wave->data[i]=i<<3; + } + break; + case 0x29: + case 0x2f: + // tiny saw + for (int i=0; i<32; i++) { + wave->data[i]=(i<<4)&0xff; + } + break; + case 0x2a: + // custom 1 + for (int i=0; i<32; i++) { + wave->data[i]=fcCustom1[i]^0x80; + } + break; + case 0x2b: + // custom 2 + for (int i=0; i<32; i++) { + wave->data[i]=fcCustom2[i]^0x80; + } + break; + case 0x2c: case 0x2d: + // tiny triangle + for (int i=0; i<32; i++) { + wave->data[i]=fcTinyTriangle[i&15]^0x80; + } + break; + default: + for (int i=0; i<32; i++) { + wave->data[i]=i; + } + break; + } +} + bool DivEngine::loadFC(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; @@ -2301,10 +2390,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_FC; - //ds.linearPitch=0; + ds.linearPitch=0; + ds.pitchMacroIsLinear=false; //ds.noSlidesOnFirstTick=true; //ds.rowResetsArpPos=true; - //ds.ignoreJumpAtEnd=false; + ds.ignoreJumpAtEnd=false; // load here if (!reader.seek(0,SEEK_SET)) { @@ -2521,12 +2611,16 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { delete[] waveArray; } else { logV("empty wave %d",i); - w->len=32; - for (int i=0; i<32; i++) { - w->data[i]=(i*255)/31; - } + generateFCPresetWave(i,w); } + ds.wave.push_back(w); + } + } else { + // generate preset waves + for (int i=0; i<48; i++) { + DivWavetable* w=new DivWavetable; + generateFCPresetWave(i,w); ds.wave.push_back(w); } } @@ -2542,6 +2636,14 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ds.subsong[0]->speed1=3; ds.subsong[0]->speed2=3; + int lastIns[4]; + int lastNote[4]; + signed char lastTranspose[4]; + + memset(lastIns,-1,4*sizeof(int)); + memset(lastNote,-1,4*sizeof(int)); + memset(lastTranspose,0,4); + for (unsigned int i=0; iorders.ord[j][i]=i; @@ -2559,6 +2661,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { for (int k=0; k<32; k++) { FCPattern& fp=pat[seq[i].pat[j]]; if (fp.note[k]>0 && fp.note[k]<0x49) { + lastNote[j]=fp.note[k]; short note=(fp.note[k]+seq[i].transpose[j])%12; short octave=2+((fp.note[k]+seq[i].transpose[j])/12); if (fp.note[k]>=0x3d) octave-=6; @@ -2574,6 +2677,27 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { p->data[k][4]=2; p->data[k][5]=0; } + } else if (fp.note[k]==0x49) { + if (k>0) { + p->data[k-1][4]=0x0d; + p->data[k-1][5]=0; + } + } else if (k==0 && lastTranspose[j]!=seq[i].transpose[j]) { + p->data[0][2]=lastIns[j]; + p->data[0][4]=0x03; + p->data[0][5]=0xff; + lastTranspose[j]=seq[i].transpose[j]; + + short note=(lastNote[j]+seq[i].transpose[j])%12; + short octave=2+((lastNote[j]+seq[i].transpose[j])/12); + if (lastNote[j]>=0x3d) octave-=6; + if (note==0) { + note=12; + octave--; + } + octave&=0xff; + p->data[k][0]=note; + p->data[k][1]=octave; } if (fp.val[k]) { if (ignoreNext) { @@ -2602,10 +2726,12 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } else { p->data[k][2]=(fp.val[k]+seq[i].offsetIns[j])&0x3f; + lastIns[j]=p->data[k][2]; } } - } else if (fp.note[k]>0) { + } else if (fp.note[k]>0 && fp.note[k]<0x49) { p->data[k][2]=seq[i].offsetIns[j]; + lastIns[j]=p->data[k][2]; } } } @@ -2619,11 +2745,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ins->type=DIV_INS_AMIGA; ins->name=fmt::sprintf("Instrument %d",i); ins->amiga.useWave=true; - //unsigned char seqSpeed=m.val[0]; + unsigned char seqSpeed=m.val[0]; unsigned char freqMacro=m.val[1]; - //unsigned char vibSpeed=m.val[2]; - //unsigned char vibDepth=m.val[3]; - //unsigned char vibDelay=m.val[4]; + unsigned char vibSpeed=m.val[2]; + unsigned char vibDepth=m.val[3]; + unsigned char vibDelay=m.val[4]; unsigned char lastVal=m.val[5]; @@ -2633,6 +2759,9 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { signed char loopMapFreq[64]; memset(loopMapFreq,-1,64); + signed char loopMapWave[64]; + memset(loopMapWave,-1,64); + // volume sequence ins->std.volMacro.len=0; for (int j=5; j<64; j++) { @@ -2672,9 +2801,13 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { if (++ins->std.volMacro.len>=128) break; } } else { - ins->std.volMacro.val[ins->std.volMacro.len]=m.val[j]; - lastVal=m.val[j]; - if (++ins->std.volMacro.len>=128) break; + // TODO: replace with upcoming macro speed + for (int k=0; kstd.volMacro.val[ins->std.volMacro.len]=m.val[j]; + lastVal=m.val[j]; + if (++ins->std.volMacro.len>=128) break; + } + if (ins->std.volMacro.len>=128) break; } } @@ -2685,6 +2818,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { FCMacro& fm=freqMacros[freqMacro]; for (int j=0; j<64; j++) { loopMapFreq[j]=ins->std.arpMacro.len; + loopMapWave[j]=ins->std.waveMacro.len; if (fm.val[j]==0xe1) { if (ins->std.arpMacro.mode) { ins->std.arpMacro.loop=(signed int)ins->std.arpMacro.len-1; @@ -2707,6 +2841,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } else if (fm.val[j]==0xe0) { if (++j>=64) break; ins->std.arpMacro.loop=loopMapFreq[fm.val[j]&63]; + ins->std.waveMacro.loop=loopMapWave[fm.val[j]&63]; break; } else if (fm.val[j]==0xe3) { logV("unhandled vibrato!"); @@ -2735,6 +2870,32 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } + // vibrato + for (int j=0; j<=vibDelay; j++) { + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=0; + if (++ins->std.pitchMacro.len>=128) break; + } + int vibPos=0; + ins->std.pitchMacro.loop=ins->std.pitchMacro.len; + do { + vibPos+=vibSpeed; + if (vibPos>vibDepth) vibPos=vibDepth; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*4; + if (++ins->std.pitchMacro.len>=128) break; + } while (vibPosstd.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*4; + if (++ins->std.pitchMacro.len>=128) break; + } while (vibPos>-vibDepth); + do { + vibPos+=vibSpeed; + if (vibPos>0) vibPos=0; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*4; + if (++ins->std.pitchMacro.len>=128) break; + } while (vibPos<0); + ds.ins.push_back(ins); } ds.insLen=(int)ds.ins.size(); From bef8cf5f5f374bdb811932523a180aa75274ac55 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 03:32:27 -0500 Subject: [PATCH 181/194] FC loader: sample loop point and more fixes --- src/engine/fileOps.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 2ac810c69..a9b0bcfc8 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2470,8 +2470,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { logD("samples: (%x)",reader.tell()); for (int i=0; i<10; i++) { sample[i].len=reader.readS_BE(); - sample[i].loopLen=reader.readS_BE(); sample[i].loopStart=reader.readS_BE(); + sample[i].loopLen=reader.readS_BE(); logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen); } @@ -2574,8 +2574,10 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { s->init(sample[i].len*2); } s->name=fmt::sprintf("Sample %d",i+1); - s->loopStart=sample[i].loopStart*2; - s->loopEnd=(sample[i].loopStart+sample[i].loopLen)*2; + if (sample[i].loopLen>1) { + s->loopStart=sample[i].loopStart; + s->loopEnd=sample[i].loopStart+(sample[i].loopLen*2); + } reader.read(s->data8,sample[i].len*2); ds.sample.push_back(s); } @@ -2703,7 +2705,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { if (ignoreNext) { ignoreNext=false; } else { - if (fp.val[k]&0xe0) { + if (fp.val[k]==0xf0) { + p->data[k][0]=100; + p->data[k][1]=0; + p->data[k][2]=-1; + } else if (fp.val[k]&0xe0) { if (fp.val[k]&0x40) { p->data[k][4]=2; p->data[k][5]=0; @@ -2780,6 +2786,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ins->std.volMacro.val[ins->std.volMacro.len]=lastVal; if (++ins->std.volMacro.len>=128) break; } + if (ins->std.volMacro.len>=128) break; } else if (m.val[j]==0xe9 || m.val[j]==0xea) { // volume slide if (++j>=64) break; signed char slideStep=m.val[j]; @@ -2870,6 +2877,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } + // waveform width + if (lastVal>=10 && (unsigned int)(lastVal-10)amiga.waveLen=ds.wave[lastVal-10]->len-1; + } + // vibrato for (int j=0; j<=vibDelay; j++) { ins->std.pitchMacro.val[ins->std.pitchMacro.len]=0; From 206b3af12a7e7aed2f814871fd8d1dc22a1b6188 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 03:40:04 -0500 Subject: [PATCH 182/194] FC loader: aaaaaaand more fixes --- src/engine/fileOps.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index a9b0bcfc8..4b7f78785 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2390,10 +2390,11 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_FC; - ds.linearPitch=0; - ds.pitchMacroIsLinear=false; + //ds.linearPitch=0; + //ds.pitchMacroIsLinear=false; //ds.noSlidesOnFirstTick=true; //ds.rowResetsArpPos=true; + ds.pitchSlideSpeed=8; ds.ignoreJumpAtEnd=false; // load here @@ -2843,7 +2844,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ins->std.waveMacro.val[ins->std.waveMacro.len]=wave-10; ins->std.waveMacro.open=true; lastVal=wave; - if (++ins->std.arpMacro.len>=128) break; + //if (++ins->std.arpMacro.len>=128) break; } } else if (fm.val[j]==0xe0) { if (++j>=64) break; @@ -2892,19 +2893,19 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { do { vibPos+=vibSpeed; if (vibPos>vibDepth) vibPos=vibDepth; - ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*4; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; if (++ins->std.pitchMacro.len>=128) break; } while (vibPosstd.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*4; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; if (++ins->std.pitchMacro.len>=128) break; } while (vibPos>-vibDepth); do { vibPos+=vibSpeed; if (vibPos>0) vibPos=0; - ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*4; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; if (++ins->std.pitchMacro.len>=128) break; } while (vibPos<0); From 20c5e14f266a4deaf1fd15864d3c8a511078a05a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 04:22:14 -0500 Subject: [PATCH 183/194] FC loader: the final fixes for this night --- src/engine/fileOps.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4b7f78785..845ee98bd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2642,10 +2642,12 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { int lastIns[4]; int lastNote[4]; signed char lastTranspose[4]; + bool isSliding[4]; memset(lastIns,-1,4*sizeof(int)); memset(lastNote,-1,4*sizeof(int)); memset(lastTranspose,0,4); + memset(isSliding,0,4*sizeof(bool)); for (unsigned int i=0; idata[k][0]=note; p->data[k][1]=octave; - if (isSliding) { - isSliding=false; + if (isSliding[j]) { + isSliding[j]=false; p->data[k][4]=2; p->data[k][5]=0; } @@ -2714,9 +2715,9 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { if (fp.val[k]&0x40) { p->data[k][4]=2; p->data[k][5]=0; - isSliding=false; + isSliding[j]=false; } else if (fp.val[k]&0x80) { - isSliding=true; + isSliding[j]=true; if (k<31) { if (fp.val[k+1]&0x20) { p->data[k][4]=2; @@ -2856,7 +2857,9 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } else if (fm.val[j]==0xe8) { logV("unhandled sustain!"); } else if (fm.val[j]==0xe7) { - logV("unhandled newseq!"); + if (++j>=64) break; + fm=freqMacros[MIN(fm.val[j],freqMacros.size()-1)]; + j=0; } else if (fm.val[j]==0xe9) { logV("unhandled pack!"); } else if (fm.val[j]==0xea) { From f86b66b4b759a908324182bf56e5ed9e76fce567 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 21:46:36 -0500 Subject: [PATCH 184/194] PET: fix missing pitch macro --- src/engine/platform/pet.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 493436271..dc524e45f 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -133,8 +133,14 @@ void DivPlatformPET::tick(bool sysTick) { } } if (chan.std.pitch.had) { - chan.freqChanged=true; + if (chan.std.pitch.mode) { + chan.pitch2+=chan.std.pitch.val; + CLAMP_VAR(chan.pitch2,-32768,32767); + } else { + chan.pitch2=chan.std.pitch.val; } + chan.freqChanged=true; + } if (chan.freqChanged || chan.keyOn || chan.keyOff) { chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2; if (chan.freq>65535) chan.freq=65535; From 474dfa2587c7bfa178a7b3c9199d84cd05cec449 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 22:36:26 -0500 Subject: [PATCH 185/194] Game Boy: fix bug involving hw sweep and zombie --- src/engine/platform/gb.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index b6a00c0f1..c29329b95 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -181,7 +181,6 @@ void DivPlatformGB::tick(bool sysTick) { chan[i].soundLen=64; if (!chan[i].keyOn) chan[i].killIt=true; - chan[i].freqChanged=true; } } } @@ -353,25 +352,24 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; chan[i].freqChanged=false; + } + if (chan[i].killIt) { + if (i!=2) { + //rWrite(16+i*5+2,8); + int killDelta=chan[i].lastKill-chan[i].outVol+1; + if (killDelta<0) killDelta+=16; + chan[i].lastKill=chan[i].outVol; - if (chan[i].killIt) { - if (i!=2) { - //rWrite(16+i*5+2,8); - int killDelta=chan[i].lastKill-chan[i].outVol+1; - if (killDelta<0) killDelta+=16; - chan[i].lastKill=chan[i].outVol; - - if (killDelta!=1) { - rWrite(16+i*5+2,((chan[i].envVol<<4))|8); - for (int j=0; j Date: Mon, 15 Aug 2022 22:40:04 -0500 Subject: [PATCH 186/194] allow rates down to 1Hz --- src/engine/playback.cpp | 6 +++--- src/gui/songInfo.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 3ba4f1417..3dbc6b5fb 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -604,7 +604,7 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: // set Hz divider=(double)(((effect&0x3)<<8)|effectVal); - if (divider<10) divider=10; + if (divider<1) divider=1; cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; clockDrift=0; subticks=0; @@ -695,7 +695,7 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xf0: // set Hz by tempo divider=(double)effectVal*2.0/5.0; - if (divider<10) divider=10; + if (divider<1) divider=1; cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; clockDrift=0; subticks=0; @@ -959,7 +959,7 @@ void DivEngine::nextRow() { bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { bool ret=false; - if (divider<10) divider=10; + if (divider<1) divider=1; if (lowLatency && !skipping && !inhibitLowLat) { tickMult=1000/divider; diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index 7f7e026ed..44982abaf 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -195,7 +195,7 @@ void FurnaceGUI::drawSongInfo() { float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz; if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED if (tempoView) setHz/=2.5; - if (setHz<10) setHz=10; + if (setHz<1) setHz=1; if (setHz>999) setHz=999; e->setSongRate(setHz,setHz<52); } From 51cc36532e223b4aefa82b8c74b1d21ac13d4e70 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 22:44:55 -0500 Subject: [PATCH 187/194] fix documentation regarding ZX beeper samples --- papers/doc/6-sample/README.md | 2 +- papers/doc/7-systems/zxbeep.md | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 035a58cc6..59613da27 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -48,7 +48,7 @@ due to limitations in some of those sound chips, some restrictions exist: - Neo Geo (ADPCM-B): no loop position supported (only entire sample), and the maximum frequency is ~55KHz. - YM2608: the maximum frequency is ~55KHz. - MSM6258/MSM6295: no arbitrary frequency. -- ZX Spectrum Beeper: your sample can't be longer than 2048. +- ZX Spectrum Beeper: your sample can't be longer than 2048, and it always plays at ~55KHz. - Seta/Allumer X1-010: frequency resolution is terrible in the lower end. your sample can't be longer than 131072. furthermore, many of these chips have a limited amount of sample memory. check memory usage in window > statistics. diff --git a/papers/doc/7-systems/zxbeep.md b/papers/doc/7-systems/zxbeep.md index 1c6cd74f3..9e25284bf 100644 --- a/papers/doc/7-systems/zxbeep.md +++ b/papers/doc/7-systems/zxbeep.md @@ -2,9 +2,12 @@ Rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. It's capabilities should be on par with an IBM PC speaker... right? -Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums. +Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, but as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums. # effects -- `12xx`: set pulse width -- `17xx`: trigger overlay drums. +- `12xx`: set pulse width. +- `17xx`: trigger overlay drum. + - `xx` is the sample number. + - overlay drums are 1-bit and always play at 55930Hz (NTSC) or 55420Hz (PAL). + - the maximum length is 2048! \ No newline at end of file From 77109c3832ab750f38ee088a8cb4768772e0eddf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 22:54:31 -0500 Subject: [PATCH 188/194] fix instrument move/del screwing up sub-songs --- src/engine/engine.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4c51f367f..68c492219 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2181,11 +2181,13 @@ void DivEngine::delInstrument(int index) { song.ins.erase(song.ins.begin()+index); song.insLen=song.ins.size(); for (int i=0; ipatLen; k++) { - if (curPat[i].data[j]->data[k][2]>index) { - curPat[i].data[j]->data[k][2]--; + for (size_t j=0; jpat[i].data[k]==NULL) continue; + for (int l=0; lpatLen; l++) { + if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) { + song.subsong[j]->pat[i].data[k]->data[l][2]--; + } } } } @@ -2986,13 +2988,15 @@ void DivEngine::moveOrderDown() { void DivEngine::exchangeIns(int one, int two) { for (int i=0; ipatLen; 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; + for (size_t j=0; jpat[i].data[k]==NULL) continue; + for (int l=0; lpatLen; l++) { + if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) { + song.subsong[j]->pat[i].data[k]->data[l][2]=two; + } else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) { + song.subsong[j]->pat[i].data[k]->data[l][2]=one; + } } } } From 8734005b2302492c4e7a1e3b1b086578245ebbd6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 15 Aug 2022 23:20:26 -0500 Subject: [PATCH 189/194] SoundUnit: fix switch roles mode --- src/engine/platform/su.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index ef06e4bf6..8cd6d3abc 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -26,7 +26,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(((c)<<5)|(a),v); -#define CHIP_DIVIDER 8 +#define CHIP_DIVIDER 2 #define CHIP_FREQBASE 524288 const char** DivPlatformSoundUnit::getRegisterSheet() { From fcb8fba77be923856a47d94b807b8764893070ef Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 16 Aug 2022 02:07:57 -0500 Subject: [PATCH 190/194] GUI: fix particle commands --- src/gui/pattern.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 153944797..254c53bed 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -701,6 +701,15 @@ void FurnaceGUI::drawPattern() { if (i.cmd==DIV_CMD_SAMPLE_BANK) continue; if (i.cmd==DIV_CMD_GET_VOLUME) continue; if (i.cmd==DIV_ALWAYS_SET_VOLUME) continue; + if (i.cmd==DIV_CMD_HINT_VOLUME || + i.cmd==DIV_CMD_HINT_PORTA || + i.cmd==DIV_CMD_HINT_LEGATO || + i.cmd==DIV_CMD_HINT_VOL_SLIDE || + i.cmd==DIV_CMD_HINT_ARPEGGIO || + i.cmd==DIV_CMD_HINT_PITCH || + i.cmd==DIV_CMD_HINT_VIBRATO || + i.cmd==DIV_CMD_HINT_VIBRATO_RANGE || + i.cmd==DIV_CMD_HINT_VIBRATO_SHAPE) continue; float width=patChanX[i.chan+1]-patChanX[i.chan]; float speedX=0.0f; From 976e1933091f078c73a333bacb0e97410cb9231d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 16 Aug 2022 02:08:10 -0500 Subject: [PATCH 191/194] SoundUnit: add 1-bit PDM rev emulation --- src/engine/platform/sound/su.cpp | 42 +++++++++++++++++++++++++++++--- src/engine/platform/sound/su.h | 4 ++- src/engine/platform/su.cpp | 2 +- src/gui/sysConf.cpp | 13 +++++++--- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp index 6637bfa7d..a87205457 100644 --- a/src/engine/platform/sound/su.cpp +++ b/src/engine/platform/sound/su.cpp @@ -300,12 +300,46 @@ void SoundUnit::NextSample(short* l, short* r) { } } - *l=minval(32767,maxval(-32767,tnsL)); - *r=minval(32767,maxval(-32767,tnsR)); + if (dsOut) { + tnsL=minval(32767,maxval(-32767,tnsL<<1)); + tnsR=minval(32767,maxval(-32767,tnsR<<1)); + + short accumL=0; + short accumR=0; + + for (int i=0; i<4; i++) { + if ((tnsL>>8)==0 && dsCounterL>0) dsCounterL=0; + dsCounterL+=tnsL>>8; + if (dsCounterL>=0) { + accumL+=4095; + dsCounterL-=127; + } else { + accumL+=-4095; + dsCounterL+=127; + } + + if ((tnsR>>8)==0 && dsCounterR>0) dsCounterR=0; + dsCounterR+=tnsR>>8; + if (dsCounterR>=0) { + accumR+=4095; + dsCounterR-=127; + } else { + accumR+=-4095; + dsCounterR+=127; + } + } + + *l=accumL; + *r=accumR; + } else { + *l=minval(32767,maxval(-32767,tnsL)); + *r=minval(32767,maxval(-32767,tnsR)); + } } -void SoundUnit::Init(int sampleMemSize) { +void SoundUnit::Init(int sampleMemSize, bool dsOutMode) { pcmSize=sampleMemSize; + dsOut=dsOutMode; Reset(); memset(pcm,0,pcmSize); for (int i=0; i<256; i++) { @@ -346,6 +380,8 @@ void SoundUnit::Reset() { oldflags[i]=0; pcmdec[i]=0; } + dsCounterL=0; + dsCounterR=0; tnsL=0; tnsR=0; ilBufPos=0; diff --git a/src/engine/platform/sound/su.h b/src/engine/platform/sound/su.h index 0ee843481..546acfc9c 100644 --- a/src/engine/platform/sound/su.h +++ b/src/engine/platform/sound/su.h @@ -27,6 +27,8 @@ class SoundUnit { unsigned short oldfreq[8]; unsigned short oldflags[8]; unsigned int pcmSize; + bool dsOut; + short dsCounterL, dsCounterR; public: unsigned short resetfreq[8]; unsigned short voldcycles[8]; @@ -99,7 +101,7 @@ class SoundUnit { if (ret>32767) ret=32767; return ret; } - void Init(int sampleMemSize=8192); + void Init(int sampleMemSize=8192, bool dsOutMode=false); void Reset(); SoundUnit(); }; diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 8cd6d3abc..afcdba242 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -596,7 +596,7 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) { sampleMemSize=flags&16; - su->Init(sampleMemSize?65536:8192); + su->Init(sampleMemSize?65536:8192,flags&32); renderSamples(); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 484490348..a6bbcb921 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -135,13 +135,20 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::RadioButton("5.95MHz (PAL)",(flags&3)==1)) { copyOfFlags=(flags&(~3))|1; } - ImGui::Text("Chip revision (sample memory):"); - if (ImGui::RadioButton("A/B/E (8K)",(flags&16)==0)) { + ImGui::Text("Sample memory:"); + if (ImGui::RadioButton("8K (rev A/B/E)",(flags&16)==0)) { copyOfFlags=(flags&(~16))|0; } - if (ImGui::RadioButton("D/F (64K)",(flags&16)==16)) { + if (ImGui::RadioButton("64K (rev D/F)",(flags&16)==16)) { copyOfFlags=(flags&(~16))|16; } + ImGui::Text("DAC resolution"); + if (ImGui::RadioButton("16-bit (rev A/B/D/F)",(flags&32)==0)) { + copyOfFlags=(flags&(~32))|0; + } + if (ImGui::RadioButton("1-bit PDM (rev C/E)",(flags&32)==32)) { + copyOfFlags=(flags&(~32))|32; + } bool echo=flags&4; if (ImGui::Checkbox("Enable echo",&echo)) { copyOfFlags=(flags&(~4))|(echo<<2); From edddff8431b0d8254e36f9df4fccb6fddb0ac057 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 16 Aug 2022 03:19:16 -0500 Subject: [PATCH 192/194] prepare for pattern optimization --- src/engine/fileOps.cpp | 4 ++ src/engine/pattern.cpp | 114 ++++++++++++++--------------------------- src/engine/pattern.h | 23 ++++++--- src/engine/song.cpp | 29 +++++++++++ src/engine/song.h | 2 + 5 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 845ee98bd..7cdcd5c8e 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2916,6 +2916,10 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } ds.insLen=(int)ds.ins.size(); + // optimize + ds.subsong[0]->optimizePatterns(); + ds.subsong[0]->rearrangePatterns(); + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 0a561376b..77255e084 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -18,6 +18,7 @@ */ #include "engine.h" +#include "../ta-log.h" static DivPattern emptyPat; @@ -40,6 +41,44 @@ DivPattern* DivChannelData::getPattern(int index, bool create) { return data[index]; } +std::vector> DivChannelData::optimize() { + std::vector> ret; + for (int i=0; i<256; i++) { + if (data[i]!=NULL) { + // compare + for (int j=0; j<256; j++) { + if (j==i) continue; + if (data[j]==NULL) continue; + if (memcmp(data[i]->data,data[j]->data,256*32*sizeof(short))==0) { + delete data[j]; + data[j]=NULL; + logV("%d == %d",i,j); + ret.push_back(std::pair(j,i)); + } + } + } + } + return ret; +} + +std::vector> DivChannelData::rearrange() { + std::vector> ret; + for (int i=0; i<256; i++) { + if (data[i]==NULL) { + for (int j=i; j<256; j++) { + if (data[j]!=NULL) { + data[i]=data[j]; + data[j]=NULL; + logV("%d -> %d",j,i); + ret.push_back(std::pair(j,i)); + if (++i>=256) break; + } + } + } + } + return ret; +} + void DivChannelData::wipePatterns() { for (int i=0; i<256; i++) { if (data[i]!=NULL) { @@ -54,81 +93,6 @@ void DivPattern::copyOn(DivPattern* dest) { memcpy(dest->data,data,sizeof(data)); } -SafeReader* DivPattern::compile(int len, int fxRows) { - SafeWriter w; - w.init(); - short lastNote, lastOctave, lastInstr, lastVolume, lastEffect[8], lastEffectVal[8]; - unsigned char rows=0; - - lastNote=0; - lastOctave=0; - lastInstr=-1; - lastVolume=-1; - memset(lastEffect,-1,8*sizeof(short)); - memset(lastEffectVal,-1,8*sizeof(short)); - - for (int i=0; i struct DivPattern { String name; @@ -28,14 +29,6 @@ struct DivPattern { * @param dest the destination pattern. */ void copyOn(DivPattern* dest); - - /** - * don't use yet! - * @param len the pattern length - * @param fxRows number of effect ...columns - * @return a SafeReader. - */ - SafeReader* compile(int len=256, int fxRows=1); DivPattern(); }; @@ -59,6 +52,20 @@ struct DivChannelData { */ DivPattern* getPattern(int index, bool create); + /** + * optimize pattern data. + * not thread-safe! use a mutex! + * @return a list of From -> To pairs + */ + std::vector> optimize(); + + /** + * re-arrange NULLs. + * not thread-safe! use a mutex! + * @return a list of From -> To pairs + */ + std::vector> rearrange(); + /** * destroy all patterns on this DivChannelData. */ diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 216a1bc4d..1adb33ebb 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -18,6 +18,7 @@ */ #include "song.h" +#include "../ta-log.h" void DivSubSong::clearData() { for (int i=0; i> clearOuts=pat[i].optimize(); + for (auto& j: clearOuts) { + for (int k=0; k<256; k++) { + if (orders.ord[i][k]==j.first) { + orders.ord[i][k]=j.second; + } + } + } + } +} + +void DivSubSong::rearrangePatterns() { + for (int i=0; i> clearOuts=pat[i].rearrange(); + for (auto& j: clearOuts) { + for (int k=0; k<256; k++) { + if (orders.ord[i][k]==j.first) { + orders.ord[i][k]=j.second; + } + } + } + } +} + void DivSong::clearSongData() { for (DivSubSong* i: subsong) { i->clearData(); diff --git a/src/engine/song.h b/src/engine/song.h index 8b7eaa59f..de422a24a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -138,6 +138,8 @@ struct DivSubSong { String chanShortName[DIV_MAX_CHANS]; void clearData(); + void optimizePatterns(); + void rearrangePatterns(); DivSubSong(): hilightA(4), From d1c5a4725bbb5b919eed2fa32c94b859dd173fad Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 16 Aug 2022 03:42:17 -0500 Subject: [PATCH 193/194] add option to save unused patterns closes #106 also prepare for the pattern manager window --- CMakeLists.txt | 1 + src/engine/fileOps.cpp | 30 +++++++++++++++++++++--------- src/gui/doAction.cpp | 6 ++++++ src/gui/gui.cpp | 5 +++++ src/gui/gui.h | 7 ++++++- src/gui/guiConst.cpp | 1 + src/gui/patManager.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/gui/settings.cpp | 8 ++++++++ 8 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 src/gui/patManager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e468d4073..732cb623f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -531,6 +531,7 @@ src/gui/midiMap.cpp src/gui/newSong.cpp src/gui/orders.cpp src/gui/osc.cpp +src/gui/patManager.cpp src/gui/pattern.cpp src/gui/piano.cpp src/gui/presets.cpp diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7cdcd5c8e..589ca6a58 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -3515,15 +3515,27 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // high short is channel // low short is pattern number 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; + if (getConfInt("saveUnusedPatterns",0)==1) { + for (int i=0; ipat[i].data[k]==NULL) continue; + patsToWrite.push_back(PatToWrite(j,i,k)); + } + } + } + } else { + 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; + } } } } diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 931de26dc..5c840df4e 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -238,6 +238,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_CHANNELS: nextWindow=GUI_WINDOW_CHANNELS; break; + case GUI_ACTION_WINDOW_PAT_MANAGER: + nextWindow=GUI_WINDOW_PAT_MANAGER; + break; case GUI_ACTION_WINDOW_REGISTER_VIEW: nextWindow=GUI_WINDOW_REGISTER_VIEW; break; @@ -322,6 +325,9 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_CHANNELS: channelsOpen=false; break; + case GUI_WINDOW_PAT_MANAGER: + patManagerOpen=false; + break; case GUI_WINDOW_REGISTER_VIEW: regViewOpen=false; break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2b4367c91..4a6f3f39a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3107,6 +3107,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; + if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; if (ImGui::MenuItem("song comments",BIND_FOR(GUI_ACTION_WINDOW_NOTES),notesOpen)) notesOpen=!notesOpen; ImGui::Separator(); @@ -3241,6 +3242,7 @@ bool FurnaceGUI::loop() { drawPiano(); drawNotes(); drawChannels(); + drawPatManager(); drawRegView(); drawLog(); drawEffectList(); @@ -4361,6 +4363,7 @@ bool FurnaceGUI::init() { pianoOpen=e->getConfBool("pianoOpen",false); notesOpen=e->getConfBool("notesOpen",false); channelsOpen=e->getConfBool("channelsOpen",false); + patManagerOpen=e->getConfBool("patManagerOpen",false); regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); @@ -4603,6 +4606,7 @@ bool FurnaceGUI::finish() { e->setConf("pianoOpen",pianoOpen); e->setConf("notesOpen",notesOpen); e->setConf("channelsOpen",channelsOpen); + e->setConf("patManagerOpen",patManagerOpen); e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); @@ -4782,6 +4786,7 @@ FurnaceGUI::FurnaceGUI(): subSongsOpen(true), findOpen(false), spoilerOpen(false), + patManagerOpen(false), selecting(false), selectingFull(false), dragging(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 1c7355060..785c63f97 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -242,6 +242,7 @@ enum FurnaceGUIWindows { GUI_WINDOW_PIANO, GUI_WINDOW_NOTES, GUI_WINDOW_CHANNELS, + GUI_WINDOW_PAT_MANAGER, GUI_WINDOW_REGISTER_VIEW, GUI_WINDOW_LOG, GUI_WINDOW_EFFECT_LIST, @@ -361,6 +362,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_PIANO, GUI_ACTION_WINDOW_NOTES, GUI_ACTION_WINDOW_CHANNELS, + GUI_ACTION_WINDOW_PAT_MANAGER, GUI_ACTION_WINDOW_REGISTER_VIEW, GUI_ACTION_WINDOW_LOG, GUI_ACTION_WINDOW_EFFECT_LIST, @@ -1117,6 +1119,7 @@ class FurnaceGUI { int unsignedDetune; int noThreadedInput; int clampSamples; + int saveUnusedPatterns; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1228,6 +1231,7 @@ class FurnaceGUI { unsignedDetune(0), noThreadedInput(0), clampSamples(0), + saveUnusedPatterns(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1257,7 +1261,7 @@ 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, findOpen, spoilerOpen; + bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen; SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; @@ -1566,6 +1570,7 @@ class FurnaceGUI { void drawPiano(); void drawNotes(); void drawChannels(); + void drawPatManager(); void drawRegView(); void drawAbout(); void drawSettings(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 5fabb1c72..ea2edf279 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -487,6 +487,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_PIANO", "Piano", 0), D("WINDOW_NOTES", "Song Comments", 0), D("WINDOW_CHANNELS", "Channels", 0), + D("WINDOW_PAT_MANAGER", "Pattern Manager", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), D("EFFECT_LIST", "Effect List", 0), diff --git a/src/gui/patManager.cpp b/src/gui/patManager.cpp new file mode 100644 index 000000000..16fdc345c --- /dev/null +++ b/src/gui/patManager.cpp @@ -0,0 +1,37 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome4.h" +#include + +void FurnaceGUI::drawPatManager() { + if (nextWindow==GUI_WINDOW_PAT_MANAGER) { + patManagerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!patManagerOpen) return; + if (ImGui::Begin("Pattern Manager",&patManagerOpen,globalWinFlags)) { + ImGui::Text("Hello World!"); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PAT_MANAGER; + ImGui::End(); +} diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ee35fd86c..3134d9e38 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -514,6 +514,11 @@ void FurnaceGUI::drawSettings() { settings.blankIns=blankInsB; } + bool saveUnusedPatternsB=settings.saveUnusedPatterns; + if (ImGui::Checkbox("Save unused patterns",&saveUnusedPatternsB)) { + settings.saveUnusedPatterns=saveUnusedPatternsB; + } + ImGui::Text("Note preview behavior:"); if (ImGui::RadioButton("Never##npb0",settings.notePreviewBehavior==0)) { settings.notePreviewBehavior=0; @@ -2133,6 +2138,7 @@ void FurnaceGUI::syncSettings() { settings.macroRelLabel=e->getConfString("macroRelLabel","REL"); settings.emptyLabel=e->getConfString("emptyLabel","..."); settings.emptyLabel2=e->getConfString("emptyLabel2",".."); + settings.saveUnusedPatterns=e->getConfInt("saveUnusedPatterns",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2221,6 +2227,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.unsignedDetune,0,1); clampSetting(settings.noThreadedInput,0,1); clampSetting(settings.clampSamples,0,1); + clampSetting(settings.saveUnusedPatterns,0,1); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2365,6 +2372,7 @@ void FurnaceGUI::commitSettings() { e->setConf("macroRelLabel",settings.macroRelLabel); e->setConf("emptyLabel",settings.emptyLabel); e->setConf("emptyLabel2",settings.emptyLabel2); + e->setConf("saveUnusedPatterns",settings.saveUnusedPatterns); // colors for (int i=0; i Date: Tue, 16 Aug 2022 04:19:00 -0500 Subject: [PATCH 194/194] GUI: pattern manager, part 1 --- src/gui/gui.h | 7 ++++ src/gui/guiConst.cpp | 7 ++++ src/gui/patManager.cpp | 74 +++++++++++++++++++++++++++++++++++++++++- src/gui/settings.cpp | 9 +++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 785c63f97..2162ae1e9 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -200,6 +200,13 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PAT_MANAGER_NULL, + GUI_COLOR_PAT_MANAGER_USED, + GUI_COLOR_PAT_MANAGER_OVERUSED, + GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED, + GUI_COLOR_PAT_MANAGER_COMBO_BREAKER, + GUI_COLOR_PAT_MANAGER_UNUSED, + GUI_COLOR_PIANO_BACKGROUND, GUI_COLOR_PIANO_KEY_BOTTOM, GUI_COLOR_PIANO_KEY_TOP, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ea2edf279..714e77709 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -812,6 +812,13 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"",ImVec4(0.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_PATTERN_EFFECT_MISC,"",ImVec4(0.3f,0.3f,1.0f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_NULL,"",ImVec4(0.15f,0.15f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_USED,"",ImVec4(0.15f,1.0f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_OVERUSED,"",ImVec4(1.0f,1.0f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED,"",ImVec4(1.0f,0.5f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_COMBO_BREAKER,"",ImVec4(1.0f,0.15f,1.0f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_UNUSED,"",ImVec4(1.0f,0.15f,0.15f,1.0f)), + D(GUI_COLOR_PIANO_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,1.0f)), D(GUI_COLOR_PIANO_KEY_BOTTOM,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_PIANO_KEY_TOP,"",ImVec4(0.0f,0.0f,0.0f,1.0f)), diff --git a/src/gui/patManager.cpp b/src/gui/patManager.cpp index 16fdc345c..d597bd9c2 100644 --- a/src/gui/patManager.cpp +++ b/src/gui/patManager.cpp @@ -29,8 +29,80 @@ void FurnaceGUI::drawPatManager() { nextWindow=GUI_WINDOW_NOTHING; } if (!patManagerOpen) return; + char id[1024]; + unsigned char isUsed[256]; + bool isNull[256]; if (ImGui::Begin("Pattern Manager",&patManagerOpen,globalWinFlags)) { - ImGui::Text("Hello World!"); + ImGui::Text("Global Tasks"); + + if (ImGui::Button("De-duplicate patterns")) { + e->lockEngine([this]() { + e->curSubSong->optimizePatterns(); + }); + } + ImGui::SameLine(); + if (ImGui::Button("Re-arrange patterns")) { + e->lockEngine([this]() { + e->curSubSong->rearrangePatterns(); + }); + } + + for (int i=0; igetTotalChannelCount(); i++) { + memset(isUsed,0,256); + memset(isNull,0,256*sizeof(bool)); + for (int j=0; jcurSubSong->ordersLen; j++) { + isUsed[e->curSubSong->orders.ord[i][j]]++; + } + for (int j=0; j<256; j++) { + isNull[j]=(e->curSubSong->pat[i].data[j]==NULL); + } + ImGui::Text("%d. %s",i+1,e->getChannelName(i)); + ImGui::PushID(1000+i); + ImGui::PushFont(patFont); + if (ImGui::BeginTable("PatManTable",32)) { + for (int k=0; k<256; k++) { + if ((k&31)==0) ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + snprintf(id,1023,"%.2X",k); + if (isNull[k]) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_NULL]); + } else if (isUsed[k]>=e->curSubSong->ordersLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_COMBO_BREAKER]); + } else if (isUsed[k]>=0.7*(double)e->curSubSong->ordersLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED]); + } else if (isUsed[k]>=0.4*(double)e->curSubSong->ordersLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_OVERUSED]); + } else if (isUsed[k]) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_USED]); + } else { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_UNUSED]); + } + ImGui::Selectable(id,isUsed[k]); + if (ImGui::IsItemHovered()) { + ImGui::PushFont(mainFont); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + if (isNull[k]) { + ImGui::SetTooltip("Pattern %.2X\n- not allocated",k); + } else { + ImGui::SetTooltip("Pattern %.2X\n- use count: %d (%.0f%%)\n\nright-click to erase",k,isUsed[k],100.0*(double)isUsed[k]/(double)e->curSubSong->ordersLen); + } + ImGui::PopStyleColor(); + ImGui::PopFont(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + e->lockEngine([this,i,k]() { + delete e->curSubSong->pat[i].data[k]; + e->curSubSong->pat[i].data[k]=NULL; + }); + } + ImGui::PopStyleColor(); + } + ImGui::EndTable(); + } + ImGui::PopFont(); + ImGui::PopID(); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PAT_MANAGER; ImGui::End(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3134d9e38..f411dea5e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1583,6 +1583,15 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); ImGui::TreePop(); } + if (ImGui::TreeNode("Pattern Manager")) { + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_NULL,"Unallocated"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_UNUSED,"Unused"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_USED,"Used"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_OVERUSED,"Overused"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED,"Really overused"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_COMBO_BREAKER,"Combo Breaker"); + ImGui::TreePop(); + } if (ImGui::TreeNode("Piano")) { UI_COLOR_CONFIG(GUI_COLOR_PIANO_BACKGROUND,"Background"); UI_COLOR_CONFIG(GUI_COLOR_PIANO_KEY_TOP,"Upper key");