From 828af722ad3c6144a8d0ecf99cc043188d4dcddd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 12:33:17 -0500 Subject: [PATCH 01/26] specs2 demo song by Mahbod_Karamoozian --- demos/specs2/rastaline_dub.fur | Bin 0 -> 27902 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/specs2/rastaline_dub.fur diff --git a/demos/specs2/rastaline_dub.fur b/demos/specs2/rastaline_dub.fur new file mode 100644 index 0000000000000000000000000000000000000000..6de407b4fb682bee355a19ab3f495287c3d00ba4 GIT binary patch literal 27902 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 literal 0 HcmV?d00001 From 068eb357d00d602b4fe744f461e8d3e2bff1a047 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 12:50:46 -0500 Subject: [PATCH 02/26] oh yeah wait I need to merge menu first --- src/gui/gui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0bb1397b5..5e7810d21 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1667,7 +1667,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { case GUI_FILE_SAVE_DMF: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( - "Save File", + "Export DMF", {"DefleMask 1.1.3 module", "*.dmf"}, workingDirSong, dpiScale @@ -1676,7 +1676,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { case GUI_FILE_SAVE_DMF_LEGACY: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( - "Save File", + "Export DMF", {"DefleMask 1.0/legacy module", "*.dmf"}, workingDirSong, dpiScale From bf6d98d3c1fdc80a7c9e2d16b572d2cef7c5b9a0 Mon Sep 17 00:00:00 2001 From: Lunathir <18320914+lunathir@users.noreply.github.com> Date: Sun, 17 Mar 2024 10:52:10 -0700 Subject: [PATCH 03/26] Resort window menu (#1793) * Resort window menu * Update gui.cpp * Update gui.cpp * Update gui.cpp --- src/gui/gui.cpp | 87 +++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 5e7810d21..0d9a1f91d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4350,15 +4350,15 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Settings":"settings")) { - #ifndef IS_MOBILE +#ifndef IS_MOBILE if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { doAction(GUI_ACTION_FULLSCREEN); } - #endif +#endif if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } - if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { + if (ImGui::MenuItem("pattern visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; e->enableCommandStream(fancyPattern); e->getCommandStream(cmdStream); @@ -4379,50 +4379,65 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Window":"window")) { - if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; - if (ImGui::MenuItem("subsongs",BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS),subSongsOpen)) subSongsOpen=!subSongsOpen; - if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen; - if (settings.unifiedDataView) { - if (ImGui::MenuItem("assets",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; - } else { - if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; - if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; - if (ImGui::MenuItem("samples",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST),sampleListOpen)) sampleListOpen=!sampleListOpen; + if (ImGui::BeginMenu("song")) { + if (ImGui::MenuItem("song comments", BIND_FOR(GUI_ACTION_WINDOW_NOTES), notesOpen)) notesOpen = !notesOpen; + if (ImGui::MenuItem("song information", BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO), songInfoOpen)) songInfoOpen = !songInfoOpen; + if (ImGui::MenuItem("subsongs", BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS), subSongsOpen)) subSongsOpen = !subSongsOpen; + ImGui::Separator(); + if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; + if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; + if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; + if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; + if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; + ImGui::EndMenu(); } - if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; - if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; + if (ImGui::BeginMenu("assets")) { + if (settings.unifiedDataView) { + if (ImGui::MenuItem("assets", BIND_FOR(GUI_ACTION_WINDOW_INS_LIST), insListOpen)) insListOpen = !insListOpen; + } else { + if (ImGui::MenuItem("instruments", BIND_FOR(GUI_ACTION_WINDOW_INS_LIST), insListOpen)) insListOpen = !insListOpen; + if (ImGui::MenuItem("samples", BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST), sampleListOpen)) sampleListOpen = !sampleListOpen; + if (ImGui::MenuItem("wavetables", BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST), waveListOpen)) waveListOpen = !waveListOpen; + } + ImGui::Separator(); + if (ImGui::MenuItem("instrument editor", BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT), insEditOpen)) insEditOpen = !insEditOpen; + if (ImGui::MenuItem("sample editor", BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT), sampleEditOpen)) sampleEditOpen = !sampleEditOpen; + if (ImGui::MenuItem("wavetable editor", BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT), waveEditOpen)) waveEditOpen = !waveEditOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("visualizers")) { + if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; + if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; + if (ImGui::MenuItem("oscilloscope (X-Y)",BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen; + if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("tempo")) { + if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen; + if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen; + if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("debug")) { + if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; + if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; + if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; + if (ImGui::MenuItem("memory composition",BIND_FOR(GUI_ACTION_WINDOW_MEMORY),memoryOpen)) memoryOpen=!memoryOpen; + ImGui::EndMenu(); + } + ImGui::Separator(); + if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; - if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen; - 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("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; - 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(); - if (ImGui::MenuItem("instrument editor",BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT),insEditOpen)) insEditOpen=!insEditOpen; - if (ImGui::MenuItem("wavetable editor",BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT),waveEditOpen)) waveEditOpen=!waveEditOpen; - if (ImGui::MenuItem("sample editor",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT),sampleEditOpen)) sampleEditOpen=!sampleEditOpen; - ImGui::Separator(); if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen; if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; - if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; - if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; - if (ImGui::MenuItem("oscilloscope (X-Y)",BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen; - if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; - if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen; - if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; - if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; - if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; - if (ImGui::MenuItem("memory composition",BIND_FOR(GUI_ACTION_WINDOW_MEMORY),memoryOpen)) memoryOpen=!memoryOpen; + if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Help":"help")) { - if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; if (ImGui::MenuItem("inspector")) inspectorOpen=!inspectorOpen; - if (ImGui::MenuItem("shader editor")) shaderEditor=!shaderEditor; if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { aboutOpen=true; From b253bd862bd7e87965482a36a83641a7b3bc4e11 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 13:14:50 -0500 Subject: [PATCH 04/26] downgrade .dmf to export status --- src/gui/exportOptions.cpp | 34 ++++++++++++++++++++++++++++++++++ src/gui/gui.cpp | 15 +++++++++------ src/gui/gui.h | 5 ++++- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 65bbefbaa..5bbf84a78 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -251,6 +251,33 @@ void FurnaceGUI::drawExportCommand(bool onWindow) { } } +void FurnaceGUI::drawExportDMF(bool onWindow) { + exitDisabledTimer=1; + + ImGui::Text( + "export in DefleMask module format.\n" + "only do it if you really, really need to, or are downgrading an existing .dmf." + ); + + ImGui::Text("format version:"); + ImGui::RadioButton("1.1.3 and higher",&dmfExportVersion,0); + ImGui::RadioButton("1.0/legacy (0.12)",&dmfExportVersion,1); + + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { + if (dmfExportVersion==1) { + openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); + } else { + openFileDialog(GUI_FILE_SAVE_DMF); + } + ImGui::CloseCurrentPopup(); + } +} + void FurnaceGUI::drawExport() { if (settings.exportOptionsLayout==1 || curExportType==GUI_EXPORT_NONE) { if (ImGui::BeginTabBar("ExportTypes")) { @@ -290,6 +317,10 @@ void FurnaceGUI::drawExport() { drawExportCommand(true); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("DMF")) { + drawExportDMF(true); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } } else switch (curExportType) { @@ -311,6 +342,9 @@ void FurnaceGUI::drawExport() { case GUI_EXPORT_CMD_STREAM: drawExportCommand(true); break; + case GUI_EXPORT_DMF: + drawExportDMF(true); + break; default: ImGui::Text("congratulations! you've unlocked a secret panel."); if (ImGui::Button("Toggle hidden systems")) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0d9a1f91d..7226932e0 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4156,12 +4156,6 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } - if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) { - openFileDialog(GUI_FILE_SAVE_DMF); - } - if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) { - openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); - } ImGui::Separator(); if (settings.exportOptionsLayout==0) { if (ImGui::BeginMenu("export audio...")) { @@ -4200,6 +4194,10 @@ bool FurnaceGUI::loop() { drawExportCommand(); ImGui::EndMenu(); } + if (ImGui::BeginMenu("export .dmf...")) { + drawExportDMF(); + ImGui::EndMenu(); + } } else if (settings.exportOptionsLayout==2) { if (ImGui::MenuItem("export audio...")) { curExportType=GUI_EXPORT_AUDIO; @@ -4237,6 +4235,10 @@ bool FurnaceGUI::loop() { curExportType=GUI_EXPORT_CMD_STREAM; displayExport=true; } + if (ImGui::MenuItem("export .dmf...")) { + curExportType=GUI_EXPORT_DMF; + displayExport=true; + } } else { if (ImGui::MenuItem("export...",BIND_FOR(GUI_ACTION_EXPORT))) { displayExport=true; @@ -7813,6 +7815,7 @@ FurnaceGUI::FurnaceGUI(): curTutorial(-1), curTutorialStep(0), audioExportType(0), + dmfExportVersion(0), curExportType(GUI_EXPORT_NONE) { // value keys valueKeys[SDLK_0]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 8358be6d5..5b17f2d8a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -586,7 +586,8 @@ enum FurnaceGUIExportTypes { GUI_EXPORT_ZSM, GUI_EXPORT_CMD_STREAM, GUI_EXPORT_AMIGA_VAL, - GUI_EXPORT_TEXT + GUI_EXPORT_TEXT, + GUI_EXPORT_DMF }; enum FurnaceGUIFMAlgs { @@ -2462,6 +2463,7 @@ class FurnaceGUI { // export options int audioExportType; + int dmfExportVersion; FurnaceGUIExportTypes curExportType; void drawExportAudio(bool onWindow=false); @@ -2470,6 +2472,7 @@ class FurnaceGUI { void drawExportAmigaVal(bool onWindow=false); void drawExportText(bool onWindow=false); void drawExportCommand(bool onWindow=false); + void drawExportDMF(bool onWindow=false); void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); From de444d926069f94cc0db4184141e6ff41c95baff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 13:23:04 -0500 Subject: [PATCH 05/26] GUI: more menu reordering --- src/gui/gui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7226932e0..925b272c5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4391,6 +4391,8 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; + if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; + if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu("assets")) { @@ -4429,15 +4431,14 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; - if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen; if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; - if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Help":"help")) { + if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; if (ImGui::MenuItem("inspector")) inspectorOpen=!inspectorOpen; if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); From c1773e09f3117b4de557c7d8cac27daf1ac60083 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 11:49:20 +0900 Subject: [PATCH 06/26] Add NDS sound support TODO: - IMA ADPCM - Instrument color, icon --- CMakeLists.txt | 3 + src/engine/dispatchContainer.cpp | 4 + src/engine/engine.cpp | 3 +- src/engine/instrument.cpp | 4 + src/engine/instrument.h | 1 + src/engine/platform/nds.cpp | 540 +++++++++++++++++++++++++ src/engine/platform/nds.h | 100 +++++ src/engine/platform/sound/nds.cpp | 634 ++++++++++++++++++++++++++++++ src/engine/platform/sound/nds.hpp | 415 +++++++++++++++++++ src/engine/song.h | 1 + src/engine/sysDef.cpp | 13 + src/gui/about.cpp | 2 + src/gui/doAction.cpp | 6 +- src/gui/gui.h | 1 + src/gui/guiConst.cpp | 5 + src/gui/insEdit.cpp | 21 +- src/gui/presets.cpp | 15 + src/gui/settings.cpp | 1 + src/gui/sysConf.cpp | 22 ++ src/main.cpp | 2 + 20 files changed, 1786 insertions(+), 7 deletions(-) create mode 100644 src/engine/platform/nds.cpp create mode 100644 src/engine/platform/nds.h create mode 100644 src/engine/platform/sound/nds.cpp create mode 100644 src/engine/platform/sound/nds.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 581e1c2de..8425a78d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -609,6 +609,8 @@ src/engine/platform/sound/c140_c219.c src/engine/platform/sound/dave/dave.cpp +src/engine/platform/sound/nds.cpp + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp @@ -717,6 +719,7 @@ src/engine/platform/c140.cpp src/engine/platform/esfm.cpp src/engine/platform/powernoise.cpp src/engine/platform/dave.cpp +src/engine/platform/nds.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 7146e90c6..170c1ae95 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -85,6 +85,7 @@ #include "platform/esfm.h" #include "platform/powernoise.h" #include "platform/dave.h" +#include "platform/nds.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -661,6 +662,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_DAVE: dispatch=new DivPlatformDave; break; + case DIV_SYSTEM_NDS: + dispatch=new DivPlatformNDS; + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 52b0879a5..235fda675 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -988,7 +988,8 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_GA20 || i->type==DIV_INS_K053260 || i->type==DIV_INS_C140 || - i->type==DIV_INS_C219) { + i->type==DIV_INS_C219 || + i->type==DIV_INS_NDS) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 24126c340..cd30a648c 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1098,6 +1098,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo break; case DIV_INS_DAVE: break; + case DIV_INS_NDS: + featureSM=true; + if (amiga.useSample) featureSL=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: diff --git a/src/engine/instrument.h b/src/engine/instrument.h index fd75a4d0f..d66ce25db 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -89,6 +89,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_POWERNOISE=56, DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_DAVE=58, + DIV_INS_NDS=59,/*temp*/ DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp new file mode 100644 index 000000000..a92075bd1 --- /dev/null +++ b/src/engine/platform/nds.cpp @@ -0,0 +1,540 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 "nds.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +#define CHIP_DIVIDER 32 +#define SAMPLE_DIVIDER 512 + +#define rRead8(a) (nds.read8(a)) +#define rWrite8(a,v) {if(!skipRegisterWrites) {nds.write8((a),(v)); regPool[(a)]=(v); if(dumpWrites) addWrite((a),(v)); }} +#define rWrite16(a,v) { \ + if(!skipRegisterWrites) { \ + nds.write16((a)>>1,(v)); \ + regPool[(a)+0]=(v)&0xff; \ + regPool[(a)+1]=((v)>>8)&0xff; \ + if(dumpWrites) addWrite((a)+0,(v)&0xff); \ + if(dumpWrites) addWrite((a)+1,((v)>>8)&0xff); \ + } \ +} + +#define rWrite32(a,v) { \ + if(!skipRegisterWrites) { \ + nds.write32((a)>>2,(v)); \ + regPool[(a)+0]=(v)&0xff; \ + regPool[(a)+1]=((v)>>8)&0xff; \ + regPool[(a)+2]=((v)>>16)&0xff; \ + regPool[(a)+3]=((v)>>24)&0xff; \ + if(dumpWrites) addWrite((a)+0,(v)&0xff); \ + if(dumpWrites) addWrite((a)+1,((v)>>8)&0xff); \ + if(dumpWrites) addWrite((a)+2,((v)>>16)&0xff); \ + if(dumpWrites) addWrite((a)+3,((v)>>24)&0xff); \ + } \ +} + +const char* regCheatSheetNDS[]={ + "CHx_Control", "000+x*10", + "CHx_Start", "004+x*10", + "CHx_Freq", "008+x*10", + "CHx_LoopStart", "00A+x*10", + "CHx_Length", "00C+x*10", + "Control", "100", + "Bias", "104", + "CAPx_Control", "108+x*1", + "CAPx_Dest", "110+x*8", + "CAPx_Length", "114+x*8", + NULL +}; + +const char** DivPlatformNDS::getRegisterSheet() { + return regCheatSheetNDS; +} + +void DivPlatformNDS::acquire(short** buf, size_t len) { + for (size_t i=0; i32767) lout=32767; + if (lout<-32768) lout=-32768; + if (rout>32767) rout=32767; + if (rout<-32768) rout=-32768; + buf[0][i]=lout; + buf[1][i]=rout; + + for (int i=0; i<16; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(nds.chan_lout(i)+nds.chan_rout(i))>>1; + } + } +} + +inline u8 DivPlatformNDS::read_byte(u32 addr) { + if (addrcalcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if ((i>=8) && (i<14)) { + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; + if ((!chan[i].pcm)) { // pulse + rWrite8(0x03+i*16,(rRead8(0x03+i*16)&0xe8)|(chan[i].duty&7)); + } + } + } + if (chan[i].std.panL.had) { // panning + chan[i].panning=0x40+chan[i].std.panL.val; + rWrite8(0x02+i*16,chan[i].panning); + } + if (chan[i].std.phaseReset.had) { + if ((chan[i].std.phaseReset.val==1) && chan[i].active) { + chan[i].audPos=0; + chan[i].setPos=true; + } + } + if (chan[i].setPos) { + // force keyon + chan[i].keyOn=true; + chan[i].setPos=false; + } else { + chan[i].audPos=0; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + unsigned char ctrl=0; + if (chan[i].pcm || i<8) { + DivSample* s=parent->getSample(chan[i].sample); + switch (s->depth) { + //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: ctrl=0x40; break; + case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x00; break; + case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x20; break; + default: break; + } + double off=(s->centerRate>=1)?(8363.0/(double)s->centerRate):1.0; + chan[i].freq=0x10000-(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x08:0x10); + if (chan[i].keyOn) { + unsigned int start=0; + unsigned int loopStart=0; + unsigned int loopEnd=0; + unsigned int end=0; + if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + start=sampleOff[chan[i].sample]; + end=s->getCurBufLen()/4; + } + if (chan[i].audPos>0) { + switch (s->depth) { + //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; + } + } + if (s->isLoopable()) { + switch (s->depth) { + //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; + case DIV_SAMPLE_DEPTH_8BIT: loopStart=s->loopStart/4; loopEnd=(s->loopEnd-s->loopStart)/4; break; + case DIV_SAMPLE_DEPTH_16BIT: loopStart=s->loopStart/2; loopEnd=(s->loopEnd-s->loopStart)/2; break; + default: break; + } + loopEnd=MIN(loopEnd,0x3fffff); + loopStart=MIN(loopStart,0xffff); + rWrite16(0x0a+i*16,loopStart&0xffff); + rWrite32(0x0c+i*16,loopEnd&0x3fffff); + } else { + end=MIN(end,0x3fffff); + rWrite16(0x0a+i*16,0); + rWrite32(0x0c+i*16,end&0x3fffff); + } + rWrite8(0x03+i*16,ctrl&~0x80); // force keyoff first + rWrite32(0x04+i*16,start&0x7fffffc); + } + } else { + chan[i].freq=0x10000-(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,8)); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + ctrl=(chan[i].active?0xe8:0)|(chan[i].duty&7); + rWrite8(0x03+i*16,ctrl&~0x80); // force keyoff first + } + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + writeOutVol(i); + } + chan[i].keyOn=false; + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite16(0x08+i*16,chan[i].freq&0xffff); + chan[i].freqChanged=false; + } + rWrite8(0x03+i*16,ctrl); + } + } +} + +int DivPlatformNDS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_NDS); + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample || (c.chan<8)) { + chan[c.chan].pcm=true; + } + if (chan[c.chan].pcm || (c.chan<8)) { + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + chan[c.chan].sampleNote=c.value; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + } + if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + } else { + chan[c.chan].macroVolMul=127; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].sample=-1; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + writeOutVol(c.chan); + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PANNING: + chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,127),127); + rWrite8(0x02+c.chan*16,chan[c.chan].panning); + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if ((c.chan>=8) && (c.chan<14) && (!chan[c.chan].pcm)) { // pulse + chan[c.chan].duty=c.value; + rWrite8(0x03+c.chan*16,(rRead8(0x03+c.chan*16)&0xe8)|(chan[c.chan].duty&7)); + } + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_NDS)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + if (chan[c.chan].pcm || (c.chan<8)) { + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + } + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformNDS::writeOutVol(int ch) { + unsigned char val=isMuted[ch]?0:chan[ch].outVol; + rWrite8(0x00+ch*16,val); +} + +void DivPlatformNDS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformNDS::forceIns() { + for (int i=0; i<16; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + chan[i].sample=-1; + + rWrite8(0x02+i*16,chan[i].panning); + } +} + +void* DivPlatformNDS::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformNDS::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformNDS::getPan(int ch) { + return parent->convertPanLinearToSplit(chan[ch].panning,8,127); +} + +DivDispatchOscBuffer* DivPlatformNDS::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +void DivPlatformNDS::reset() { + memset(regPool,0,288); + nds.reset(); + rWrite32(0x100,0x807f); // enable keyon + rWrite32(0x104,0x200); // initialize bias + for (int i=0; i<16; i++) { + chan[i]=DivPlatformNDS::Channel(); + chan[i].std.setEngine(parent); + rWrite32(0x00+i*16,0x40007f); + } +} + +int DivPlatformNDS::getOutputCount() { + return 2; +} + +void DivPlatformNDS::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformNDS::notifyWaveChange(int wave) { + // TODO when wavetables are added + // TODO they probably won't be added unless the samples reside in RAM +} + +void DivPlatformNDS::notifyInsDeletion(void* ins) { + for (int i=0; i<16; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformNDS::poke(unsigned int addr, unsigned short val) { + rWrite8(addr,val); +} + +void DivPlatformNDS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite8(i.addr,i.val); +} + +unsigned char* DivPlatformNDS::getRegisterPool() { + return regPool; +} + +int DivPlatformNDS::getRegisterPoolSize() { + return 288; +} + +float DivPlatformNDS::getPostAmp() { + return 1.0f; +} + +const void* DivPlatformNDS::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformNDS::getSampleMemCapacity(int index) { + return index == 0 ? (isDSi?16777216:4194304) : 0; +} + +size_t DivPlatformNDS::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformNDS::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +void DivPlatformNDS::renderSamples(int sysID) { + memset(sampleMem,0,16777216); + memset(sampleOff,0,256*sizeof(unsigned int)); + memset(sampleLoaded,0,256*sizeof(bool)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + + int length=s->getCurBufLen(); + unsigned char* src=(unsigned char*)s->getCurBuf(); + int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length); + if (actualLength>0) { + memcpy(&sampleMem[memPos],src,actualLength); + sampleOff[i]=memPos; + memPos+=length; + } + if (actualLengthrate=rate; + } +} + +int DivPlatformNDS::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + + for (int i=0; i<16; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + sampleMem=new unsigned char[16777216]; + sampleMemLen=0; + nds.reset(); + setFlags(flags); + reset(); + + return 16; +} + +void DivPlatformNDS::quit() { + delete[] sampleMem; + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h new file mode 100644 index 000000000..1861b97b9 --- /dev/null +++ b/src/engine/platform/nds.h @@ -0,0 +1,100 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 _NDS_H +#define _NDS_H + +#include "../dispatch.h" +#include "sound/nds.hpp" + +using namespace nds_sound_emu; + +class DivPlatformNDS: public DivDispatch, public nds_sound_intf { + struct Channel: public SharedChannel { + unsigned int audPos; + int sample, wave; + int panning, duty; + bool setPos, pcm; + int macroVolMul; + Channel(): + SharedChannel(127), + audPos(0), + sample(-1), + wave(-1), + panning(8), + duty(0), + setPos(false), + pcm(false), + macroVolMul(64) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + bool isDSi; + unsigned int sampleOff[256]; + bool sampleLoaded[256]; + + unsigned char* sampleMem; + size_t sampleMemLen; + nds_sound_t nds; + unsigned char regPool[288]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + + virtual inline u8 read_byte(u32 addr) override; + virtual inline void write_byte(u32 addr, u8 data) override; + + public: + void acquire(short** buf, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + float getPostAmp(); + int getOutputCount(); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + bool isSampleLoaded(int index, int sample); + void renderSamples(int chipID); + void setFlags(const DivConfig& flags); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + DivPlatformNDS(): + DivDispatch(), + nds_sound_intf(), + nds(*this) {} + private: + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/platform/sound/nds.cpp b/src/engine/platform/sound/nds.cpp new file mode 100644 index 000000000..67c45a884 --- /dev/null +++ b/src/engine/platform/sound/nds.cpp @@ -0,0 +1,634 @@ +/* + +============================================================================ + +NDS sound emulator +by cam900 + +This file is licensed under zlib license. + +============================================================================ + +zlib License + +(C) 2024-present cam900 and contributors + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +============================================================================ +TODO: +- needs to further verifications from real hardware + +Tech info: https://problemkaputt.de/gbatek.htm + +*/ + +#include "nds.hpp" + +namespace nds_sound_emu +{ + void nds_sound_t::reset() + { + for (channel_t &elem : m_channel) + elem.reset(); + for (capture_t &elem : m_capture) + elem.reset(); + + m_control = 0; + m_bias = 0; + m_loutput = 0; + m_routput = 0; + } + + void nds_sound_t::tick(s32 cycle) + { + m_loutput = m_routput = (m_bias & 0x3ff); + if (!enable()) + return; + + // mix outputs + s32 lmix = 0, rmix = 0; + for (u8 i = 0; i < 16; i++) + { + channel_t &channel = m_channel[i]; + channel.update(cycle); + // bypass mixer + if (((i == 1) && (mix_ch1())) || ((i == 3) && (mix_ch3()))) + continue; + + lmix += channel.loutput(); + rmix += channel.routput(); + } + + // send mixer output to capture + m_capture[0].update(lmix, cycle); + m_capture[1].update(rmix, cycle); + + // select left/right output source + switch (lout_from()) + { + case 0: // left mixer + break; + case 1: // channel 1 + lmix = m_channel[1].loutput(); + break; + case 2: // channel 3 + lmix = m_channel[3].loutput(); + break; + case 3: // channel 1 + 3 + lmix = m_channel[1].loutput() + m_channel[3].loutput(); + break; + } + + switch (rout_from()) + { + case 0: // right mixer + break; + case 1: // channel 1 + rmix = m_channel[1].routput(); + break; + case 2: // channel 3 + rmix = m_channel[3].routput(); + break; + case 3: // channel 1 + 3 + rmix = m_channel[1].routput() + m_channel[3].routput(); + break; + } + + // adjust master volume + lmix = (lmix * mvol()) >> 13; + rmix = (rmix * mvol()) >> 13; + + // add bias and clip output + m_loutput = clamp((lmix + (m_bias & 0x3ff)), 0, 0x3ff); + m_routput = clamp((rmix + (m_bias & 0x3ff)), 0, 0x3ff); + } + + u8 nds_sound_t::read8(u32 addr) + { + return bitfield(read32(addr >> 2), bitfield(addr, 0, 2) << 3, 8); + } + + u16 nds_sound_t::read16(u32 addr) + { + return bitfield(read32(addr >> 1), bitfield(addr, 0) << 4, 16); + } + + u32 nds_sound_t::read32(u32 addr) + { + addr <<= 2; // word address + + switch (addr & 0x100) + { + case 0x000: + if ((addr & 0xc) == 0) + return m_channel[bitfield(addr, 4, 4)].control(); + break; + case 0x100: + switch (addr & 0xff) + { + case 0x00: + return m_control; + case 0x04: + return m_bias; + case 0x08: + return m_capture[0].control() | (m_capture[1].control() << 8); + case 0x10: + case 0x18: + return m_capture[bitfield(addr, 3)].dstaddr(); + default: + break; + } + break; + } + return 0; + } + + void nds_sound_t::write8(u32 addr, u8 data) + { + const u8 bit = bitfield(addr, 0, 2); + const u32 in = u32(data) << (bit << 3); + const u32 in_mask = 0xff << (bit << 3); + write32(addr >> 2, in, in_mask); + } + + void nds_sound_t::write16(u32 addr, u16 data, u16 mask) + { + const u8 bit = bitfield(addr, 0); + const u32 in = u32(data) << (bit << 4); + const u32 in_mask = u32(mask) << (bit << 4); + write32(addr >> 1, in, in_mask); + } + + void nds_sound_t::write32(u32 addr, u32 data, u32 mask) + { + addr <<= 2; // word address + + switch (addr & 0x100) + { + case 0x000: + m_channel[bitfield(addr, 4, 4)].write(bitfield(addr, 2, 2), data, mask); + break; + case 0x100: + switch (addr & 0xff) + { + case 0x00: + m_control = (m_control & ~mask) | (data & mask); + break; + case 0x04: + mask &= 0x3ff; + m_bias = (m_bias & ~mask) | (data & mask); + break; + case 0x08: + if (bitfield(mask, 0, 8)) + m_capture[0].control_w(data & 0xff); + if (bitfield(mask, 8, 8)) + m_capture[1].control_w((data >> 8) & 0xff); + break; + case 0x10: + case 0x14: + case 0x18: + case 0x1c: + m_capture[bitfield(addr, 3)].addrlen_w(bitfield(addr, 2), data, mask); + break; + default: + break; + } + break; + } + } + + // channels + void nds_sound_t::channel_t::reset() + { + m_control = 0; + m_sourceaddr = 0; + m_freq = 0; + m_loopstart = 0; + m_length = 0; + + m_playing = false; + m_adpcm_out = 0; + m_adpcm_index = 0; + m_prev_adpcm_out = 0; + m_prev_adpcm_index = 0; + m_cur_addr = 0; + m_cur_state = 0; + m_cur_bitaddr = 0; + m_delay = 0; + m_sample = 0; + m_lfsr = 0x7fff; + m_lfsr_out = 0x7fff; + m_counter = 0x10000; + m_output = 0; + m_loutput = 0; + m_routput = 0; + } + + void nds_sound_t::channel_t::write(u32 offset, u32 data, u32 mask) + { + const u32 old = m_control; + switch (offset & 3) + { + case 0: // Control/Status + m_control = (m_control & ~mask) | (data & mask); + if (bitfield(old ^ m_control, 31)) + { + if (busy()) + keyon(); + else if (!busy()) + keyoff(); + } + // reset hold flag + if (!m_playing && !hold()) + { + m_sample = m_lfsr_out = 0; + m_output = m_loutput = m_routput = 0; + } + break; + case 1: // Source address + mask &= 0x7ffffff; + m_sourceaddr = (m_sourceaddr & ~mask) | (data & mask); + break; + case 2: // Frequency, Loopstart + if (bitfield(mask, 0, 16)) + m_freq = (m_freq & bitfield(~mask, 0, 16)) | (bitfield(data & mask, 0, 16)); + if (bitfield(mask, 16, 16)) + m_loopstart = (m_loopstart & bitfield(~mask, 16, 16)) | (bitfield(data & mask, 16, 16)); + break; + case 3: // Length + mask &= 0x3fffff; + m_length = (m_length & ~mask) | (data & mask); + break; + } + } + + void nds_sound_t::channel_t::keyon() + { + if (!m_playing) + { + m_playing = true; + m_delay = format() == 2 ? 11 : 3; // 3 (11 for ADPCM) delay for playing sample + m_cur_bitaddr = m_cur_addr = 0; + m_cur_state = (format() == 2) ? STATE_ADPCM_LOAD : ((m_loopstart == 0) ? STATE_POST_LOOP : STATE_PRE_LOOP); + m_counter = 0x10000; + m_sample = 0; + m_lfsr_out = 0x7fff; + m_lfsr = 0x7fff; + } + } + + + void nds_sound_t::channel_t::keyoff() + { + if (m_playing) + { + if (busy()) + m_control &= ~(1 << 31); + if (!hold()) + { + m_sample = m_lfsr_out = 0; + m_output = m_loutput = m_routput = 0; + } + + m_playing = false; + } + } + + void nds_sound_t::channel_t::update(s32 cycle) + { + if (m_playing) + { + // get output + fetch(); + m_counter -= cycle; + while (m_counter <= m_freq) + { + // advance + advance(); + m_counter += 0x10000 - m_freq; + } + m_output = (m_sample * volume()) >> (7 + voldiv()); + m_loutput = (m_output * lvol()) >> 7; + m_routput = (m_output * rvol()) >> 7; + } + } + + void nds_sound_t::channel_t::fetch() + { + if (m_playing) + { + // fetch samples + switch (format()) + { + case 0: // PCM8 + m_sample = s16(m_host.m_intf.read_byte(addr()) << 8); + break; + case 1: // PCM16 + m_sample = m_host.m_intf.read_word(addr()); + break; + case 2: // ADPCM + m_sample = m_cur_state == STATE_ADPCM_LOAD ? 0 : m_adpcm_out; + break; + case 3: // PSG or Noise + m_sample = 0; + if (m_psg) // psg + m_sample = (duty() == 7) ? -0x8000 : ((m_cur_bitaddr < s32(u32(7) - duty())) ? -0x8000 : 0x7fff); + else if (m_noise) // noise + m_sample = m_lfsr_out; + break; + } + } + + // apply delay + if (format() != 3 && m_delay > 0) + m_sample = 0; + } + + void nds_sound_t::channel_t::advance() + { + if (m_playing) + { + // advance bit address + switch (format()) + { + case 0: // PCM8 + m_cur_bitaddr += 8; + break; + case 1: // PCM16 + m_cur_bitaddr += 16; + break; + case 2: // ADPCM + if (m_cur_state == STATE_ADPCM_LOAD) // load ADPCM data + { + if (m_cur_bitaddr == 0) + m_prev_adpcm_out = m_adpcm_out = m_host.m_intf.read_word(addr()); + if (m_cur_bitaddr == 16) + m_prev_adpcm_index = m_adpcm_index = clamp(m_host.m_intf.read_byte(addr()) & 0x7f, 0, 88); + } + else // decode ADPCM + { + const u8 input = bitfield(m_host.m_intf.read_byte(addr()), m_cur_bitaddr & 4, 4); + s32 diff = ((bitfield(input, 0, 3) * 2 + 1) * m_host.adpcm_diff_table[m_adpcm_index] / 8); + if (bitfield(input, 3)) diff = -diff; + m_adpcm_out = clamp(m_adpcm_out + diff, -0x8000, 0x7fff); + m_adpcm_index = clamp(m_adpcm_index + m_host.adpcm_index_table[bitfield(input, 0, 3)], 0, 88); + } + m_cur_bitaddr += 4; + break; + case 3: // PSG or Noise + if (m_psg) // psg + m_cur_bitaddr = (m_cur_bitaddr + 1) & 7; + else if (m_noise) // noise + { + if (bitfield(m_lfsr, 1)) + { + m_lfsr = (m_lfsr >> 1) ^ 0x6000; + m_lfsr_out = -0x8000; + } + else + { + m_lfsr >>= 1; + m_lfsr_out = 0x7fff; + } + } + break; + } + + // address update + if (format() != 3) + { + // adjust delay + m_delay--; + + // update address, loop + while (m_cur_bitaddr >= 32) + { + // already loaded? + if (format() == 2 && m_cur_state == STATE_ADPCM_LOAD) + { + m_cur_state = m_loopstart == 0 ? STATE_POST_LOOP : STATE_PRE_LOOP; + } + m_cur_addr++; + if (m_cur_state == STATE_PRE_LOOP && m_cur_addr >= m_loopstart) + { + m_cur_state = STATE_POST_LOOP; + m_cur_addr = 0; + if (format() == 2) + { + m_prev_adpcm_out = m_adpcm_out; + m_prev_adpcm_index = m_adpcm_index; + } + } + else if (m_cur_state == STATE_POST_LOOP && m_cur_addr >= m_length) + { + switch (repeat()) + { + case 0: // manual; not correct? + case 2: // one-shot + case 3: // prohibited + keyoff(); + break; + case 1: // loop infinitely + if (format() == 2) + { + if (m_loopstart == 0) // reload ADPCM + { + m_cur_state = STATE_ADPCM_LOAD; + } + else // restore + { + m_adpcm_out = m_prev_adpcm_out; + m_adpcm_index = m_prev_adpcm_index; + } + } + m_cur_addr = 0; + break; + } + } + m_cur_bitaddr -= 32; + } + } + } + } + + // capture + void nds_sound_t::capture_t::reset() + { + m_control = 0; + m_dstaddr = 0; + m_length = 0; + + m_counter = 0x10000; + m_cur_addr = 0; + m_cur_waddr = 0; + m_cur_bitaddr = 0; + m_enable = false; + } + + void nds_sound_t::capture_t::control_w(u8 data) + { + const u8 old = m_control; + m_control = data; + if (bitfield(old ^ m_control, 7)) + { + if (busy()) + capture_on(); + else if (!busy()) + capture_off(); + } + } + + void nds_sound_t::capture_t::addrlen_w(u32 offset, u32 data, u32 mask) + { + switch (offset & 1) + { + case 0: // Destination Address + mask &= 0x7ffffff; + m_dstaddr = (m_dstaddr & ~mask) | (data & mask); + break; + case 1: // Buffer Length + mask &= 0xffff; + m_length = (m_length & ~mask) | (data & mask); + break; + } + } + + void nds_sound_t::capture_t::update(s32 mix, s32 cycle) + { + if (m_enable) + { + s32 inval = 0; + // get inputs + // TODO: hardware bugs aren't emulated, add mode behavior not verified + if (addmode()) + inval = get_source() ? m_input.output() + m_output.output() : mix; + else + inval = get_source() ? m_input.output() : mix; + + // clip output + inval = clamp(inval, -0x8000, 0x7fff); + + // update counter + m_counter -= cycle; + while (m_counter <= m_output.freq()) + { + // write to memory; TODO: verify write behavior + if (format()) // 8 bit output + { + m_fifo[m_fifo_head & 7].write_byte(m_cur_bitaddr & 0x18, (inval >> 8) & 0xff); + m_cur_bitaddr += 8; + } + else + { + m_fifo[m_fifo_head & 7].write_word(m_cur_bitaddr & 0x10, inval & 0xffff); + m_cur_bitaddr += 16; + } + + // update address + while (m_cur_bitaddr >= 32) + { + // clear FIFO empty flag + m_fifo_empty = false; + + // advance FIFO head position + m_fifo_head = (m_fifo_head + 1) & 7; + if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask())) + m_fifo_full = true; + + // update loop + if (++m_cur_addr >= m_length) + { + if (repeat()) + m_cur_addr = 0; + else + capture_off(); + } + + if (m_fifo_full) + { + // execute FIFO + fifo_write(); + + // check repeat + if (m_cur_waddr >= m_length && repeat()) + m_cur_waddr = 0; + } + + m_cur_bitaddr -= 32; + } + m_counter += 0x10000 - m_output.freq(); + } + } + } + + bool nds_sound_t::capture_t::fifo_write() + { + if (m_fifo_empty) + return true; + + // clear FIFO full flag + m_fifo_full = false; + + // write FIFO data to memory + m_host.m_intf.write_dword(waddr(), m_fifo[m_fifo_tail].data()); + m_cur_waddr++; + + // advance FIFO tail position + m_fifo_tail = (m_fifo_tail + 1) & 7; + if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask())) + m_fifo_empty = true; + + return m_fifo_empty; + } + + void nds_sound_t::capture_t::capture_on() + { + if (!m_enable) + { + m_enable = true; + + // reset address + m_cur_bitaddr = 0; + m_cur_addr = m_cur_waddr = 0; + m_counter = 0x10000; + + // reset FIFO + m_fifo_head = m_fifo_tail = 0; + m_fifo_empty = true; + m_fifo_full = false; + } + } + + void nds_sound_t::capture_t::capture_off() + { + if (m_enable) + { + // flush FIFO + while (m_cur_waddr < m_length) + { + if (fifo_write()) + break; + } + + m_enable = false; + if (busy()) + m_control &= ~(1 << 7); + } + } + +}; // namespace nds_sound_emu \ No newline at end of file diff --git a/src/engine/platform/sound/nds.hpp b/src/engine/platform/sound/nds.hpp new file mode 100644 index 000000000..fb1dd6fbc --- /dev/null +++ b/src/engine/platform/sound/nds.hpp @@ -0,0 +1,415 @@ +/* + +============================================================================ + +NDS sound emulator +by cam900 + +This file is licensed under zlib license. + +============================================================================ + +zlib License + +(C) 2024-present cam900 and contributors + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +============================================================================ +TODO: +- needs to further verifications from real hardware + +Tech info: https://problemkaputt.de/gbatek.htm + +*/ + +#ifndef NDS_SOUND_EMU_H +#define NDS_SOUND_EMU_H + +namespace nds_sound_emu +{ + using u8 = unsigned char; + using u16 = unsigned short; + using u32 = unsigned int; + using u64 = unsigned long long; + using s8 = signed char; + using s16 = signed short; + using s32 = signed int; + using s64 = signed long long; + + template + static const inline T bitfield(const T in, const u8 pos) + { + return (in >> pos) & 1; + } // bitfield + + template + static const inline T bitfield(const T in, const u8 pos, const u8 len) + { + return (in >> pos) & ((1 << len) - 1); + } // bitfield + + template + static const inline T clamp(const T in, const T min, const T max) + { + return (in < min) ? min : ((in > max) ? max : in); + } // clamp + + class nds_sound_intf + { + public: + nds_sound_intf() + { + } + + virtual inline u8 read_byte(u32 addr) { return 0; } + inline u16 read_word(u32 addr) { return read_byte(addr) | (u16(read_byte(addr + 1)) << 8); } + inline u32 read_dword(u32 addr) { return read_word(addr) | (u16(read_word(addr + 2)) << 16); } + + virtual inline void write_byte(u32 addr, u8 data) {} + inline void write_word(u32 addr, u16 data) + { + write_byte(addr, data & 0xff); + write_byte(addr + 1, data >> 8); + } + inline void write_dword(u32 addr, u32 data) + { + write_word(addr, data & 0xffff); + write_word(addr + 2, data >> 16); + } + }; + + class nds_sound_t + { + public: + nds_sound_t(nds_sound_intf &intf) + : m_intf(intf) + , m_channel{ + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, true, false), channel_t(*this, true, false), + channel_t(*this, true, false), channel_t(*this, true, false), + channel_t(*this, true, false), channel_t(*this, true, false), + channel_t(*this, false, true), channel_t(*this, false, true) + } + , m_capture{ + capture_t(*this, m_channel[0], m_channel[1]), + capture_t(*this, m_channel[2], m_channel[3]) + } + , m_control(0) + , m_bias(0) + , m_loutput(0) + , m_routput(0) + { + } + + void reset(); + void tick(s32 cycle); + + // host accesses + u32 read32(u32 addr); + void write32(u32 addr, u32 data, u32 mask = ~0); + + u16 read16(u32 addr); + void write16(u32 addr, u16 data, u16 mask = ~0); + + u8 read8(u32 addr); + void write8(u32 addr, u8 data); + + s32 loutput() { return m_loutput; } + s32 routput() { return m_routput; } + + // for debug + s32 chan_lout(u8 ch) { return m_channel[ch].loutput(); } + s32 chan_rout(u8 ch) { return m_channel[ch].routput(); } + + private: + // ADPCM tables + s8 adpcm_index_table[8] = + { + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + u16 adpcm_diff_table[89] = + { + 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x0010, + 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001c, 0x001f, 0x0022, 0x0025, + 0x0029, 0x002d, 0x0032, 0x0037, 0x003c, 0x0042, 0x0049, 0x0050, 0x0058, + 0x0061, 0x006b, 0x0076, 0x0082, 0x008f, 0x009d, 0x00ad, 0x00be, 0x00d1, + 0x00e6, 0x00fd, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01c1, 0x01ee, + 0x0220, 0x0256, 0x0292, 0x02d4, 0x031c, 0x036c, 0x03c3, 0x0424, 0x048e, + 0x0502, 0x0583, 0x0610, 0x06ab, 0x0756, 0x0812, 0x08e0, 0x09c3, 0x0abd, + 0x0bd0, 0x0cff, 0x0e4c, 0x0fba, 0x114c, 0x1307, 0x14ee, 0x1706, 0x1954, + 0x1bdc, 0x1ea5, 0x21b6, 0x2515, 0x28ca, 0x2cdf, 0x315b, 0x364b, 0x3bb9, + 0x41b2, 0x4844, 0x4f7e, 0x5771, 0x602f, 0x69ce, 0x7462, 0x7fff + }; + + // structs + enum + { + STATE_ADPCM_LOAD = 0, + STATE_PRE_LOOP, + STATE_POST_LOOP + }; + + class channel_t + { + public: + channel_t(nds_sound_t &host, bool psg, bool noise) + : m_host(host) + + , m_psg(psg) + , m_noise(noise) + + , m_control(0) + , m_sourceaddr(0) + , m_freq(0) + , m_loopstart(0) + , m_length(0) + , m_playing(false) + , m_adpcm_out(0) + , m_adpcm_index(0) + , m_prev_adpcm_out(0) + , m_prev_adpcm_index(0) + , m_cur_addr(0) + , m_cur_state(0) + , m_cur_bitaddr(0) + , m_delay(0) + , m_sample(0) + , m_lfsr(0x7fff) + , m_lfsr_out(0x7fff) + , m_counter(0x10000) + , m_output(0) + , m_loutput(0) + , m_routput(0) + { + } + + void reset(); + void write(u32 offset, u32 data, u32 mask = ~0); + + void update(s32 cycle); + + // getters + // control word + u32 control() const { return m_control; } + u32 freq() const { return m_freq; } + + // outputs + s32 output() const { return m_output; } + s32 loutput() const { return m_loutput; } + s32 routput() const { return m_routput; } + + private: + // inline constants + const u8 m_voldiv_shift[4] = {0, 1, 2, 4}; + + // control bits + s32 volume() const { return bitfield(m_control, 0, 7); } // global volume + u32 voldiv() const { return m_voldiv_shift[bitfield(m_control, 8, 2)]; } // volume shift + bool hold() const { return bitfield(m_control, 15); } // hold bit + u32 pan() const { return bitfield(m_control, 16, 7); } // panning (0...127, 0 = left, 127 = right, 64 = half) + u32 duty() const { return bitfield(m_control, 24, 3); } // PSG duty + u32 repeat() const { return bitfield(m_control, 27, 2); } // Repeat mode (Manual, Loop infinitely, One-shot) + u32 format() const { return bitfield(m_control, 29, 2); } // Sound Format (PCM8, PCM16, ADPCM, PSG/Noise when exists) + bool busy() const { return bitfield(m_control, 31); } // Busy flag + + // calculated values + s32 lvol() const { return (pan() == 0x7f) ? 0 : 128 - pan(); } // calculated left volume + s32 rvol() const { return (pan() == 0x7f) ? 128 : pan(); } // calculated right volume + + // calculated address + u32 addr() const { return (m_sourceaddr & ~3) + (m_cur_bitaddr >> 3) + (m_cur_state == STATE_POST_LOOP ? ((m_loopstart + m_cur_addr) << 2) : (m_cur_addr << 2)); } + + void keyon(); + void keyoff(); + void fetch(); + void advance(); + + // interfaces + nds_sound_t &m_host; // host device + + // configuration + bool m_psg = false; // PSG Enable + bool m_noise = false; // Noise Enable + + // registers + u32 m_control = 0; // Control + u32 m_sourceaddr = 0; // Source Address + u16 m_freq = 0; // Frequency + u16 m_loopstart = 0; // Loop Start + u32 m_length = 0; // Length + + // internal states + bool m_playing = false; // playing flag + s32 m_adpcm_out = 0; // current ADPCM sample value + s32 m_adpcm_index = 0; // current ADPCM step + s32 m_prev_adpcm_out = 0; // previous ADPCM sample value + s32 m_prev_adpcm_index = 0; // previous ADPCM step + u32 m_cur_addr = 0; // current address + s32 m_cur_state = 0; // current state + s32 m_cur_bitaddr = 0; // bit address + s32 m_delay = 0; // delay + s16 m_sample = 0; // current sample + u32 m_lfsr = 0x7fff; // noise LFSR + s16 m_lfsr_out = 0x7fff; // LFSR output + s32 m_counter = 0x10000; // clock counter + s32 m_output = 0; // current output + s32 m_loutput = 0; // current left output + s32 m_routput = 0; // current right output + }; + + class capture_t + { + public: + capture_t(nds_sound_t &host, channel_t &input, channel_t &output) + : m_host(host) + , m_input(input) + , m_output(output) + + , m_control(0) + , m_dstaddr(0) + , m_length(0) + + , m_counter(0x10000) + , m_cur_addr(0) + , m_cur_waddr(0) + , m_cur_bitaddr(0) + , m_enable(0) + + , m_fifo{ + fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t(), + fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t() + } + , m_fifo_head(0) + , m_fifo_tail(0) + , m_fifo_empty(true) + , m_fifo_full(false) + { + } + + void reset(); + void update(s32 mix, s32 cycle); + + void control_w(u8 data); + void addrlen_w(u32 offset, u32 data, u32 mask = ~0); + + // getters + u32 control() const { return m_control; } + u32 dstaddr() const { return m_dstaddr; } + + private: + // inline constants + // control bits + bool addmode() const { return bitfield(m_control, 0); } // Add mode (add channel 1/3 output with channel 0/2) + bool get_source() const { return bitfield(m_control, 1); } // Select source (left or right mixer, channel 0/2) + bool repeat() const { return bitfield(m_control, 2); } // repeat flag + bool format() const { return bitfield(m_control, 3); } // store format (PCM16, PCM8) + bool busy() const { return bitfield(m_control, 7); } // busy flag + + // FIFO offset mask + u32 fifo_mask() const { return format() ? 7 : 3; } + + // calculated address + u32 waddr() const { return (m_dstaddr & ~3) + (m_cur_waddr << 2); } + + void capture_on(); + void capture_off(); + bool fifo_write(); + + // interfaces + nds_sound_t &m_host; // host device + channel_t &m_input; // Input channel + channel_t &m_output; // Output channel + + // registers + u8 m_control = 0; // Control + u32 m_dstaddr = 0; // Destination Address + u32 m_length = 0; // Buffer Length + + // internal states + u32 m_counter = 0x10000; // clock counter + u32 m_cur_addr = 0; // current address + u32 m_cur_waddr = 0; // current write address + s32 m_cur_bitaddr = 0; // bit address + bool m_enable = false; // capture enable + + // FIFO + class fifo_data_t + { + public: + fifo_data_t() + : m_data(0) + { + } + + void reset() + { + m_data = 0; + } + + // accessors + void write_byte(const u8 bit, const u8 data) + { + u32 input = u32(data) << bit; + u32 mask = (0xff << bit); + m_data = (m_data & ~mask) | (input & mask); + } + + void write_word(const u8 bit, const u16 data) + { + u32 input = u32(data) << bit; + u32 mask = (0xffff << bit); + m_data = (m_data & ~mask) | (input & mask); + } + + // getters + u32 data() const { return m_data; } + + private: + u32 m_data = 0; + }; + + fifo_data_t m_fifo[8]; // FIFO (8 word, for 16 sample delay) + u32 m_fifo_head = 0; // FIFO head + u32 m_fifo_tail = 0; // FIFO tail + bool m_fifo_empty = true; // FIFO empty flag + bool m_fifo_full = false; // FIFO full flag + }; + + nds_sound_intf &m_intf; // memory interface + + channel_t m_channel[16]; // 16 channels + capture_t m_capture[2]; // 2 capture channels + + inline u8 mvol() const { return bitfield(m_control, 0, 7); } // master volume + inline u8 lout_from() const { return bitfield(m_control, 8, 2); } // left output source (mixer, channel 1, channel 3, channel 1+3) + inline u8 rout_from() const { return bitfield(m_control, 10, 2); } // right output source (mixer, channel 1, channel 3, channel 1+3) + inline bool mix_ch1() const { return bitfield(m_control, 12); } // mix/bypass channel 1 + inline bool mix_ch3() const { return bitfield(m_control, 13); } // mix/bypass channel 3 + inline bool enable() const { return bitfield(m_control, 15); } // global enable + + u32 m_control = 0; // global control + u32 m_bias = 0; // output bias + s32 m_loutput = 0; // left output + s32 m_routput = 0; // right output + }; +}; // namespace nds_sound_emu + +#endif // NDS_SOUND_EMU_H \ No newline at end of file diff --git a/src/engine/song.h b/src/engine/song.h index 86674ee52..749feefc8 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -135,6 +135,7 @@ enum DivSystem { DIV_SYSTEM_ESFM, DIV_SYSTEM_POWERNOISE, DIV_SYSTEM_DAVE, + DIV_SYSTEM_NDS, }; enum DivEffectType: unsigned short { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index e33b17b1c..258d47eff 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2021,6 +2021,19 @@ void DivEngine::registerSystems() { } ); + sysDefs[DIV_SYSTEM_NDS]=new DivSysDef( + "NDS", NULL, 0xfe/* placeholder */, 0, 16, false, true, 0, false, (1U<type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VRC6 || - ins->type==DIV_INS_SU) { + ins->type==DIV_INS_SU || + ins->type==DIV_INS_NDS) { P(ImGui::Checkbox("Use sample",&ins->amiga.useSample)); if (ins->type==DIV_INS_X1_010) { if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER @@ -6021,7 +6022,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_GA20 || ins->type==DIV_INS_K053260 || ins->type==DIV_INS_C140 || - ins->type==DIV_INS_C219) { + ins->type==DIV_INS_C219 || + ins->type==DIV_INS_NDS) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6736,7 +6738,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES || ins->type==DIV_INS_MSM5232 || - ins->type==DIV_INS_K053260) { + ins->type==DIV_INS_K053260 || ins->type==DIV_INS_NDS) { volMax=127; } if (ins->type==DIV_INS_GB) { @@ -6921,6 +6923,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise Freq"; dutyMax=3; } + if (ins->type==DIV_INS_NDS) { + dutyLabel="Duty"; + dutyMax=ins->amiga.useSample?0:7; + } const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1)); @@ -6959,6 +6965,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_POWERNOISE_SLOPE) waveMax=0; if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7; if (ins->type==DIV_INS_DAVE) waveMax=4; + if (ins->type==DIV_INS_NDS) waveMax=0; if (ins->type==DIV_INS_PET) { waveMax=8; waveBitMode=true; @@ -7111,6 +7118,11 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_DAVE) { panMax=63; } + if (ins->type==DIV_INS_NDS) { + panMin=-64; + panMax=63; + panSingleNoBit=true; + } if (volMax>0) { macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); @@ -7201,7 +7213,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_ESFM || ins->type==DIV_INS_POWERNOISE || ins->type==DIV_INS_POWERNOISE_SLOPE || - ins->type==DIV_INS_DAVE) { + ins->type==DIV_INS_DAVE || + ins->type==DIV_INS_NDS) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 18a0ebdc9..ad688b292 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -266,6 +266,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_PV1000, 1.0f, 0, "") } ); + ENTRY( + "NDS", { + CH(DIV_SYSTEM_NDS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("Computers","let's get to work on chiptune today."); @@ -2703,6 +2708,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_C219, 1.0f, 0, "") } ); + ENTRY( + "NDS", { + CH(DIV_SYSTEM_NDS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("Wavetable","chips which use user-specified waveforms to generate sound."); @@ -2873,6 +2883,11 @@ void FurnaceGUI::initSystemPresets() { }, "tickRate=50" ); + ENTRY( + "NDS", { + CH(DIV_SYSTEM_NDS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 22d4b503b..ed919546e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3551,6 +3551,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE,"PowerNoise (noise)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"PowerNoise (slope)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_DAVE,"Dave"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_NDS,"NDS"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 18bc3d967..39957372b 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2316,6 +2316,28 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_NDS: { + int chipType=flags.getInt("chipType",0); + + ImGui::Text("Model:"); + ImGui::Indent(); + if (ImGui::RadioButton("DS (4MB RAM)",chipType==0)) { + chipType=0; + altered=true; + } + if (ImGui::RadioButton("DSi (16MB RAM)",chipType==1)) { + chipType=1; + altered=true; + } + ImGui::Unindent(); + + if (altered) { + e->lockSave([&]() { + flags.set("chipType",chipType); + }); + } + break; + } case DIV_SYSTEM_SWAN: case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_PET: diff --git a/src/main.cpp b/src/main.cpp index 10e8bec5a..4f36135dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -248,6 +248,8 @@ TAParamResult pVersion(String) { printf("- D65010G031 emulator (modified version) by cam900 (zlib license)\n"); printf("- C140/C219 emulator (modified version) by cam900 (zlib license)\n"); printf("- PowerNoise emulator by scratchminer (MIT)\n"); + printf("- ep128emu by Istvan Varga (GPLv2)\n"); + printf("- NDS sound emulator by cam900 (zlib license)\n"); return TA_PARAM_QUIT; } From 625f1e453866e22441793627c6d2488c2e608a43 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 12:02:19 +0900 Subject: [PATCH 07/26] Fix Mac build --- src/engine/platform/nds.h | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h index 1861b97b9..8c64dfd25 100644 --- a/src/engine/platform/nds.h +++ b/src/engine/platform/nds.h @@ -61,34 +61,34 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf { virtual inline void write_byte(u32 addr, u8 data) override; public: - void acquire(short** buf, size_t len); - int dispatch(DivCommand c); - void* getChanState(int chan); - DivMacroInt* getChanMacroInt(int ch); - unsigned short getPan(int chan); - DivDispatchOscBuffer* getOscBuffer(int chan); - unsigned char* getRegisterPool(); - int getRegisterPoolSize(); - void reset(); - void forceIns(); - void tick(bool sysTick=true); - void muteChannel(int ch, bool mute); - float getPostAmp(); - int getOutputCount(); - void notifyInsChange(int ins); - void notifyWaveChange(int wave); - void notifyInsDeletion(void* ins); - void poke(unsigned int addr, unsigned short val); - void poke(std::vector& wlist); - const char** getRegisterSheet(); - const void* getSampleMem(int index = 0); - size_t getSampleMemCapacity(int index = 0); - size_t getSampleMemUsage(int index = 0); - bool isSampleLoaded(int index, int sample); - void renderSamples(int chipID); - void setFlags(const DivConfig& flags); - int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); - void quit(); + virtual void acquire(short** buf, size_t len) override; + virtual int dispatch(DivCommand c) override; + virtual void* getChanState(int chan) override; + virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual unsigned short getPan(int chan) override; + virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; + virtual unsigned char* getRegisterPool() override; + virtual int getRegisterPoolSize() override; + virtual void reset() override; + virtual void forceIns() override; + virtual void tick(bool sysTick=true) override; + virtual void muteChannel(int ch, bool mute) override; + virtual float getPostAmp() override; + virtual int getOutputCount() override; + virtual void notifyInsChange(int ins) override; + virtual void notifyWaveChange(int wave) override; + virtual void notifyInsDeletion(void* ins) override; + virtual void poke(unsigned int addr, unsigned short val) override; + virtual void poke(std::vector& wlist) override; + virtual const char** getRegisterSheet() override; + virtual const void* getSampleMem(int index = 0) override; + virtual size_t getSampleMemCapacity(int index = 0) override; + virtual size_t getSampleMemUsage(int index = 0) override; + virtual bool isSampleLoaded(int index, int sample) override; + virtual void renderSamples(int chipID) override; + virtual void setFlags(const DivConfig& flags) override; + virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; + virtual void quit() override; DivPlatformNDS(): DivDispatch(), nds_sound_intf(), From 8e5925d84cd2c59eab25208a87353e925833e3d2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 12:11:49 +0900 Subject: [PATCH 08/26] Fix MSVC build --- src/engine/platform/nds.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h index 8c64dfd25..39ff8b84d 100644 --- a/src/engine/platform/nds.h +++ b/src/engine/platform/nds.h @@ -57,10 +57,10 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf { friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); - virtual inline u8 read_byte(u32 addr) override; - virtual inline void write_byte(u32 addr, u8 data) override; - public: + virtual inline u8 read_byte(u32 addr) override; + virtual inline void write_byte(u32 addr, u8 data) override; + virtual void acquire(short** buf, size_t len) override; virtual int dispatch(DivCommand c) override; virtual void* getChanState(int chan) override; From 024d1500af3129fce1510b594419972f7a354c91 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 15:43:29 +0900 Subject: [PATCH 09/26] Use NDS chip ID 0xd6 --- 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 258d47eff..3cc1796f2 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2022,7 +2022,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_NDS]=new DivSysDef( - "NDS", NULL, 0xfe/* placeholder */, 0, 16, false, true, 0, false, (1U< Date: Sun, 11 Feb 2024 15:43:56 +0900 Subject: [PATCH 10/26] Remove outdated comment --- src/engine/instrument.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index d66ce25db..52797ad35 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -89,7 +89,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_POWERNOISE=56, DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_DAVE=58, - DIV_INS_NDS=59,/*temp*/ + DIV_INS_NDS=59, DIV_INS_MAX, DIV_INS_NULL }; From 6b076dcaedcdad7bf574a57293ad932a30e86f40 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 15:45:18 +0900 Subject: [PATCH 11/26] really fix? MSVC build --- src/engine/platform/nds.cpp | 4 ++-- src/engine/platform/nds.h | 4 ++-- src/engine/platform/sound/nds.hpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index a92075bd1..0c3c075bc 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -87,14 +87,14 @@ void DivPlatformNDS::acquire(short** buf, size_t len) { } } -inline u8 DivPlatformNDS::read_byte(u32 addr) { +u8 DivPlatformNDS::read_byte(u32 addr) { if (addr Date: Sun, 11 Feb 2024 16:48:48 +0900 Subject: [PATCH 12/26] Fix 9xxx effect --- src/engine/platform/nds.cpp | 61 +++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index 0c3c075bc..1e36ea640 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -23,7 +23,7 @@ #include #define CHIP_DIVIDER 32 -#define SAMPLE_DIVIDER 512 +#define CLOCK_DIVIDER 512 // for match to output rate #define rRead8(a) (nds.read8(a)) #define rWrite8(a,v) {if(!skipRegisterWrites) {nds.write8((a),(v)); regPool[(a)]=(v); if(dumpWrites) addWrite((a),(v)); }} @@ -71,7 +71,7 @@ const char** DivPlatformNDS::getRegisterSheet() { void DivPlatformNDS::acquire(short** buf, size_t len) { for (size_t i=0; i32767) lout=32767; @@ -166,9 +166,9 @@ void DivPlatformNDS::tick(bool sysTick) { ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x08:0x10); if (chan[i].keyOn) { unsigned int start=0; - unsigned int loopStart=0; - unsigned int loopEnd=0; - unsigned int end=0; + int loopStart=0; + int loopEnd=0; + int end=0; if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { start=sampleOff[chan[i].sample]; end=s->getCurBufLen()/4; @@ -176,24 +176,47 @@ void DivPlatformNDS::tick(bool sysTick) { if (chan[i].audPos>0) { switch (s->depth) { //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; + case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; end-=(chan[i].audPos/4); break; + case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; end-=(chan[i].audPos/2); break; default: break; } } if (s->isLoopable()) { - switch (s->depth) { - //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; - case DIV_SAMPLE_DEPTH_8BIT: loopStart=s->loopStart/4; loopEnd=(s->loopEnd-s->loopStart)/4; break; - case DIV_SAMPLE_DEPTH_16BIT: loopStart=s->loopStart/2; loopEnd=(s->loopEnd-s->loopStart)/2; break; - default: break; + if (chan[i].audPos>0) { + switch (s->depth) { + //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; + case DIV_SAMPLE_DEPTH_8BIT: + loopStart=(s->loopStart-chan[i].audPos)/4; + loopEnd=(s->loopEnd-s->loopStart)/4; + if (chan[i].audPos>(unsigned int)s->loopStart) { + loopStart=0; + loopEnd-=(chan[i].audPos-s->loopStart)/4; + } + break; + case DIV_SAMPLE_DEPTH_16BIT: + loopStart=(s->loopStart-chan[i].audPos)/2; + loopEnd=(s->loopEnd-s->loopStart)/2; + if (chan[i].audPos>(unsigned int)s->loopStart) { + loopStart=0; + loopEnd-=(chan[i].audPos-s->loopStart)/2; + } + break; + default: break; + } + } else { + switch (s->depth) { + //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; + case DIV_SAMPLE_DEPTH_8BIT: loopStart=s->loopStart/4; loopEnd=(s->loopEnd-s->loopStart)/4; break; + case DIV_SAMPLE_DEPTH_16BIT: loopStart=s->loopStart/2; loopEnd=(s->loopEnd-s->loopStart)/2; break; + default: break; + } } - loopEnd=MIN(loopEnd,0x3fffff); - loopStart=MIN(loopStart,0xffff); - rWrite16(0x0a+i*16,loopStart&0xffff); - rWrite32(0x0c+i*16,loopEnd&0x3fffff); + loopEnd=CLAMP(loopEnd,0,0x3fffff); + loopStart=CLAMP(loopStart,0,0xffff); + rWrite16(0x0a+i*16,loopStart); + rWrite32(0x0c+i*16,loopEnd); } else { - end=MIN(end,0x3fffff); + end=CLAMP(end,0,0x3fffff); rWrite16(0x0a+i*16,0); rWrite32(0x0c+i*16,end&0x3fffff); } @@ -486,7 +509,7 @@ void DivPlatformNDS::renderSamples(int sysID) { continue; } - int length=s->getCurBufLen(); + int length=MIN(16777215,s->getCurBufLen()); unsigned char* src=(unsigned char*)s->getCurBuf(); int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length); if (actualLength>0) { @@ -508,7 +531,7 @@ void DivPlatformNDS::renderSamples(int sysID) { void DivPlatformNDS::setFlags(const DivConfig& flags) { isDSi=flags.getBool("chipType",0); chipClock=33513982; - rate=chipClock/2/SAMPLE_DIVIDER; + rate=chipClock/2/CLOCK_DIVIDER; for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } From 9d19a1735ade1eb1effda126e2b0aaa03139c9d1 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 16:51:37 +0900 Subject: [PATCH 13/26] Fix placeholder for ADPCM --- src/engine/platform/nds.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index 1e36ea640..31a94448d 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -175,7 +175,7 @@ void DivPlatformNDS::tick(bool sysTick) { } if (chan[i].audPos>0) { switch (s->depth) { - //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: start+=chan[i].audPos/2; break; + //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: start+=chan[i].audPos/2; end-=(chan[i].audPos/8); break; case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; end-=(chan[i].audPos/4); break; case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; end-=(chan[i].audPos/2); break; default: break; @@ -184,7 +184,16 @@ void DivPlatformNDS::tick(bool sysTick) { if (s->isLoopable()) { if (chan[i].audPos>0) { switch (s->depth) { - //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; + /* + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + loopStart=(s->loopStart-chan[i].audPos)/8; + loopEnd=(s->loopEnd-s->loopStart)/8; + if (chan[i].audPos>(unsigned int)s->loopStart) { + loopStart=0; + loopEnd-=(chan[i].audPos-s->loopStart)/8; + } + break; + */ case DIV_SAMPLE_DEPTH_8BIT: loopStart=(s->loopStart-chan[i].audPos)/4; loopEnd=(s->loopEnd-s->loopStart)/4; From 153ceea3bd3b71ecc3a8782d57155e50f0b64869 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 11 Feb 2024 17:28:17 +0900 Subject: [PATCH 14/26] Add global volume command --- src/engine/platform/nds.cpp | 10 +++++++++- src/engine/platform/nds.h | 1 + src/engine/sysDef.cpp | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index 31a94448d..b39e99993 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -307,6 +307,13 @@ int DivPlatformNDS::dispatch(DivCommand c) { chan[c.chan].ins=c.value; } break; + case DIV_CMD_ADPCMA_GLOBAL_VOLUME: { + if (globalVolume!=(c.value&0x7f)) { + globalVolume=c.value&0x7f; + rWrite32(0x100,0x8000|globalVolume); + } + break; + } case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; @@ -435,7 +442,8 @@ DivDispatchOscBuffer* DivPlatformNDS::getOscBuffer(int ch) { void DivPlatformNDS::reset() { memset(regPool,0,288); nds.reset(); - rWrite32(0x100,0x807f); // enable keyon + globalVolume=0x7f; + rWrite32(0x100,0x8000|globalVolume); // enable keyon rWrite32(0x104,0x200); // initialize bias for (int i=0; i<16; i++) { chan[i]=DivPlatformNDS::Channel(); diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h index cf846d847..5e2850618 100644 --- a/src/engine/platform/nds.h +++ b/src/engine/platform/nds.h @@ -47,6 +47,7 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf { DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; bool isDSi; + int globalVolume; unsigned int sampleOff[256]; bool sampleLoaded[256]; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3cc1796f2..45d08a687 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2031,6 +2031,7 @@ void DivEngine::registerSystems() { {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, { {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle (pulse: 0 to 7)"}}, + {0x1f, {DIV_CMD_ADPCMA_GLOBAL_VOLUME, "1Fxx: Set global volume (0 to 7F)"}}, } ); From c3c50e3ccfd8858f76dd36bf9261b6b88582ad77 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 12 Feb 2024 12:15:44 +0900 Subject: [PATCH 15/26] Fix frequency effect --- src/engine/platform/nds.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index b39e99993..c779e1038 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -163,6 +163,8 @@ void DivPlatformNDS::tick(bool sysTick) { chan[i].freq=0x10000-(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; + if ((!chan[i].keyOn) && ((rRead8(0x03+i*16)&0x80)==0)) + chan[i].active=false; ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x08:0x10); if (chan[i].keyOn) { unsigned int start=0; From b868e37c4221e1fbb8aad9ebeeaf1fdb0619cb1a Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 12 Feb 2024 12:54:07 +0900 Subject: [PATCH 16/26] Fix phase reset macro --- src/engine/platform/nds.cpp | 8 ++++++-- src/engine/platform/nds.h | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index c779e1038..c3976d7db 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -140,6 +140,8 @@ void DivPlatformNDS::tick(bool sysTick) { if ((chan[i].std.phaseReset.val==1) && chan[i].active) { chan[i].audPos=0; chan[i].setPos=true; + if ((rRead8(0x03+i*16)&0x80)==0) + chan[i].busy=true; } } if (chan[i].setPos) { @@ -164,8 +166,8 @@ void DivPlatformNDS::tick(bool sysTick) { if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; if ((!chan[i].keyOn) && ((rRead8(0x03+i*16)&0x80)==0)) - chan[i].active=false; - ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x08:0x10); + chan[i].busy=false; + ctrl|=(chan[i].busy?0x80:0)|((s->isLoopable())?0x08:0x10); if (chan[i].keyOn) { unsigned int start=0; int loopStart=0; @@ -287,6 +289,7 @@ int DivPlatformNDS::dispatch(DivCommand c) { chan[c.chan].note=c.value; } chan[c.chan].active=true; + chan[c.chan].busy=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { @@ -297,6 +300,7 @@ int DivPlatformNDS::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].sample=-1; chan[c.chan].active=false; + chan[c.chan].busy=false; chan[c.chan].keyOff=true; chan[c.chan].macroInit(NULL); break; diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h index 5e2850618..45adc8920 100644 --- a/src/engine/platform/nds.h +++ b/src/engine/platform/nds.h @@ -30,7 +30,7 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf { unsigned int audPos; int sample, wave; int panning, duty; - bool setPos, pcm; + bool setPos, pcm, busy; int macroVolMul; Channel(): SharedChannel(127), @@ -41,6 +41,7 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf { duty(0), setPos(false), pcm(false), + busy(false), macroVolMul(64) {} }; Channel chan[16]; From 4822e0d06023a46adeba7276058d0bae7a7bff7a Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 12 Feb 2024 22:41:19 +0900 Subject: [PATCH 17/26] align to 4 --- src/engine/platform/nds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index c3976d7db..d002ed419 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -532,7 +532,7 @@ void DivPlatformNDS::renderSamples(int sysID) { continue; } - int length=MIN(16777215,s->getCurBufLen()); + int length=MIN(16777216,s->getCurBufLen()); unsigned char* src=(unsigned char*)s->getCurBuf(); int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length); if (actualLength>0) { From d5ea50f8dc27f6b6cac7e43727e84e3a53b354bc Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 13 Feb 2024 12:45:41 +0900 Subject: [PATCH 18/26] Fix masking --- src/engine/platform/nds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index d002ed419..ca203b25d 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -532,7 +532,7 @@ void DivPlatformNDS::renderSamples(int sysID) { continue; } - int length=MIN(16777216,s->getCurBufLen()); + int length=MIN(16777212,s->getCurBufLen()); unsigned char* src=(unsigned char*)s->getCurBuf(); int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length); if (actualLength>0) { From 0cd7ed84f9b9823e6bd2ff226862bf091fdc4849 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 27 Feb 2024 16:15:58 +0900 Subject: [PATCH 19/26] Reduce duplicated condition --- src/engine/platform/nds.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index ca203b25d..615c05a6d 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -283,8 +283,6 @@ int DivPlatformNDS::dispatch(DivCommand c) { } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - } - if (c.value!=DIV_NOTE_NULL) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } From f46cd60f08e2bb0216d4e8e22582947beef4df54 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 6 Mar 2024 11:39:53 +0900 Subject: [PATCH 20/26] Sync with master, Fix crash --- src/engine/platform/nds.cpp | 14 +++++++++++++- src/engine/platform/nds.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index 615c05a6d..006b3f1dc 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -517,11 +517,19 @@ bool DivPlatformNDS::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformNDS::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformNDS::renderSamples(int sysID) { memset(sampleMem,0,16777216); memset(sampleOff,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Main Memory"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -536,7 +544,8 @@ void DivPlatformNDS::renderSamples(int sysID) { if (actualLength>0) { memcpy(&sampleMem[memPos],src,actualLength); sampleOff[i]=memPos; - memPos+=length; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength)); + memPos+=actualLength; } if (actualLength Date: Sat, 16 Mar 2024 17:58:13 -0500 Subject: [PATCH 21/26] fix initial panning --- src/engine/platform/nds.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h index 5c657fbc2..ec4a84796 100644 --- a/src/engine/platform/nds.h +++ b/src/engine/platform/nds.h @@ -37,7 +37,7 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf { audPos(0), sample(-1), wave(-1), - panning(8), + panning(64), duty(0), setPos(false), pcm(false), From be3240d9e649e02131bb8e4b1037cde9cf1d839f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 03:58:58 -0500 Subject: [PATCH 22/26] use NDS icon --- src/gui/guiConst.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 112b3665f..daca077ba 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -179,7 +179,7 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"PowerNoise (noise)",ICON_FUR_NOISE,ICON_FUR_INS_POWERNOISE}, {"PowerNoise (slope)",ICON_FUR_SAW,ICON_FUR_INS_POWERNOISE_SAW}, {"Dave",ICON_FA_BAR_CHART,ICON_FUR_INS_DAVE}, - {"NDS",ICON_FA_BAR_CHART,ICON_FUR_INS_DAVE},/* placeholder */ + {"NDS",ICON_FA_BAR_CHART,ICON_FUR_INS_NDS}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; From 8b3c48d42ee59994ba855bf2b060b5ebdbe1c73a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 11:19:26 -0500 Subject: [PATCH 23/26] description --- 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 45d08a687..8e1a2c38e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2023,7 +2023,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_NDS]=new DivSysDef( "NDS", NULL, 0xd6, 0, 16, false, true, 0, false, (1U< Date: Sun, 17 Mar 2024 12:24:19 -0500 Subject: [PATCH 24/26] prepare for IMA ADPCM --- papers/format.md | 4 ++++ papers/newIns.md | 2 ++ src/engine/sample.cpp | 28 ++++++++++++++++++++++++++++ src/engine/sample.h | 7 ++++++- src/engine/sysDef.cpp | 2 +- src/gui/guiConst.cpp | 2 +- 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/papers/format.md b/papers/format.md index 97c1f1302..e28a14773 100644 --- a/papers/format.md +++ b/papers/format.md @@ -564,9 +564,13 @@ size | description | - 4: QSound ADPCM | - 5: ADPCM-A | - 6: ADPCM-B + | - 7: K05 ADPCM | - 8: 8-bit PCM | - 9: BRR (SNES) | - 10: VOX + | - 11: 8-bit μ-law PCM + | - 12: C219 PCM + | - 13: IMA ADPCM | - 16: 16-bit PCM 1 | loop direction (>=123) or reserved | - 0: forward diff --git a/papers/newIns.md b/papers/newIns.md index d301d27c2..f37a54deb 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -124,6 +124,8 @@ the following instrument types are available: - 55: ESFM - 56: PowerNoise (noise) - 57: PowerNoise (slope) +- 58: Dave +- 59: NDS the following feature codes are recognized: diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index bb8a21d3f..8ce8dd16f 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -279,6 +279,9 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { case DIV_SAMPLE_DEPTH_C219: off=offset; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + off=(offset+1)/2; + break; case DIV_SAMPLE_DEPTH_16BIT: off=offset*2; break; @@ -338,6 +341,10 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { off=offset; len=length; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + off=(offset+1)/2; + len=(length+1)/2; + break; case DIV_SAMPLE_DEPTH_16BIT: off=offset*2; len=length*2; @@ -396,6 +403,9 @@ int DivSample::getEndPosition(DivSampleDepth depth) { case DIV_SAMPLE_DEPTH_C219: off=lengthC219; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + off=lengthIMA; + break; case DIV_SAMPLE_DEPTH_16BIT: off=length16; break; @@ -587,6 +597,12 @@ bool DivSample::initInternal(DivSampleDepth d, int count) { dataC219=new unsigned char[(count+4095)&(~0xfff)]; memset(dataC219,0,(count+4095)&(~0xfff)); break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM + if (dataIMA!=NULL) delete[] dataIMA; + lengthIMA=(count+1)/2; + dataIMA=new unsigned char[lengthIMA]; + memset(dataIMA,0,lengthIMA); + break; case DIV_SAMPLE_DEPTH_16BIT: // 16-bit if (data16!=NULL) delete[] data16; length16=count*2; @@ -1271,6 +1287,9 @@ void DivSample::render(unsigned int formatMask) { if (dataC219[i]&0x80) data16[i]=-data16[i]; } break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM + // TODO: decode + break; default: return; } @@ -1442,6 +1461,10 @@ void DivSample::render(unsigned int formatMask) { dataC219[i]=x|(negate?0x80:0); } } + if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_IMA_ADPCM)) { // C219 + if (!initInternal(DIV_SAMPLE_DEPTH_IMA_ADPCM,samples)) return; + // TODO: encode + } } void* DivSample::getCurBuf() { @@ -1470,6 +1493,8 @@ void* DivSample::getCurBuf() { return dataMuLaw; case DIV_SAMPLE_DEPTH_C219: return dataC219; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + return dataIMA; case DIV_SAMPLE_DEPTH_16BIT: return data16; default: @@ -1504,6 +1529,8 @@ unsigned int DivSample::getCurBufLen() { return lengthMuLaw; case DIV_SAMPLE_DEPTH_C219: return lengthC219; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + return lengthIMA; case DIV_SAMPLE_DEPTH_16BIT: return length16; default: @@ -1616,4 +1643,5 @@ DivSample::~DivSample() { if (dataVOX) delete[] dataVOX; if (dataMuLaw) delete[] dataMuLaw; if (dataC219) delete[] dataC219; + if (dataIMA) delete[] dataIMA; } diff --git a/src/engine/sample.h b/src/engine/sample.h index 63d219259..cd4175e9d 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -46,6 +46,7 @@ enum DivSampleDepth: unsigned char { DIV_SAMPLE_DEPTH_VOX=10, DIV_SAMPLE_DEPTH_MULAW=11, DIV_SAMPLE_DEPTH_C219=12, + DIV_SAMPLE_DEPTH_IMA_ADPCM=13, DIV_SAMPLE_DEPTH_16BIT=16, DIV_SAMPLE_DEPTH_MAX // boundary for sample depth }; @@ -114,6 +115,7 @@ struct DivSample { // - 10: VOX ADPCM // - 11: 8-bit µ-law PCM // - 12: C219 "µ-law" PCM + // - 13: IMA ADPCM // - 16: 16-bit PCM DivSampleDepth depth; bool loop, brrEmphasis, dither; @@ -139,8 +141,9 @@ struct DivSample { unsigned char* dataVOX; // 10 unsigned char* dataMuLaw; // 11 unsigned char* dataC219; // 12 + unsigned char* dataIMA; // 13 - unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219; + unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA; unsigned int samples; @@ -349,6 +352,7 @@ struct DivSample { dataVOX(NULL), dataMuLaw(NULL), dataC219(NULL), + dataIMA(NULL), length8(0), length16(0), length1(0), @@ -362,6 +366,7 @@ struct DivSample { lengthVOX(0), lengthMuLaw(0), lengthC219(0), + lengthIMA(0), samples(0) { for (int i=0; i Date: Sun, 17 Mar 2024 15:05:07 -0500 Subject: [PATCH 25/26] IMA DO MY ADPCM --- .gitmodules | 3 +++ CMakeLists.txt | 2 ++ extern/adpcm-xq | 1 + src/engine/platform/nds.cpp | 10 ++++------ src/engine/sample.cpp | 22 ++++++++++++++++++---- src/gui/about.cpp | 1 + src/main.cpp | 1 + 7 files changed, 30 insertions(+), 10 deletions(-) create mode 160000 extern/adpcm-xq diff --git a/.gitmodules b/.gitmodules index c78fee42a..498a32410 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,3 +15,6 @@ [submodule "extern/portaudio"] path = extern/portaudio url = https://github.com/PortAudio/portaudio.git +[submodule "extern/adpcm-xq"] + path = extern/adpcm-xq + url = https://github.com/dbry/adpcm-xq.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 8425a78d3..2779c8b54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -485,6 +485,8 @@ extern/adpcm/yma_codec.c extern/adpcm/ymb_codec.c extern/adpcm/ymz_codec.c +extern/adpcm-xq/adpcm-lib.c + extern/opn/ym3438.c extern/Nuked-PSG/ympsg.c extern/opm/opm.c diff --git a/extern/adpcm-xq b/extern/adpcm-xq new file mode 160000 index 000000000..6220fed76 --- /dev/null +++ b/extern/adpcm-xq @@ -0,0 +1 @@ +Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index 006b3f1dc..b52980198 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -156,7 +156,7 @@ void DivPlatformNDS::tick(bool sysTick) { if (chan[i].pcm || i<8) { DivSample* s=parent->getSample(chan[i].sample); switch (s->depth) { - //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: ctrl=0x40; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: ctrl=0x40; break; case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x00; break; case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x20; break; default: break; @@ -179,7 +179,7 @@ void DivPlatformNDS::tick(bool sysTick) { } if (chan[i].audPos>0) { switch (s->depth) { - //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: start+=chan[i].audPos/2; end-=(chan[i].audPos/8); break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: start+=chan[i].audPos/2; end-=(chan[i].audPos/8); break; case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; end-=(chan[i].audPos/4); break; case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; end-=(chan[i].audPos/2); break; default: break; @@ -188,8 +188,7 @@ void DivPlatformNDS::tick(bool sysTick) { if (s->isLoopable()) { if (chan[i].audPos>0) { switch (s->depth) { - /* - case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + case DIV_SAMPLE_DEPTH_IMA_ADPCM: loopStart=(s->loopStart-chan[i].audPos)/8; loopEnd=(s->loopEnd-s->loopStart)/8; if (chan[i].audPos>(unsigned int)s->loopStart) { @@ -197,7 +196,6 @@ void DivPlatformNDS::tick(bool sysTick) { loopEnd-=(chan[i].audPos-s->loopStart)/8; } break; - */ case DIV_SAMPLE_DEPTH_8BIT: loopStart=(s->loopStart-chan[i].audPos)/4; loopEnd=(s->loopEnd-s->loopStart)/4; @@ -218,7 +216,7 @@ void DivPlatformNDS::tick(bool sysTick) { } } else { switch (s->depth) { - //case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; case DIV_SAMPLE_DEPTH_8BIT: loopStart=s->loopStart/4; loopEnd=(s->loopEnd-s->loopStart)/4; break; case DIV_SAMPLE_DEPTH_16BIT: loopStart=s->loopStart/2; loopEnd=(s->loopEnd-s->loopStart)/2; break; default: break; diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 8ce8dd16f..17340f551 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -35,6 +35,7 @@ extern "C" { #include "../../extern/adpcm/ymb_codec.h" #include "../../extern/adpcm/ymz_codec.h" } +#include "../../extern/adpcm-xq/adpcm-lib.h" #include "brrUtils.h" DivSampleHistory::~DivSampleHistory() { @@ -599,7 +600,7 @@ bool DivSample::initInternal(DivSampleDepth d, int count) { break; case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM if (dataIMA!=NULL) delete[] dataIMA; - lengthIMA=(count+1)/2; + lengthIMA=4+((count+1)/2); dataIMA=new unsigned char[lengthIMA]; memset(dataIMA,0,lengthIMA); break; @@ -1288,7 +1289,7 @@ void DivSample::render(unsigned int formatMask) { } break; case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM - // TODO: decode + if (adpcm_decode_block(data16,dataIMA,lengthIMA,1)==0) logE("oh crap!"); break; default: return; @@ -1461,9 +1462,22 @@ void DivSample::render(unsigned int formatMask) { dataC219[i]=x|(negate?0x80:0); } } - if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_IMA_ADPCM)) { // C219 + if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_IMA_ADPCM)) { // IMA ADPCM if (!initInternal(DIV_SAMPLE_DEPTH_IMA_ADPCM,samples)) return; - // TODO: encode + int delta[2]; + delta[0]=0; + delta[1]=0; + + void* codec=adpcm_create_context(1,4,NOISE_SHAPING_OFF,delta); + if (codec==NULL) { + logE("oh no IMA encoder could not be created!"); + } else { + size_t whyPointer=0; + adpcm_encode_block(codec,dataIMA,&whyPointer,data16,samples); + if (whyPointer!=lengthIMA) logW("IMA length mismatch! %d -> %d!=%d",(int)samples,(int)whyPointer,(int)lengthIMA); + + adpcm_free_context(codec); + } } } diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 30d891042..21ebc3873 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -214,6 +214,7 @@ const char* aboutLine[]={ "FFTW by Matteo Frigo and Steven G. Johnson", "backward-cpp by Google", "adpcm by superctr", + "adpcm-xq by David Bryant", "Nuked-OPL3/OPLL/OPM/OPN2/PSG by nukeykt", "YM3812-LLE, YMF262-LLE and YMF276-LLE by nukeykt", "ESFMu (modified version) by Kagamiin~", diff --git a/src/main.cpp b/src/main.cpp index 4f36135dd..a954759a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -215,6 +215,7 @@ TAParamResult pVersion(String) { printf("- ESFMu (modified version) by Kagamiin~ (LGPLv2.1)\n"); printf("- ymfm by Aaron Giles (BSD 3-clause)\n"); printf("- adpcm by superctr (public domain)\n"); + printf("- adpcm-xq by David Bryant (BSD 3-clause)\n"); printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n"); printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n"); printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n"); From 42ccba822ccf1630cd7ba1462c33792bf9a36f78 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 15:39:52 -0500 Subject: [PATCH 26/26] why is text export in .dmf --- CMakeLists.txt | 1 + src/engine/fileOps/dmf.cpp | 352 ---------------------------------- src/engine/fileOps/text.cpp | 372 ++++++++++++++++++++++++++++++++++++ 3 files changed, 373 insertions(+), 352 deletions(-) create mode 100644 src/engine/fileOps/text.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2779c8b54..8bedba4ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -624,6 +624,7 @@ src/engine/fileOps/ftm.cpp src/engine/fileOps/fur.cpp src/engine/fileOps/mod.cpp src/engine/fileOps/s3m.cpp +src/engine/fileOps/text.cpp src/engine/blip_buf.c src/engine/brrUtils.c diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index d317aab8c..d226a54e8 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -1626,355 +1626,3 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { saveLock.unlock(); return w; } - -static const char* trueFalse[2]={ - "no", "yes" -}; - -static const char* gbEnvDir[2]={ - "down", "up" -}; - -static const char* notes[12]={ - "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" -}; - -static const char* notesNegative[12]={ - "c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_" -}; - -static const char* sampleLoopModes[4]={ - "forward", "backward", "ping-pong", "invalid" -}; - -void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) { - if ((m.open&6)==0 && m.len<1) return; - if (!wroteMacroHeader) { - w->writeText("- macros:\n"); - wroteMacroHeader=true; - } - w->writeText(fmt::sprintf(" - %s:",name)); - int len=m.len; - switch (m.open&6) { - case 2: - len=16; - w->writeText(" [ADSR]"); - break; - case 4: - len=16; - w->writeText(" [LFO]"); - break; - } - if (m.mode) { - w->writeText(fmt::sprintf(" [MODE %d]",m.mode)); - } - if (m.delay>0) { - w->writeText(fmt::sprintf(" [DELAY %d]",m.delay)); - } - if (m.speed>1) { - w->writeText(fmt::sprintf(" [SPEED %d]",m.speed)); - } - for (int i=0; iwriteText(" |"); - } - if (i==m.rel) { - w->writeText(" /"); - } - w->writeText(fmt::sprintf(" %d",m.val[i])); - } - w->writeText("\n"); -} - -SafeWriter* DivEngine::saveText(bool separatePatterns) { - saveLock.lock(); - - SafeWriter* w=new SafeWriter; - w->init(); - - w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION)); - w->writeText(fmt::sprintf("- name: %s\n",song.name)); - w->writeText(fmt::sprintf("- author: %s\n",song.author)); - w->writeText(fmt::sprintf("- album: %s\n",song.category)); - w->writeText(fmt::sprintf("- system: %s\n",song.systemName)); - w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning)); - - w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen)); - w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen)); - w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen)); - - w->writeText("# Sound Chips\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("- %s\n",getSystemName(song.system[i]))); - w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i])); - w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i])); - w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i])); - w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i])); - - String sysFlags=song.systemFlags[i].toString(); - - if (!sysFlags.empty()) { - w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags)); - } - } - - if (!song.notes.empty()) { - w->writeText("\n# Song Comments\n\n"); - w->writeText(song.notes); - } - - w->writeText("\n# Instruments\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name)); - - w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); - - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { - w->writeText("- FM parameters:\n"); - w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); - w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); - w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms)); - w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); - w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); - w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); - w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); - w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); - w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); - w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); - w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); - w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); - - for (int j=0; jfm.ops; j++) { - DivInstrumentFM::Operator& op=ins->fm.op[j]; - - w->writeText(fmt::sprintf(" - operator %d:\n",j)); - w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0])); - w->writeText(fmt::sprintf(" - AM: %d\n",op.am)); - w->writeText(fmt::sprintf(" - AR: %d\n",op.ar)); - w->writeText(fmt::sprintf(" - DR: %d\n",op.dr)); - w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult)); - w->writeText(fmt::sprintf(" - RR: %d\n",op.rr)); - w->writeText(fmt::sprintf(" - SL: %d\n",op.sl)); - w->writeText(fmt::sprintf(" - TL: %d\n",op.tl)); - w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2)); - w->writeText(fmt::sprintf(" - RS: %d\n",op.rs)); - w->writeText(fmt::sprintf(" - DT: %d\n",op.dt)); - w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r)); - w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv)); - w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam)); - w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb)); - w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt)); - w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl)); - w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus)); - w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib)); - w->writeText(fmt::sprintf(" - WS: %d\n",op.ws)); - w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr)); - w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs)); - } - } - - if (ins->type==DIV_INS_ESFM) { - w->writeText("- ESFM parameters:\n"); - w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); - - for (int j=0; jfm.ops; j++) { - DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; - - w->writeText(fmt::sprintf(" - operator %d:\n",j)); - w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay)); - w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl)); - w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn)); - w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0])); - w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0])); - w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct)); - w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt)); - w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0])); - } - } - - if (ins->type==DIV_INS_GB) { - w->writeText("- Game Boy parameters:\n"); - w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); - w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); - w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); - w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); - w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); - w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0])); - if (ins->gb.hwSeqLen>0) { - w->writeText(" - hardware sequence:\n"); - for (int j=0; jgb.hwSeqLen; j++) { - w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data)); - } - } - } - - bool header=false; - writeTextMacro(w,ins->std.volMacro,"vol",header); - writeTextMacro(w,ins->std.arpMacro,"arp",header); - writeTextMacro(w,ins->std.dutyMacro,"duty",header); - writeTextMacro(w,ins->std.waveMacro,"wave",header); - writeTextMacro(w,ins->std.pitchMacro,"pitch",header); - writeTextMacro(w,ins->std.panLMacro,"panL",header); - writeTextMacro(w,ins->std.panRMacro,"panR",header); - writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header); - writeTextMacro(w,ins->std.ex1Macro,"ex1",header); - writeTextMacro(w,ins->std.ex2Macro,"ex2",header); - writeTextMacro(w,ins->std.ex3Macro,"ex3",header); - writeTextMacro(w,ins->std.ex4Macro,"ex4",header); - writeTextMacro(w,ins->std.ex5Macro,"ex5",header); - writeTextMacro(w,ins->std.ex6Macro,"ex6",header); - writeTextMacro(w,ins->std.ex7Macro,"ex7",header); - writeTextMacro(w,ins->std.ex8Macro,"ex8",header); - writeTextMacro(w,ins->std.algMacro,"alg",header); - writeTextMacro(w,ins->std.fbMacro,"fb",header); - writeTextMacro(w,ins->std.fmsMacro,"fms",header); - writeTextMacro(w,ins->std.amsMacro,"ams",header); - - // TODO: the rest - w->writeText("\n"); - } - - w->writeText("\n# Wavetables\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1)); - for (int j=0; j<=wave->len; j++) { - w->writeText(fmt::sprintf(" %d",wave->data[j])); - } - w->writeText("\n"); - } - - w->writeText("\n# Samples\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name)); - - w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth)); - w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen())); - w->writeText(fmt::sprintf("- samples: %d\n",sample->samples)); - w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate)); - w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate)); - w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0])); - if (sample->loop) { - w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart)); - w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd)); - w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3])); - } - w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0])); - w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0])); - - // TODO' render matrix - unsigned char* buf=(unsigned char*)sample->getCurBuf(); - unsigned int bufLen=sample->getCurBufLen(); - w->writeText("\n```"); - for (unsigned int i=0; iwriteText(fmt::sprintf("\n%.8X:",i)); - w->writeText(fmt::sprintf(" %.2X",buf[i])); - } - w->writeText("\n```\n\n"); - } - - w->writeText("\n# Subsongs\n\n"); - - for (size_t i=0; iwriteText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name)); - - w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz)); - w->writeText(fmt::sprintf("- speeds:")); - for (int j=0; jspeeds.len; j++) { - w->writeText(fmt::sprintf(" %d",s->speeds.val[j])); - } - w->writeText("\n"); - w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD)); - w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase)); - w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen)); - w->writeText(fmt::sprintf("\norders:\n```\n")); - - for (int j=0; jordersLen; j++) { - w->writeText(fmt::sprintf("%.2X |",j)); - for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); - } - w->writeText("\n"); - } - w->writeText("```\n\n## Patterns\n\n"); - - if (separatePatterns) { - w->writeText("TODO: separate patterns\n\n"); - } else { - for (int j=0; jordersLen; j++) { - w->writeText(fmt::sprintf("----- ORDER %.2X\n",j)); - - for (int k=0; kpatLen; k++) { - w->writeText(fmt::sprintf("%.2X ",k)); - - for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); - - int note=p->data[k][0]; - int octave=p->data[k][1]; - - if (note==0 && octave==0) { - w->writeText("|... "); - } else if (note==100) { - w->writeText("|OFF "); - } else if (note==101) { - w->writeText("|=== "); - } else if (note==102) { - w->writeText("|REL "); - } else if ((octave>9 && octave<250) || note>12) { - w->writeText("|??? "); - } else { - if (octave>=128) octave-=256; - if (note>11) { - note-=12; - octave++; - } - w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave)); - } - - if (p->data[k][2]==-1) { - w->writeText(".. "); - } else { - w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff)); - } - - if (p->data[k][3]==-1) { - w->writeText(".."); - } else { - w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff)); - } - - for (int m=0; mpat[l].effectCols; m++) { - if (p->data[k][4+(m<<1)]==-1) { - w->writeText(" .."); - } else { - w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff)); - } - if (p->data[k][5+(m<<1)]==-1) { - w->writeText(".."); - } else { - w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff)); - } - } - } - - w->writeText("\n"); - } - } - } - - } - - saveLock.unlock(); - return w; -} diff --git a/src/engine/fileOps/text.cpp b/src/engine/fileOps/text.cpp new file mode 100644 index 000000000..6ededd1fd --- /dev/null +++ b/src/engine/fileOps/text.cpp @@ -0,0 +1,372 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 "fileOpsCommon.h" + +static const char* trueFalse[2]={ + "no", "yes" +}; + +static const char* gbEnvDir[2]={ + "down", "up" +}; + +static const char* notes[12]={ + "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" +}; + +static const char* notesNegative[12]={ + "c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_" +}; + +static const char* sampleLoopModes[4]={ + "forward", "backward", "ping-pong", "invalid" +}; + +void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) { + if ((m.open&6)==0 && m.len<1) return; + if (!wroteMacroHeader) { + w->writeText("- macros:\n"); + wroteMacroHeader=true; + } + w->writeText(fmt::sprintf(" - %s:",name)); + int len=m.len; + switch (m.open&6) { + case 2: + len=16; + w->writeText(" [ADSR]"); + break; + case 4: + len=16; + w->writeText(" [LFO]"); + break; + } + if (m.mode) { + w->writeText(fmt::sprintf(" [MODE %d]",m.mode)); + } + if (m.delay>0) { + w->writeText(fmt::sprintf(" [DELAY %d]",m.delay)); + } + if (m.speed>1) { + w->writeText(fmt::sprintf(" [SPEED %d]",m.speed)); + } + for (int i=0; iwriteText(" |"); + } + if (i==m.rel) { + w->writeText(" /"); + } + w->writeText(fmt::sprintf(" %d",m.val[i])); + } + w->writeText("\n"); +} + +SafeWriter* DivEngine::saveText(bool separatePatterns) { + saveLock.lock(); + + SafeWriter* w=new SafeWriter; + w->init(); + + w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION)); + w->writeText(fmt::sprintf("- name: %s\n",song.name)); + w->writeText(fmt::sprintf("- author: %s\n",song.author)); + w->writeText(fmt::sprintf("- album: %s\n",song.category)); + w->writeText(fmt::sprintf("- system: %s\n",song.systemName)); + w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning)); + + w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen)); + w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen)); + w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen)); + + w->writeText("# Sound Chips\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("- %s\n",getSystemName(song.system[i]))); + w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i])); + w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i])); + w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i])); + w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i])); + + String sysFlags=song.systemFlags[i].toString(); + + if (!sysFlags.empty()) { + w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags)); + } + } + + if (!song.notes.empty()) { + w->writeText("\n# Song Comments\n\n"); + w->writeText(song.notes); + } + + w->writeText("\n# Instruments\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name)); + + w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { + w->writeText("- FM parameters:\n"); + w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); + w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); + w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms)); + w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); + w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); + w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); + w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); + w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); + w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); + w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); + w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); + w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); + + for (int j=0; jfm.ops; j++) { + DivInstrumentFM::Operator& op=ins->fm.op[j]; + + w->writeText(fmt::sprintf(" - operator %d:\n",j)); + w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0])); + w->writeText(fmt::sprintf(" - AM: %d\n",op.am)); + w->writeText(fmt::sprintf(" - AR: %d\n",op.ar)); + w->writeText(fmt::sprintf(" - DR: %d\n",op.dr)); + w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult)); + w->writeText(fmt::sprintf(" - RR: %d\n",op.rr)); + w->writeText(fmt::sprintf(" - SL: %d\n",op.sl)); + w->writeText(fmt::sprintf(" - TL: %d\n",op.tl)); + w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2)); + w->writeText(fmt::sprintf(" - RS: %d\n",op.rs)); + w->writeText(fmt::sprintf(" - DT: %d\n",op.dt)); + w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r)); + w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv)); + w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam)); + w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb)); + w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt)); + w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl)); + w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus)); + w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib)); + w->writeText(fmt::sprintf(" - WS: %d\n",op.ws)); + w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr)); + w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs)); + } + } + + if (ins->type==DIV_INS_ESFM) { + w->writeText("- ESFM parameters:\n"); + w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); + + for (int j=0; jfm.ops; j++) { + DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; + + w->writeText(fmt::sprintf(" - operator %d:\n",j)); + w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay)); + w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl)); + w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn)); + w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0])); + w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0])); + w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct)); + w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt)); + w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0])); + } + } + + if (ins->type==DIV_INS_GB) { + w->writeText("- Game Boy parameters:\n"); + w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); + w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); + w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); + w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); + w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); + w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0])); + if (ins->gb.hwSeqLen>0) { + w->writeText(" - hardware sequence:\n"); + for (int j=0; jgb.hwSeqLen; j++) { + w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data)); + } + } + } + + bool header=false; + writeTextMacro(w,ins->std.volMacro,"vol",header); + writeTextMacro(w,ins->std.arpMacro,"arp",header); + writeTextMacro(w,ins->std.dutyMacro,"duty",header); + writeTextMacro(w,ins->std.waveMacro,"wave",header); + writeTextMacro(w,ins->std.pitchMacro,"pitch",header); + writeTextMacro(w,ins->std.panLMacro,"panL",header); + writeTextMacro(w,ins->std.panRMacro,"panR",header); + writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header); + writeTextMacro(w,ins->std.ex1Macro,"ex1",header); + writeTextMacro(w,ins->std.ex2Macro,"ex2",header); + writeTextMacro(w,ins->std.ex3Macro,"ex3",header); + writeTextMacro(w,ins->std.ex4Macro,"ex4",header); + writeTextMacro(w,ins->std.ex5Macro,"ex5",header); + writeTextMacro(w,ins->std.ex6Macro,"ex6",header); + writeTextMacro(w,ins->std.ex7Macro,"ex7",header); + writeTextMacro(w,ins->std.ex8Macro,"ex8",header); + writeTextMacro(w,ins->std.algMacro,"alg",header); + writeTextMacro(w,ins->std.fbMacro,"fb",header); + writeTextMacro(w,ins->std.fmsMacro,"fms",header); + writeTextMacro(w,ins->std.amsMacro,"ams",header); + + // TODO: the rest + w->writeText("\n"); + } + + w->writeText("\n# Wavetables\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1)); + for (int j=0; j<=wave->len; j++) { + w->writeText(fmt::sprintf(" %d",wave->data[j])); + } + w->writeText("\n"); + } + + w->writeText("\n# Samples\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name)); + + w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth)); + w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen())); + w->writeText(fmt::sprintf("- samples: %d\n",sample->samples)); + w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate)); + w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate)); + w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0])); + if (sample->loop) { + w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart)); + w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd)); + w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3])); + } + w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0])); + w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0])); + + // TODO' render matrix + unsigned char* buf=(unsigned char*)sample->getCurBuf(); + unsigned int bufLen=sample->getCurBufLen(); + w->writeText("\n```"); + for (unsigned int i=0; iwriteText(fmt::sprintf("\n%.8X:",i)); + w->writeText(fmt::sprintf(" %.2X",buf[i])); + } + w->writeText("\n```\n\n"); + } + + w->writeText("\n# Subsongs\n\n"); + + for (size_t i=0; iwriteText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name)); + + w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz)); + w->writeText(fmt::sprintf("- speeds:")); + for (int j=0; jspeeds.len; j++) { + w->writeText(fmt::sprintf(" %d",s->speeds.val[j])); + } + w->writeText("\n"); + w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD)); + w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase)); + w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen)); + w->writeText(fmt::sprintf("\norders:\n```\n")); + + for (int j=0; jordersLen; j++) { + w->writeText(fmt::sprintf("%.2X |",j)); + for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); + } + w->writeText("\n"); + } + w->writeText("```\n\n## Patterns\n\n"); + + if (separatePatterns) { + w->writeText("TODO: separate patterns\n\n"); + } else { + for (int j=0; jordersLen; j++) { + w->writeText(fmt::sprintf("----- ORDER %.2X\n",j)); + + for (int k=0; kpatLen; k++) { + w->writeText(fmt::sprintf("%.2X ",k)); + + for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); + + int note=p->data[k][0]; + int octave=p->data[k][1]; + + if (note==0 && octave==0) { + w->writeText("|... "); + } else if (note==100) { + w->writeText("|OFF "); + } else if (note==101) { + w->writeText("|=== "); + } else if (note==102) { + w->writeText("|REL "); + } else if ((octave>9 && octave<250) || note>12) { + w->writeText("|??? "); + } else { + if (octave>=128) octave-=256; + if (note>11) { + note-=12; + octave++; + } + w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave)); + } + + if (p->data[k][2]==-1) { + w->writeText(".. "); + } else { + w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff)); + } + + if (p->data[k][3]==-1) { + w->writeText(".."); + } else { + w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff)); + } + + for (int m=0; mpat[l].effectCols; m++) { + if (p->data[k][4+(m<<1)]==-1) { + w->writeText(" .."); + } else { + w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff)); + } + if (p->data[k][5+(m<<1)]==-1) { + w->writeText(".."); + } else { + w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff)); + } + } + } + + w->writeText("\n"); + } + } + } + + } + + saveLock.unlock(); + return w; +}