diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fd08833..7e060924 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -243,7 +243,7 @@ jobs: cp -v ../LICENSE LICENSE.txt cp -v ../README.md README.txt - cp -vr ../{papers,demos} ../${binPath}/furnace.exe ./ + cp -vr ../{papers,demos,instruments} ../${binPath}/furnace.exe ./ popd @@ -278,6 +278,7 @@ jobs: popd - name: Upload artifact + if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }} uses: actions/upload-artifact@v3 with: name: ${{ steps.package-identify.outputs.id }} diff --git a/CMakeLists.txt b/CMakeLists.txt index ce27aa93..1bfac71c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -635,6 +635,7 @@ if (NOT ANDROID OR TERMUX) 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) 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/demos/bruno_time.fur b/demos/bruno_time.fur index 6d5756d7..0ad2d841 100644 Binary files a/demos/bruno_time.fur and b/demos/bruno_time.fur differ diff --git a/instruments/FM/default.fui b/instruments/FM/default.fui new file mode 100644 index 00000000..6b8360d4 Binary files /dev/null and b/instruments/FM/default.fui differ diff --git a/instruments/OPL/key.fui b/instruments/OPL/key.fui new file mode 100644 index 00000000..976c2c52 Binary files /dev/null and b/instruments/OPL/key.fui differ diff --git a/instruments/OPLL/Weeppiko-piano_guitar_idk.fui b/instruments/OPLL/Weeppiko-piano_guitar_idk.fui new file mode 100644 index 00000000..1db0556c Binary files /dev/null and b/instruments/OPLL/Weeppiko-piano_guitar_idk.fui differ diff --git a/instruments/README.md b/instruments/README.md new file mode 100644 index 00000000..fdfae2f3 --- /dev/null +++ b/instruments/README.md @@ -0,0 +1,18 @@ +# instruments + +a directory of ready-to-use instruments. +these are organized in the following categories: + +- **FM**: FM instruments for OPN/OPM. +- **OPL**: FM instruments for OPL. +- **OPLL**: FM instruments for OPLL. +- **other**: instruments for any other system. + +# submit instruments + +you may submit your own instruments by creating a pull request or contacting me! the only guidelines are: + +- any format Furnace is able to load is accepted. +- I (strongly) prefer original instruments. + +thanks for your contribution! diff --git a/instruments/other/compatibility.fui b/instruments/other/compatibility.fui new file mode 100644 index 00000000..1682a424 Binary files /dev/null and b/instruments/other/compatibility.fui differ diff --git a/papers/doc/7-systems/c64.md b/papers/doc/7-systems/c64.md index 1029eda9..9b3e8701 100644 --- a/papers/doc/7-systems/c64.md +++ b/papers/doc/7-systems/c64.md @@ -1,9 +1,11 @@ # Commodore 64 -a home computer with a synthesizer-grade sound chip of which people took decades to master. +a home computer with a synthesizer-grade sound chip of which people took decades to master. Three oscillators with four selectable waveforms, ring modulation, oscillator sync, multimode filter, ADSR envelope... very popular in Europe and mostly due to the demoscene, which stretched the machine's limbs to no end. +Two revisions do exist - 6581 (original chip) and 8580 (improved version with working waveform mixing and somewhat more consistent filter curves) + # effects - `10xx`: change wave. the following values are accepted: diff --git a/papers/doc/7-systems/namco.md b/papers/doc/7-systems/namco.md new file mode 100644 index 00000000..a856d8fb --- /dev/null +++ b/papers/doc/7-systems/namco.md @@ -0,0 +1,13 @@ +# Namco WSG | Namco C15 | Namco C30 + +a family of wavetable synth sound chips used by Namco in their arcade machines (Pacman and later). Waveforms are 4-bit, with 32-byte sample length. + +Everything starts with Namco WSG, simple 3ch wavetable with no extra frills. C15 is much more advanced sound source with 8 channels. C30 adds stereo output and noise mode. + +# effects + +-`10xx`: change waveform +-`11xx`: toggle noise mode WARNING: only on C30 + + + diff --git a/papers/doc/7-systems/oki.md b/papers/doc/7-systems/oki.md new file mode 100644 index 00000000..4b46eb21 --- /dev/null +++ b/papers/doc/7-systems/oki.md @@ -0,0 +1,9 @@ +# OKI MSM6258 | OKI MSM6295 + +MSM6258 is a single-channel ADPCM sound source developed by OKI. It allows max sample rate of 15.6 kHz... with no variable pitch. Most prominent use of this chip was Sharp X68000 computer, where it was paired with Yamaha YM2151. + +MSM6295 is an upgrade from 6258 - it provides 4 ADPCM channels, at max 32 kHz (still no variable pitch though). Between late 80s and late 90s, it was one of the most common, if not THE most common soundchip used in arcade machines (Capcom, Toaplan, Kaneko, Atari, Tecmo, the list can go on and on...) + +# effects + +- `20xx`: set chip output rate diff --git a/papers/doc/7-systems/ricoh.md b/papers/doc/7-systems/ricoh.md new file mode 100644 index 00000000..6f5405f2 --- /dev/null +++ b/papers/doc/7-systems/ricoh.md @@ -0,0 +1,8 @@ +# Ricoh RF5C68 + +YM2612's sidekick - poor man's SPC-700. 8ch PCM sample-based synthesizer used in Sega Mega CD, Fujitsu FM Towns and some of Sega's arcade machines. Supports up to 64 Kbytes of external PCM data. + +# effects + +none so far. + diff --git a/papers/doc/7-systems/ym2612.md b/papers/doc/7-systems/ym2612.md index e86dde9d..6b221201 100644 --- a/papers/doc/7-systems/ym2612.md +++ b/papers/doc/7-systems/ym2612.md @@ -2,6 +2,8 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player. +For 0.6pre1, Furnace can now support advanced YM2612 features that [Fractal](https://gitlab.com/Natsumi/Fractal-Sound) sound driver adds: two software-mixed PCM channels (variable pitch, sample offsets, max 13.7 khz rate) and CSM - ch3 special mode feature that can be abused to produce rudimentary speech synthesis. + # effects - `10xy`: set LFO parameters. @@ -64,4 +66,4 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operat - `5Cxx`: set D2R/SR of operator 1. - `5Dxx`: set D2R/SR of operator 2. - `5Exx`: set D2R/SR of operator 3. -- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/ymz280b.md b/papers/doc/7-systems/ymz280b.md new file mode 100644 index 00000000..097fa6a4 --- /dev/null +++ b/papers/doc/7-systems/ymz280b.md @@ -0,0 +1,7 @@ +# Yamaha YMZ280B (PCMD8) + +8ch PCM/ADPCM sample-based synthesizer designed for use with arcade machines. Lived throughout mid to late 90s. It has 16-level stereo panning, up to 16-bit PCM and up to 16Mbytes of external PCM data. + +# effects + +none so far. diff --git a/papers/doc/7-systems/zxbeep.md b/papers/doc/7-systems/zxbeep.md new file mode 100644 index 00000000..1c6cd74f --- /dev/null +++ b/papers/doc/7-systems/zxbeep.md @@ -0,0 +1,10 @@ +# ZX Spectrum Speaker + +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. + +# effects + +- `12xx`: set pulse width +- `17xx`: trigger overlay drums. diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index 2257a595..7b3c76d7 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -29,8 +29,9 @@ cp ../../win32build/furnace.exe . || exit 1 cp ../../README.md README.txt || exit 1 cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 +cp -r ../../instruments instruments || exit 1 -zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments furName=$(git describe --tags | sed "s/v0/0/") diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index db580e09..75a07f51 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -29,8 +29,9 @@ cp ../../winbuild/furnace.exe . || exit 1 cp ../../README.md README.txt || exit 1 cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 +cp -r ../../instruments instruments || exit 1 -zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments furName=$(git describe --tags | sed "s/v0/0/") diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index a23e4e8f..227f6067 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -312,10 +312,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformTX81Z; break; case DIV_SYSTEM_SAA1099: { - int saaCore=eng->getConfInt("saaCore",1); - if (saaCore<0 || saaCore>2) saaCore=0; dispatch=new DivPlatformSAA1099; - ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); break; } case DIV_SYSTEM_PCSPKR: diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index dc9a13e8..c562838a 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -71,28 +71,6 @@ const char* DivPlatformSAA1099::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformSAA1099::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) { - if (saaBufLenClear(); - break; - case DIV_SAA_CORE_E: - break; - } + saa_saaSound->Clear(); for (int i=0; i<6; i++) { chan[i]=DivPlatformSAA1099::Channel(); chan[i].std.setEngine(parent); @@ -500,16 +459,8 @@ void DivPlatformSAA1099::setFlags(unsigned int flags) { oscBuf[i]->rate=rate; } - switch (core) { - case DIV_SAA_CORE_MAME: - break; - case DIV_SAA_CORE_SAASOUND: - saa_saaSound->SetClockRate(chipClock); - saa_saaSound->SetSampleRate(rate); - break; - case DIV_SAA_CORE_E: - break; - } + saa_saaSound->SetClockRate(chipClock); + saa_saaSound->SetSampleRate(rate); } void DivPlatformSAA1099::poke(unsigned int addr, unsigned short val) { @@ -520,10 +471,6 @@ void DivPlatformSAA1099::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } -void DivPlatformSAA1099::setCore(DivSAACores c) { - core=c; -} - int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -533,11 +480,9 @@ int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - if (core==DIV_SAA_CORE_SAASOUND) { - saa_saaSound=CreateCSAASound(); - saa_saaSound->SetOversample(1); - saa_saaSound->SetSoundParameters(SAAP_NOFILTER|SAAP_16BIT|SAAP_STEREO); - } + saa_saaSound=CreateCSAASound(); + saa_saaSound->SetOversample(1); + saa_saaSound->SetSoundParameters(SAAP_NOFILTER|SAAP_16BIT|SAAP_STEREO); setFlags(flags); saaBufLen=65536; for (int i=0; i<2; i++) saaBuf[i]=new short[saaBufLen]; diff --git a/src/engine/platform/saa.h b/src/engine/platform/saa.h index fafb36d7..0efd498d 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -22,15 +22,8 @@ #include "../dispatch.h" #include "../macroInt.h" #include -#include "sound/saa1099.h" #include "../../../extern/SAASound/src/SAASound.h" -enum DivSAACores { - DIV_SAA_CORE_MAME=0, - DIV_SAA_CORE_SAASOUND, - DIV_SAA_CORE_E -}; - class DivPlatformSAA1099: public DivDispatch { protected: struct Channel { @@ -58,8 +51,6 @@ class DivPlatformSAA1099: public DivDispatch { QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; std::queue writes; - DivSAACores core; - saa1099_device saa; CSAASound* saa_saaSound; unsigned char regPool[32]; unsigned char lastBusy; @@ -83,9 +74,7 @@ class DivPlatformSAA1099: public DivDispatch { unsigned char saaNoise[2]; friend void putDispatchChan(void*,int,int); - void acquire_e(short* bufL, short* bufR, size_t start, size_t len); void acquire_saaSound(short* bufL, short* bufR, size_t start, size_t len); - void acquire_mame(short* bufL, short* bufR, size_t start, size_t len); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -99,7 +88,6 @@ class DivPlatformSAA1099: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); - void setCore(DivSAACores core); void setFlags(unsigned int flags); bool isStereo(); int getPortaFloor(int ch); diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index fd9e2e10..5bd945f1 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -190,8 +190,8 @@ void FurnaceGUI::drawChanOsc() { needlePos-=alignment; */ - String cPhase=fmt::sprintf("%d cphase: %f",point,phase); - dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + //String cPhase=fmt::sprintf("%d cphase: %f",point,phase); + //dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); needlePos-=displaySize; for (unsigned short i=0; i<512; i++) { diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp index 3a5ec75f..1d83ab4e 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -3,6 +3,16 @@ #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +const char* queryModes[GUI_QUERY_MAX]={ + "ignore", + "equals", + "not equal", + "between", + "not between", + "any", + "none" +}; + void FurnaceGUI::drawFindReplace() { if (nextWindow==GUI_WINDOW_FIND) { findOpen=true; @@ -12,7 +22,60 @@ void FurnaceGUI::drawFindReplace() { if (!findOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Find/Replace",&findOpen,globalWinFlags)) { - ImGui::Text("What am I gonna do with you?"); + if (curQuery.empty()) { + curQuery.push_back(FurnaceGUIFindQuery()); + } + ImGui::Text("Find"); + for (FurnaceGUIFindQuery& i: curQuery) { + if (ImGui::BeginTable("FindRep",4,ImGuiTableFlags_BordersOuter)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Note"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##NCondition",&i.noteMode,queryModes,GUI_QUERY_MAX); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Ins"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##ICondition",&i.noteMode,queryModes,GUI_QUERY_MAX); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Volume"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##VCondition",&i.noteMode,queryModes,GUI_QUERY_MAX); + + for (int j=0; jgetConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); subSongsOpen=e->getConfBool("subSongsOpen",true); + findOpen=e->getConfBool("findOpen",false); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); @@ -4314,6 +4312,7 @@ bool FurnaceGUI::finish() { e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); e->setConf("subSongsOpen",subSongsOpen); + e->setConf("findOpen",findOpen); // commit last window size e->setConf("lastWindowWidth",scrW); diff --git a/src/gui/gui.h b/src/gui/gui.h index 982f8c27..b448f84f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -805,6 +805,50 @@ struct FurnaceGUIMacroDesc { } }; +enum FurnaceGUIFindQueryModes { + GUI_QUERY_IGNORE=0, + GUI_QUERY_MATCH, + GUI_QUERY_MATCH_NOT, + GUI_QUERY_RANGE, + GUI_QUERY_RANGE_NOT, + GUI_QUERY_ANY, + GUI_QUERY_NONE, + + GUI_QUERY_MAX +}; + +struct FurnaceGUIFindQuery { + int noteMode, insMode, volMode, effectCount; + int effectMode[8]; + int effectValMode[8]; + int note, noteMax; + unsigned char ins, insMax; + unsigned char vol, volMax; + unsigned char effect[8]; + unsigned char effectMax[8]; + unsigned char effectVal[8]; + unsigned char effectValMax[8]; + + FurnaceGUIFindQuery(): + noteMode(GUI_QUERY_IGNORE), + insMode(GUI_QUERY_IGNORE), + volMode(GUI_QUERY_IGNORE), + effectCount(0), + note(0), + noteMax(0), + ins(0), + insMax(0), + vol(0), + volMax(0) { + memset(effectMode,0,8*sizeof(int)); + memset(effectValMode,0,8*sizeof(int)); + memset(effect,0,8); + memset(effectMax,0,8); + memset(effectVal,0,8); + memset(effectValMax,0,8); + } +}; + class FurnaceGUI { DivEngine* e; @@ -876,7 +920,6 @@ class FurnaceGUI { int arcadeCore; int ym2612Core; int snCore; - int saaCore; int nesCore; int fdsCore; int pcSpeakerOutMethod; @@ -977,7 +1020,6 @@ class FurnaceGUI { arcadeCore(0), ym2612Core(0), snCore(0), - saaCore(1), nesCore(0), fdsCore(0), pcSpeakerOutMethod(0), @@ -1122,6 +1164,8 @@ class FurnaceGUI { std::vector pgProgram; int pgSys, pgAddr, pgVal; + std::vector curQuery; + struct ActiveNote { int chan; int note; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 384bb4dc..a7d60229 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -84,11 +84,6 @@ const char* snCores[]={ "Nuked-PSG Mod" }; -const char* saaCores[]={ - "MAME", - "SAASound" -}; - const char* nesCores[]={ "puNES", "NSFplay" @@ -899,10 +894,6 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##SNCore",&settings.snCore,snCores,2); - ImGui::Text("SAA1099 core"); - ImGui::SameLine(); - ImGui::Combo("##SAACore",&settings.saaCore,saaCores,2); - ImGui::Text("NES core"); ImGui::SameLine(); ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2); @@ -1955,7 +1946,6 @@ void FurnaceGUI::syncSettings() { settings.arcadeCore=e->getConfInt("arcadeCore",0); settings.ym2612Core=e->getConfInt("ym2612Core",0); settings.snCore=e->getConfInt("snCore",0); - settings.saaCore=e->getConfInt("saaCore",1); settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); @@ -2036,6 +2026,7 @@ void FurnaceGUI::syncSettings() { settings.insCellSpacing=e->getConfInt("insCellSpacing",0); settings.volCellSpacing=e->getConfInt("volCellSpacing",0); settings.effectCellSpacing=e->getConfInt("effectCellSpacing",0); + settings.effectValCellSpacing=e->getConfInt("effectValCellSpacing",0); settings.doubleClickColumn=e->getConfInt("doubleClickColumn",1); settings.blankIns=e->getConfInt("blankIns",0); @@ -2049,7 +2040,6 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.arcadeCore,0,1); clampSetting(settings.ym2612Core,0,1); clampSetting(settings.snCore,0,1); - clampSetting(settings.saaCore,0,1); clampSetting(settings.nesCore,0,1); clampSetting(settings.fdsCore,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); @@ -2172,7 +2162,6 @@ void FurnaceGUI::commitSettings() { e->setConf("arcadeCore",settings.arcadeCore); e->setConf("ym2612Core",settings.ym2612Core); e->setConf("snCore",settings.snCore); - e->setConf("saaCore",settings.saaCore); e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod);