From 60a52d3b9f0b29173c10d74c880c70f7696d0f7d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Jul 2022 00:06:04 -0500 Subject: [PATCH 01/93] Revert "Revert "Fix issue #567: LFO disable/enable behavior for YM2151."" --- src/engine/platform/arcade.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 41042cd5..29a2f67d 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -172,7 +172,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si w.addrOrVal=true; } } - + OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); @@ -182,13 +182,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si for (int i=0; i<8; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]; } - + if (o[0]<-32768) o[0]=-32768; if (o[0]>32767) o[0]=32767; if (o[1]<-32768) o[1]=-32768; if (o[1]>32767) o[1]=32767; - + bufL[h]=o[0]; bufR[h]=o[1]; } @@ -211,7 +211,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz delay=1; } } - + fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { @@ -225,7 +225,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz os[1]=out_ymfm.data[1]; if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; - + bufL[h]=os[0]; bufR[h]=os[1]; } @@ -616,6 +616,12 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { + if(c.value==0) { + rWrite(0x01,0x02); + } + else { + rWrite(0x01,0x00); + } rWrite(0x18,c.value); break; } @@ -939,6 +945,8 @@ void DivPlatformArcade::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x01,0x02); // LFO Off + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); From e88e0a4e4ef6ece61866480f7c9463f4905d700c Mon Sep 17 00:00:00 2001 From: aurora Date: Mon, 22 Aug 2022 03:47:00 +0300 Subject: [PATCH 02/93] GUI: Remember window x/y position and maximized state. Warning: This may cause issues when windows are re-ordered. Is there a way to fix windows spawning outside of screen boundaries? --- src/gui/gui.cpp | 61 +++++++++++++++++++++++++++++++++++++------------ src/gui/gui.h | 8 ++++--- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9189618d..20c3fc58 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -862,7 +862,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { void FurnaceGUI::noteInput(int num, int key, int vol) { DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); - + prepareUndo(GUI_UNDO_PATTERN_EDIT); if (key==100) { // note off @@ -2103,7 +2103,7 @@ void FurnaceGUI::editOptions(bool topMenu) { snprintf(id,63,"%.2x##LatchFX",data); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } - + if (ImGui::Selectable(id,latchTarget==3,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=3; latchNibble=false; @@ -2176,7 +2176,7 @@ void FurnaceGUI::editOptions(bool topMenu) { doTranspose(transposeAmount,opMaskTransposeValue); ImGui::CloseCurrentPopup(); } - + ImGui::Separator(); if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); if (ImGui::BeginMenu("change instrument...")) { @@ -2303,7 +2303,7 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) { if (mobileUI!=enable || force) { if (!mobileUI && enable) { ImGui::SaveIniSettingsToDisk(finalLayoutPath); - } + } mobileUI=enable; if (mobileUI) { ImGui::GetIO().IniFilename=NULL; @@ -2311,7 +2311,7 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) { ImGui::GetIO().IniFilename=finalLayoutPath; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); } - } + } } int _processEvent(void* instance, SDL_Event* event) { @@ -2536,6 +2536,7 @@ bool FurnaceGUI::loop() { if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } eventTimeBegin=SDL_GetPerformanceCounter(); + bool updateWindow = false; while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); @@ -2642,6 +2643,20 @@ bool FurnaceGUI::loop() { scrW=ev.window.data1/dpiScale; scrH=ev.window.data2/dpiScale; #endif + updateWindow=true; + break; + case SDL_WINDOWEVENT_MOVED: + scrX=ev.window.data1; + scrY=ev.window.data2; + updateWindow=true; + break; + case SDL_WINDOWEVENT_MAXIMIZED: + scrMax=true; + updateWindow=true; + break; + case SDL_WINDOWEVENT_RESTORED: + scrMax=false; + updateWindow=true; break; } break; @@ -2697,6 +2712,18 @@ bool FurnaceGUI::loop() { } } + // update config x/y/w/h values based on scrMax state + if(updateWindow) { + if(scrMax) { + scrConfY=scrConfX = SDL_WINDOWPOS_CENTERED_DISPLAY(SDL_GetWindowDisplayIndex(sdlWin)); + } else { + scrConfX=scrX; + scrConfY=scrY; + scrConfW=scrW; + scrConfH=scrH; + } + } + wantCaptureKeyboard=ImGui::GetIO().WantTextInput; if (wantCaptureKeyboard!=oldWantCaptureKeyboard) { @@ -2714,7 +2741,7 @@ bool FurnaceGUI::loop() { if (ImGui::GetIO().MouseDown[0] || ImGui::GetIO().MouseDown[1] || ImGui::GetIO().MouseDown[2] || ImGui::GetIO().MouseDown[3] || ImGui::GetIO().MouseDown[4]) { WAKE_UP; } - + while (true) { midiLock.lock(); if (midiQueue.empty()) { @@ -2870,7 +2897,7 @@ bool FurnaceGUI::loop() { eventTimeEnd=SDL_GetPerformanceCounter(); layoutTimeBegin=SDL_GetPerformanceCounter(); - + ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); @@ -3125,7 +3152,7 @@ bool FurnaceGUI::loop() { 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 (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; - + ImGui::EndMenu(); } if (ImGui::BeginMenu("help")) { @@ -4263,7 +4290,7 @@ bool FurnaceGUI::loop() { } logD("saving backup..."); SafeWriter* w=e->saveFur(true); - + if (w!=NULL) { FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); if (outFile!=NULL) { @@ -4436,8 +4463,11 @@ bool FurnaceGUI::init() { SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000); #endif - scrW=e->getConfInt("lastWindowWidth",1280); - scrH=e->getConfInt("lastWindowHeight",800); + scrW=scrConfW=e->getConfInt("lastWindowWidth",1280); + scrH=scrConfH=e->getConfInt("lastWindowHeight",800); + scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED); + scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); + scrMax=e->getConfBool("lastWindowMax",false); #ifndef __APPLE__ SDL_Rect displaySize; @@ -4453,7 +4483,7 @@ bool FurnaceGUI::init() { SDL_Init(SDL_INIT_VIDEO); - sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)); + sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)); if (sdlWin==NULL) { logE("could not open window! %s",SDL_GetError()); return false; @@ -4619,8 +4649,11 @@ bool FurnaceGUI::finish() { e->setConf("spoilerOpen",spoilerOpen); // commit last window size - e->setConf("lastWindowWidth",scrW); - e->setConf("lastWindowHeight",scrH); + e->setConf("lastWindowWidth",scrConfW); + e->setConf("lastWindowHeight",scrConfH); + e->setConf("lastWindowX",scrConfX); + e->setConf("lastWindowY",scrConfY); + e->setConf("lastWindowMax",scrMax); e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); diff --git a/src/gui/gui.h b/src/gui/gui.h index 8a3548f2..7d42eb84 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -996,7 +996,9 @@ class FurnaceGUI { FurnaceGUIFileDialog* fileDialog; - int scrW, scrH; + int scrW, scrH, scrConfW, scrConfH; + int scrX, scrY, scrConfX, scrConfY; + bool scrMax; double dpiScale; @@ -1429,7 +1431,7 @@ class FurnaceGUI { int chanToMove; ImVec2 patWindowPos, patWindowSize; - + // pattern view specific ImVec2 fourChars, threeChars, twoChars; ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize; @@ -1505,7 +1507,7 @@ class FurnaceGUI { // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; - + // log window bool followLog; From 8b3c4a84a81a3c9d72c3c1d3624e006be6136e09 Mon Sep 17 00:00:00 2001 From: aurora Date: Mon, 22 Aug 2022 22:05:16 +0300 Subject: [PATCH 03/93] implement bounds check for window spawning --- src/gui/gui.cpp | 40 +++++++++++++++++++++++++++++++++++++--- src/gui/gui.h | 1 + 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 20c3fc58..204be1af 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2517,6 +2517,35 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { } } +// how many pixels should be visible at least at x/y dir +#define OOB_PIXELS_SAFETY 25 + +bool FurnaceGUI::detectOutOfBoundsWindow() { + int count = SDL_GetNumVideoDisplays(); + if(count < 1) { + logW("bounds check: error %s", SDL_GetError()); + return false; + } + + SDL_Rect rect; + for(int i = 0;i < count;i ++) { + if(SDL_GetDisplayUsableBounds(i, &rect) != 0) { + logW("bounds check: error %s", SDL_GetError()); + return false; + } + + bool xbound = (rect.x + OOB_PIXELS_SAFETY) <= (scrX + scrW) && (rect.x + rect.w - OOB_PIXELS_SAFETY) >= scrX; + bool ybound = (rect.y + OOB_PIXELS_SAFETY) <= (scrY + scrH) && (rect.y + rect.h - OOB_PIXELS_SAFETY) >= scrY; + logD("bounds check: display %d is at %dx%dx%dx%d: %s%s", i, rect.x + OOB_PIXELS_SAFETY, rect.y + OOB_PIXELS_SAFETY, rect.x + rect.w - OOB_PIXELS_SAFETY, rect.y + rect.h - OOB_PIXELS_SAFETY, xbound ? "x" : "", ybound ? "y" : ""); + + if(xbound && ybound) { + return true; + } + } + + return false; +} + bool FurnaceGUI::loop() { bool doThreadedInput=!settings.noThreadedInput; if (doThreadedInput) { @@ -2714,9 +2743,7 @@ bool FurnaceGUI::loop() { // update config x/y/w/h values based on scrMax state if(updateWindow) { - if(scrMax) { - scrConfY=scrConfX = SDL_WINDOWPOS_CENTERED_DISPLAY(SDL_GetWindowDisplayIndex(sdlWin)); - } else { + if(!scrMax) { scrConfX=scrX; scrConfY=scrY; scrConfW=scrW; @@ -4483,6 +4510,13 @@ bool FurnaceGUI::init() { SDL_Init(SDL_INIT_VIDEO); + // if window would spawn out of bounds, force it to be get default position + if(!detectOutOfBoundsWindow()) { + scrMax = false; + scrX=scrConfX=SDL_WINDOWPOS_CENTERED; + scrY=scrConfY=SDL_WINDOWPOS_CENTERED; + } + sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)); if (sdlWin==NULL) { logE("could not open window! %s",SDL_GetError()); diff --git a/src/gui/gui.h b/src/gui/gui.h index 7d42eb84..f4276f05 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1710,6 +1710,7 @@ class FurnaceGUI { void runBackupThread(); void pushPartBlend(); void popPartBlend(); + bool detectOutOfBoundsWindow(); int processEvent(SDL_Event* ev); bool loop(); bool finish(); From 0e847dc1aab974d8adf18ead6e73eda287f2919b Mon Sep 17 00:00:00 2001 From: aurora Date: Mon, 22 Aug 2022 22:17:19 +0300 Subject: [PATCH 04/93] add setting for choosing whether to save window position --- src/gui/gui.cpp | 4 ++-- src/gui/gui.h | 1 + src/gui/settings.cpp | 35 +++++++++++++++++++++++------------ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 204be1af..76e2b8a1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4685,8 +4685,8 @@ bool FurnaceGUI::finish() { // commit last window size e->setConf("lastWindowWidth",scrConfW); e->setConf("lastWindowHeight",scrConfH); - e->setConf("lastWindowX",scrConfX); - e->setConf("lastWindowY",scrConfY); + e->setConf("lastWindowX",settings.saveWindowPos?scrConfX:(int)SDL_WINDOWPOS_CENTERED); + e->setConf("lastWindowY",settings.saveWindowPos?scrConfY:(int)SDL_WINDOWPOS_CENTERED); e->setConf("lastWindowMax",scrMax); e->setConf("tempoView",tempoView); diff --git a/src/gui/gui.h b/src/gui/gui.h index f4276f05..21e4ecf5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1129,6 +1129,7 @@ class FurnaceGUI { int dragMovesSelection; int unsignedDetune; int noThreadedInput; + int saveWindowPos; int clampSamples; int saveUnusedPatterns; int channelColors; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 03c32e65..cb720da4 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -259,7 +259,7 @@ void FurnaceGUI::drawSettings() { } ImGui::Separator(); - + ImGui::Text("Initial system:"); ImGui::SameLine(); if (ImGui::Button("Current system")) { @@ -369,7 +369,7 @@ void FurnaceGUI::drawSettings() { } rightClickable ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); CWSliderScalar("Panning",ImGuiDataType_S8,&settings.initialSys[i+2],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable - + // oh please MSVC don't cry if (ImGui::TreeNode("Configure")) { drawSysConf(-1,(DivSystem)settings.initialSys[i],(unsigned int&)settings.initialSys[i+3],false); @@ -452,7 +452,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Double click selects entire column",&doubleClickColumnB)) { settings.doubleClickColumn=doubleClickColumnB; } - + bool allowEditDockingB=settings.allowEditDocking; if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { settings.allowEditDocking=allowEditDockingB; @@ -509,6 +509,14 @@ void FurnaceGUI::drawSettings() { ImGui::SetTooltip("threaded input processes key presses for note preview on a separate thread (on supported platforms), which reduces latency.\nhowever, crashes have been reported when threaded input is on. enable this option if that is the case."); } + bool saveWindowPosB=settings.saveWindowPos; + if (ImGui::Checkbox("Remember window location",&saveWindowPosB)) { + settings.saveWindowPos=saveWindowPosB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("remembers where window was last located on start-up. When disabled, window will start in a defaul location."); + } + bool blankInsB=settings.blankIns; if (ImGui::Checkbox("New instruments are blank",&blankInsB)) { settings.blankIns=blankInsB; @@ -639,7 +647,7 @@ void FurnaceGUI::drawSettings() { BUFFER_SIZE_SELECTABLE(2048); ImGui::EndCombo(); } - + ImGui::Text("Quality"); ImGui::SameLine(); ImGui::Combo("##Quality",&settings.audioQuality,audioQualities,2); @@ -697,7 +705,7 @@ void FurnaceGUI::drawSettings() { } if (hasToReloadMidi) { - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); } @@ -1344,7 +1352,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Unsigned FM detune values",&unsignedDetuneB)) { settings.unsignedDetune=unsignedDetuneB; } - + // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. /*bool sysSeparatorsB=settings.sysSeparators; if (ImGui::Checkbox("Add separators between systems in Orders",&sysSeparatorsB)) { @@ -1553,12 +1561,12 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_MOD,"Mod. accent (secondary)"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_MOD,"Mod. border"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_MOD,"Mod. border shadow"); - + UI_COLOR_CONFIG(GUI_COLOR_FM_PRIMARY_CAR,"Car. accent (primary"); UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_CAR,"Car. accent (secondary)"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_CAR,"Car. border"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_CAR,"Car. border shadow"); - + ImGui::TreePop(); } if (ImGui::TreeNode("Macro Editor")) { @@ -1917,7 +1925,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); - + // TODO: collapse/expand pattern and song KEYBIND_CONFIG_END; @@ -2213,6 +2221,7 @@ void FurnaceGUI::syncSettings() { settings.dragMovesSelection=e->getConfInt("dragMovesSelection",2); settings.unsignedDetune=e->getConfInt("unsignedDetune",0); settings.noThreadedInput=e->getConfInt("noThreadedInput",0); + settings.saveWindowPos=e->getConfInt("saveWindowPos",1); settings.initialSysName=e->getConfString("initialSysName",""); settings.clampSamples=e->getConfInt("clampSamples",0); settings.noteOffLabel=e->getConfString("noteOffLabel","OFF"); @@ -2313,6 +2322,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.dragMovesSelection,0,2); clampSetting(settings.unsignedDetune,0,1); clampSetting(settings.noThreadedInput,0,1); + clampSetting(settings.saveWindowPos,0,1); clampSetting(settings.clampSamples,0,1); clampSetting(settings.saveUnusedPatterns,0,1); clampSetting(settings.channelColors,0,2); @@ -2344,7 +2354,7 @@ void FurnaceGUI::syncSettings() { parseKeybinds(); - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); e->setMidiDirect(midiMap.directChannel); @@ -2458,6 +2468,7 @@ void FurnaceGUI::commitSettings() { e->setConf("dragMovesSelection",settings.dragMovesSelection); e->setConf("unsignedDetune",settings.unsignedDetune); e->setConf("noThreadedInput",settings.noThreadedInput); + e->setConf("saveWindowPos",settings.saveWindowPos); e->setConf("clampSamples",settings.clampSamples); e->setConf("noteOffLabel",settings.noteOffLabel); e->setConf("noteRelLabel",settings.noteRelLabel); @@ -3130,7 +3141,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { logE("could not load icon font!"); } - + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { logD("using main font for pat font."); patFont=mainFont; @@ -3164,7 +3175,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { } } } - + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { logE("could not load big UI font!"); } From bc0f696eb3743f233801bbd50a27d753fdb20fee Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Thu, 25 Aug 2022 18:55:32 +0200 Subject: [PATCH 05/93] Update soundunit.md fix 12xx description --- papers/doc/7-systems/soundunit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/soundunit.md b/papers/doc/7-systems/soundunit.md index 89e34655..8c3863b9 100644 --- a/papers/doc/7-systems/soundunit.md +++ b/papers/doc/7-systems/soundunit.md @@ -12,7 +12,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti - 5: periodic noise - 6: XOR sine - 7: XOR triangle -- `12xx`: set waveform (0 to 7F) +- `12xx`: set pulse width (0 to 7F) - `13xx`: set resonance of filter (0 to FF) - despite what the internal effects list says (0 to F), you can use a resonance value from 0 to FF (255) - `14xx`: set filter mode and ringmod From c3ced46fa3a3a4420a6a8f5d51ba3d1f2220d87a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 28 Aug 2022 15:10:16 -0500 Subject: [PATCH 06/93] coming soon: reSIDfp core --- CMakeLists.txt | 18 + src/engine/platform/sound/c64_fp/AUTHORS | 6 + src/engine/platform/sound/c64_fp/COPYING | 339 ++++++++++++ src/engine/platform/sound/c64_fp/Dac.cpp | 123 +++++ src/engine/platform/sound/c64_fp/Dac.h | 111 ++++ .../sound/c64_fp/EnvelopeGenerator.cpp | 155 ++++++ .../platform/sound/c64_fp/EnvelopeGenerator.h | 419 +++++++++++++++ .../platform/sound/c64_fp/ExternalFilter.cpp | 68 +++ .../platform/sound/c64_fp/ExternalFilter.h | 125 +++++ src/engine/platform/sound/c64_fp/Filter.cpp | 90 ++++ src/engine/platform/sound/c64_fp/Filter.h | 177 ++++++ .../platform/sound/c64_fp/Filter6581.cpp | 75 +++ src/engine/platform/sound/c64_fp/Filter6581.h | 425 +++++++++++++++ .../platform/sound/c64_fp/Filter8580.cpp | 101 ++++ src/engine/platform/sound/c64_fp/Filter8580.h | 383 +++++++++++++ .../sound/c64_fp/FilterModelConfig.cpp | 79 +++ .../platform/sound/c64_fp/FilterModelConfig.h | 166 ++++++ .../sound/c64_fp/FilterModelConfig6581.cpp | 263 +++++++++ .../sound/c64_fp/FilterModelConfig6581.h | 112 ++++ .../sound/c64_fp/FilterModelConfig8580.cpp | 222 ++++++++ .../sound/c64_fp/FilterModelConfig8580.h | 68 +++ .../platform/sound/c64_fp/Integrator6581.cpp | 25 + .../platform/sound/c64_fp/Integrator6581.h | 285 ++++++++++ .../platform/sound/c64_fp/Integrator8580.cpp | 25 + .../platform/sound/c64_fp/Integrator8580.h | 142 +++++ src/engine/platform/sound/c64_fp/OpAmp.cpp | 84 +++ src/engine/platform/sound/c64_fp/OpAmp.h | 113 ++++ .../platform/sound/c64_fp/Potentiometer.h | 50 ++ src/engine/platform/sound/c64_fp/README | 20 + src/engine/platform/sound/c64_fp/SID.cpp | 504 ++++++++++++++++++ src/engine/platform/sound/c64_fp/SID.h | 372 +++++++++++++ src/engine/platform/sound/c64_fp/Spline.cpp | 119 +++++ src/engine/platform/sound/c64_fp/Spline.h | 78 +++ src/engine/platform/sound/c64_fp/Voice.h | 130 +++++ .../sound/c64_fp/WaveformCalculator.cpp | 204 +++++++ .../sound/c64_fp/WaveformCalculator.h | 128 +++++ .../sound/c64_fp/WaveformGenerator.cpp | 357 +++++++++++++ .../platform/sound/c64_fp/WaveformGenerator.h | 396 ++++++++++++++ src/engine/platform/sound/c64_fp/array.h | 73 +++ .../sound/c64_fp/resample/Resampler.h | 86 +++ .../sound/c64_fp/resample/SincResampler.cpp | 393 ++++++++++++++ .../sound/c64_fp/resample/SincResampler.h | 114 ++++ .../c64_fp/resample/TwoPassSincResampler.h | 83 +++ .../c64_fp/resample/ZeroOrderResampler.h | 88 +++ .../platform/sound/c64_fp/resample/test.cpp | 87 +++ src/engine/platform/sound/c64_fp/sidcxx11.h | 29 + src/engine/platform/sound/c64_fp/sidcxx14.h | 29 + src/engine/platform/sound/c64_fp/siddefs-fp.h | 62 +++ src/engine/platform/sound/c64_fp/version.cc | 21 + src/gui/about.cpp | 2 + src/gui/gui.h | 2 + src/gui/settings.cpp | 12 + src/main.cpp | 1 + 53 files changed, 7639 insertions(+) create mode 100644 src/engine/platform/sound/c64_fp/AUTHORS create mode 100644 src/engine/platform/sound/c64_fp/COPYING create mode 100644 src/engine/platform/sound/c64_fp/Dac.cpp create mode 100644 src/engine/platform/sound/c64_fp/Dac.h create mode 100644 src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp create mode 100644 src/engine/platform/sound/c64_fp/EnvelopeGenerator.h create mode 100644 src/engine/platform/sound/c64_fp/ExternalFilter.cpp create mode 100644 src/engine/platform/sound/c64_fp/ExternalFilter.h create mode 100644 src/engine/platform/sound/c64_fp/Filter.cpp create mode 100644 src/engine/platform/sound/c64_fp/Filter.h create mode 100644 src/engine/platform/sound/c64_fp/Filter6581.cpp create mode 100644 src/engine/platform/sound/c64_fp/Filter6581.h create mode 100644 src/engine/platform/sound/c64_fp/Filter8580.cpp create mode 100644 src/engine/platform/sound/c64_fp/Filter8580.h create mode 100644 src/engine/platform/sound/c64_fp/FilterModelConfig.cpp create mode 100644 src/engine/platform/sound/c64_fp/FilterModelConfig.h create mode 100644 src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp create mode 100644 src/engine/platform/sound/c64_fp/FilterModelConfig6581.h create mode 100644 src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp create mode 100644 src/engine/platform/sound/c64_fp/FilterModelConfig8580.h create mode 100644 src/engine/platform/sound/c64_fp/Integrator6581.cpp create mode 100644 src/engine/platform/sound/c64_fp/Integrator6581.h create mode 100644 src/engine/platform/sound/c64_fp/Integrator8580.cpp create mode 100644 src/engine/platform/sound/c64_fp/Integrator8580.h create mode 100644 src/engine/platform/sound/c64_fp/OpAmp.cpp create mode 100644 src/engine/platform/sound/c64_fp/OpAmp.h create mode 100644 src/engine/platform/sound/c64_fp/Potentiometer.h create mode 100644 src/engine/platform/sound/c64_fp/README create mode 100644 src/engine/platform/sound/c64_fp/SID.cpp create mode 100644 src/engine/platform/sound/c64_fp/SID.h create mode 100644 src/engine/platform/sound/c64_fp/Spline.cpp create mode 100644 src/engine/platform/sound/c64_fp/Spline.h create mode 100644 src/engine/platform/sound/c64_fp/Voice.h create mode 100644 src/engine/platform/sound/c64_fp/WaveformCalculator.cpp create mode 100644 src/engine/platform/sound/c64_fp/WaveformCalculator.h create mode 100644 src/engine/platform/sound/c64_fp/WaveformGenerator.cpp create mode 100644 src/engine/platform/sound/c64_fp/WaveformGenerator.h create mode 100644 src/engine/platform/sound/c64_fp/array.h create mode 100644 src/engine/platform/sound/c64_fp/resample/Resampler.h create mode 100755 src/engine/platform/sound/c64_fp/resample/SincResampler.cpp create mode 100644 src/engine/platform/sound/c64_fp/resample/SincResampler.h create mode 100644 src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h create mode 100644 src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h create mode 100644 src/engine/platform/sound/c64_fp/resample/test.cpp create mode 100644 src/engine/platform/sound/c64_fp/sidcxx11.h create mode 100644 src/engine/platform/sound/c64_fp/sidcxx14.h create mode 100644 src/engine/platform/sound/c64_fp/siddefs-fp.h create mode 100644 src/engine/platform/sound/c64_fp/version.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b47d2a74..ff7db42e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -368,6 +368,24 @@ src/engine/platform/sound/c64/wave8580_PST.cc src/engine/platform/sound/c64/wave8580_P_T.cc src/engine/platform/sound/c64/wave8580__ST.cc +src/engine/platform/sound/c64_fp/Dac.cpp +src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp +src/engine/platform/sound/c64_fp/ExternalFilter.cpp +src/engine/platform/sound/c64_fp/Filter6581.cpp +src/engine/platform/sound/c64_fp/Filter8580.cpp +src/engine/platform/sound/c64_fp/Filter.cpp +src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp +src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp +src/engine/platform/sound/c64_fp/FilterModelConfig.cpp +src/engine/platform/sound/c64_fp/Integrator6581.cpp +src/engine/platform/sound/c64_fp/Integrator8580.cpp +src/engine/platform/sound/c64_fp/OpAmp.cpp +src/engine/platform/sound/c64_fp/SID.cpp +src/engine/platform/sound/c64_fp/Spline.cpp +src/engine/platform/sound/c64_fp/WaveformCalculator.cpp +src/engine/platform/sound/c64_fp/WaveformGenerator.cpp +src/engine/platform/sound/c64_fp/resample/SincResampler.cpp + src/engine/platform/sound/tia/TIASnd.cpp src/engine/platform/sound/ymfm/ymfm_adpcm.cpp diff --git a/src/engine/platform/sound/c64_fp/AUTHORS b/src/engine/platform/sound/c64_fp/AUTHORS new file mode 100644 index 00000000..b04ee0f0 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/AUTHORS @@ -0,0 +1,6 @@ +Authors of reSIDfp. + +Dag Lem: Designed and programmed complete emulation engine. +Antti S. Lankila: Distortion simulation and calculation of combined waveforms +Ken Händel: source code conversion to Java +Leandro Nini: port to c++, merge with reSID 1.0 diff --git a/src/engine/platform/sound/c64_fp/COPYING b/src/engine/platform/sound/c64_fp/COPYING new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/src/engine/platform/sound/c64_fp/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/src/engine/platform/sound/c64_fp/Dac.cpp b/src/engine/platform/sound/c64_fp/Dac.cpp new file mode 100644 index 00000000..0665da81 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Dac.cpp @@ -0,0 +1,123 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 "Dac.h" + +namespace reSIDfp +{ + +Dac::Dac(unsigned int bits) : + dac(new double[bits]), + dacLength(bits) +{} + +Dac::~Dac() +{ + delete [] dac; +} + +double Dac::getOutput(unsigned int input) const +{ + double dacValue = 0.; + + for (unsigned int i = 0; i < dacLength; i++) + { + if ((input & (1 << i)) != 0) + { + dacValue += dac[i]; + } + } + + return dacValue; +} + +void Dac::kinkedDac(ChipModel chipModel) +{ + const double R_INFINITY = 1e6; + + // Non-linearity parameter, 8580 DACs are perfectly linear + const double _2R_div_R = chipModel == MOS6581 ? 2.20 : 2.00; + + // 6581 DACs are not terminated by a 2R resistor + const bool term = chipModel == MOS8580; + + // Calculate voltage contribution by each individual bit in the R-2R ladder. + for (unsigned int set_bit = 0; set_bit < dacLength; set_bit++) + { + double Vn = 1.; // Normalized bit voltage. + double R = 1.; // Normalized R + const double _2R = _2R_div_R * R; // 2R + double Rn = term ? // Rn = 2R for correct termination, + _2R : R_INFINITY; // INFINITY for missing termination. + + unsigned int bit; + + // Calculate DAC "tail" resistance by repeated parallel substitution. + for (bit = 0; bit < set_bit; bit++) + { + Rn = (Rn == R_INFINITY) ? + R + _2R : + R + (_2R * Rn) / (_2R + Rn); // R + 2R || Rn + } + + // Source transformation for bit voltage. + if (Rn == R_INFINITY) + { + Rn = _2R; + } + else + { + Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn + Vn = Vn * Rn / _2R; + } + + // Calculate DAC output voltage by repeated source transformation from + // the "tail". + + for (++bit; bit < dacLength; bit++) + { + Rn += R; + const double I = Vn / Rn; + Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn + Vn = Rn * I; + } + + dac[set_bit] = Vn; + } + + // Normalize to integerish behavior + double Vsum = 0.; + + for (unsigned int i = 0; i < dacLength; i++) + { + Vsum += dac[i]; + } + + Vsum /= 1 << dacLength; + + for (unsigned int i = 0; i < dacLength; i++) + { + dac[i] /= Vsum; + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Dac.h b/src/engine/platform/sound/c64_fp/Dac.h new file mode 100644 index 00000000..35bc0b2c --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Dac.h @@ -0,0 +1,111 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 DAC_H +#define DAC_H + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * Estimate DAC nonlinearity. + * The SID DACs are built up as R-2R ladder as follows: + * + * n n-1 2 1 0 VGND + * | | | | | | Termination + * 2R 2R 2R 2R 2R 2R only for + * | | | | | | MOS 8580 + * Vo -o-R-o-R-...-o-R-o-R-- --+ + * + * + * All MOS 6581 DACs are missing a termination resistor at bit 0. This causes + * pronounced errors for the lower 4 - 5 bits (e.g. the output for bit 0 is + * actually equal to the output for bit 1), resulting in DAC discontinuities + * for the lower bits. + * In addition to this, the 6581 DACs exhibit further severe discontinuities + * for higher bits, which may be explained by a less than perfect match between + * the R and 2R resistors, or by output impedance in the NMOS transistors + * providing the bit voltages. A good approximation of the actual DAC output is + * achieved for 2R/R ~ 2.20. + * + * The MOS 8580 DACs, on the other hand, do not exhibit any discontinuities. + * These DACs include the correct termination resistor, and also seem to have + * very accurately matched R and 2R resistors (2R/R = 2.00). + * + * On the 6581 the output of the waveform and envelope DACs go through + * a voltage follower built with two NMOS: + * + * Vdd + * + * | + * |-+ + * Vin -------| T1 (enhancement-mode) + * |-+ + * | + * o-------- Vout + * | + * |-+ + * +---| T2 (depletion-mode) + * | |-+ + * | | + * + * GND GND + */ +class Dac +{ +private: + /// analog values + double * const dac; + + /// the dac array length + const unsigned int dacLength; + +public: + /** + * Initialize DAC model. + * + * @param bits the number of input bits + */ + Dac(unsigned int bits); + ~Dac(); + + /** + * Build DAC model for specific chip. + * + * @param chipModel 6581 or 8580 + */ + void kinkedDac(ChipModel chipModel); + + /** + * Get the Vo output for a given combination of input bits. + * + * @param input the digital input + * @return the analog output value + */ + double getOutput(unsigned int input) const; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp new file mode 100644 index 00000000..af636ac7 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp @@ -0,0 +1,155 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2018 VICE Project + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define ENVELOPEGENERATOR_CPP + +#include "EnvelopeGenerator.h" + +namespace reSIDfp +{ + +/** + * Lookup table to convert from attack, decay, or release value to rate + * counter period. + * + * The rate counter is a 15 bit register which is left shifted each cycle. + * When the counter reaches a specific comparison value, + * the envelope counter is incremented (attack) or decremented + * (decay/release) and the rate counter is resetted. + * + * see [kevtris.org](http://blog.kevtris.org/?p=13) + */ +const unsigned int EnvelopeGenerator::adsrtable[16] = +{ + 0x007f, + 0x3000, + 0x1e00, + 0x0660, + 0x0182, + 0x5573, + 0x000e, + 0x3805, + 0x2424, + 0x2220, + 0x090c, + 0x0ecd, + 0x010e, + 0x23f7, + 0x5237, + 0x64a8 +}; + +void EnvelopeGenerator::reset() +{ + // counter is not changed on reset + envelope_pipeline = 0; + + state_pipeline = 0; + + attack = 0; + decay = 0; + sustain = 0; + release = 0; + + gate = false; + + resetLfsr = true; + + exponential_counter = 0; + exponential_counter_period = 1; + new_exponential_counter_period = 0; + + state = RELEASE; + counter_enabled = true; + rate = adsrtable[release]; +} + +void EnvelopeGenerator::writeCONTROL_REG(unsigned char control) +{ + const bool gate_next = (control & 0x01) != 0; + + if (gate_next != gate) + { + gate = gate_next; + + // The rate counter is never reset, thus there will be a delay before the + // envelope counter starts counting up (attack) or down (release). + + if (gate_next) + { + // Gate bit on: Start attack, decay, sustain. + next_state = ATTACK; + state_pipeline = 2; + + if (resetLfsr || (exponential_pipeline == 2)) + { + envelope_pipeline = (exponential_counter_period == 1) || (exponential_pipeline == 2) ? 2 : 4; + } + else if (exponential_pipeline == 1) + { + state_pipeline = 3; + } + } + else + { + // Gate bit off: Start release. + next_state = RELEASE; + state_pipeline = envelope_pipeline > 0 ? 3 : 2; + } + } +} + +void EnvelopeGenerator::writeATTACK_DECAY(unsigned char attack_decay) +{ + attack = (attack_decay >> 4) & 0x0f; + decay = attack_decay & 0x0f; + + if (state == ATTACK) + { + rate = adsrtable[attack]; + } + else if (state == DECAY_SUSTAIN) + { + rate = adsrtable[decay]; + } +} + +void EnvelopeGenerator::writeSUSTAIN_RELEASE(unsigned char sustain_release) +{ + // From the sustain levels it follows that both the low and high 4 bits + // of the envelope counter are compared to the 4-bit sustain value. + // This has been verified by sampling ENV3. + // + // For a detailed description see: + // http://ploguechipsounds.blogspot.it/2010/11/new-research-on-sid-adsr.html + sustain = (sustain_release & 0xf0) | ((sustain_release >> 4) & 0x0f); + + release = sustain_release & 0x0f; + + if (state == RELEASE) + { + rate = adsrtable[release]; + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h new file mode 100644 index 00000000..f2aab387 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h @@ -0,0 +1,419 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2018 VICE Project + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 ENVELOPEGENERATOR_H +#define ENVELOPEGENERATOR_H + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * A 15 bit [LFSR] is used to implement the envelope rates, in effect dividing + * the clock to the envelope counter by the currently selected rate period. + * + * In addition, another 5 bit counter is used to implement the exponential envelope decay, + * in effect further dividing the clock to the envelope counter. + * The period of this counter is set to 1, 2, 4, 8, 16, 30 at the envelope counter + * values 255, 93, 54, 26, 14, 6, respectively. + * + * [LFSR]: https://en.wikipedia.org/wiki/Linear_feedback_shift_register + */ +class EnvelopeGenerator +{ +private: + /** + * The envelope state machine's distinct states. In addition to this, + * envelope has a hold mode, which freezes envelope counter to zero. + */ + enum State + { + ATTACK, DECAY_SUSTAIN, RELEASE + }; + +private: + /// XOR shift register for ADSR prescaling. + unsigned int lfsr; + + /// Comparison value (period) of the rate counter before next event. + unsigned int rate; + + /** + * During release mode, the SID approximates envelope decay via piecewise + * linear decay rate. + */ + unsigned int exponential_counter; + + /** + * Comparison value (period) of the exponential decay counter before next + * decrement. + */ + unsigned int exponential_counter_period; + unsigned int new_exponential_counter_period; + + unsigned int state_pipeline; + + /// + unsigned int envelope_pipeline; + + unsigned int exponential_pipeline; + + /// Current envelope state + State state; + State next_state; + + /// Whether counter is enabled. Only switching to ATTACK can release envelope. + bool counter_enabled; + + /// Gate bit + bool gate; + + /// + bool resetLfsr; + + /// The current digital value of envelope output. + unsigned char envelope_counter; + + /// Attack register + unsigned char attack; + + /// Decay register + unsigned char decay; + + /// Sustain register + unsigned char sustain; + + /// Release register + unsigned char release; + + /// The ENV3 value, sampled at the first phase of the clock + unsigned char env3; + +private: + static const unsigned int adsrtable[16]; + +private: + void set_exponential_counter(); + + void state_change(); + +public: + /** + * SID clocking. + */ + void clock(); + + /** + * Get the Envelope Generator digital output. + */ + unsigned int output() const { return envelope_counter; } + + /** + * Constructor. + */ + EnvelopeGenerator() : + lfsr(0x7fff), + rate(0), + exponential_counter(0), + exponential_counter_period(1), + new_exponential_counter_period(0), + state_pipeline(0), + envelope_pipeline(0), + exponential_pipeline(0), + state(RELEASE), + next_state(RELEASE), + counter_enabled(true), + gate(false), + resetLfsr(false), + envelope_counter(0xaa), + attack(0), + decay(0), + sustain(0), + release(0), + env3(0) + {} + + /** + * SID reset. + */ + void reset(); + + /** + * Write control register. + * + * @param control + * control register value + */ + void writeCONTROL_REG(unsigned char control); + + /** + * Write Attack/Decay register. + * + * @param attack_decay + * attack/decay value + */ + void writeATTACK_DECAY(unsigned char attack_decay); + + /** + * Write Sustain/Release register. + * + * @param sustain_release + * sustain/release value + */ + void writeSUSTAIN_RELEASE(unsigned char sustain_release); + + /** + * Return the envelope current value. + * + * @return envelope counter value + */ + unsigned char readENV() const { return env3; } +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(ENVELOPEGENERATOR_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +void EnvelopeGenerator::clock() +{ + env3 = envelope_counter; + + if (unlikely(new_exponential_counter_period > 0)) + { + exponential_counter_period = new_exponential_counter_period; + new_exponential_counter_period = 0; + } + + if (unlikely(state_pipeline)) + { + state_change(); + } + + if (unlikely(envelope_pipeline != 0) && (--envelope_pipeline == 0)) + { + if (likely(counter_enabled)) + { + if (state == ATTACK) + { + if (++envelope_counter==0xff) + { + next_state = DECAY_SUSTAIN; + state_pipeline = 3; + } + } + else if ((state == DECAY_SUSTAIN) || (state == RELEASE)) + { + if (--envelope_counter==0x00) + { + counter_enabled = false; + } + } + + set_exponential_counter(); + } + } + else if (unlikely(exponential_pipeline != 0) && (--exponential_pipeline == 0)) + { + exponential_counter = 0; + + if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain)) + || (state == RELEASE)) + { + // The envelope counter can flip from 0x00 to 0xff by changing state to + // attack, then to release. The envelope counter will then continue + // counting down in the release state. + // This has been verified by sampling ENV3. + + envelope_pipeline = 1; + } + } + else if (unlikely(resetLfsr)) + { + lfsr = 0x7fff; + resetLfsr = false; + + if (state == ATTACK) + { + // The first envelope step in the attack state also resets the exponential + // counter. This has been verified by sampling ENV3. + exponential_counter = 0; // NOTE this is actually delayed one cycle, not modeled + + // The envelope counter can flip from 0xff to 0x00 by changing state to + // release, then to attack. The envelope counter is then frozen at + // zero; to unlock this situation the state must be changed to release, + // then to attack. This has been verified by sampling ENV3. + + envelope_pipeline = 2; + } + else + { + if (counter_enabled && (++exponential_counter == exponential_counter_period)) + exponential_pipeline = exponential_counter_period != 1 ? 2 : 1; + } + } + + // ADSR delay bug. + // If the rate counter comparison value is set below the current value of the + // rate counter, the counter will continue counting up until it wraps around + // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the + // envelope can constly be stepped. + // This has been verified by sampling ENV3. + + // check to see if LFSR matches table value + if (likely(lfsr != rate)) + { + // it wasn't a match, clock the LFSR once + // by performing XOR on last 2 bits + const unsigned int feedback = ((lfsr << 14) ^ (lfsr << 13)) & 0x4000; + lfsr = (lfsr >> 1) | feedback; + } + else + { + resetLfsr = true; + } +} + +/** + * This is what happens on chip during state switching, + * based on die reverse engineering and transistor level + * emulation. + * + * Attack + * + * 0 - Gate on + * 1 - Counting direction changes + * During this cycle the decay rate is "accidentally" activated + * 2 - Counter is being inverted + * Now the attack rate is correctly activated + * Counter is enabled + * 3 - Counter will be counting upward from now on + * + * Decay + * + * 0 - Counter == $ff + * 1 - Counting direction changes + * The attack state is still active + * 2 - Counter is being inverted + * During this cycle the decay state is activated + * 3 - Counter will be counting downward from now on + * + * Release + * + * 0 - Gate off + * 1 - During this cycle the release state is activated if coming from sustain/decay + * *2 - Counter is being inverted, the release state is activated + * *3 - Counter will be counting downward from now on + * + * (* only if coming directly from Attack state) + * + * Freeze + * + * 0 - Counter == $00 + * 1 - Nothing + * 2 - Counter is disabled + */ +RESID_INLINE +void EnvelopeGenerator::state_change() +{ + state_pipeline--; + + switch (next_state) + { + case ATTACK: + if (state_pipeline == 1) + { + // The decay rate is "accidentally" enabled during first cycle of attack phase + rate = adsrtable[decay]; + } + else if (state_pipeline == 0) + { + state = ATTACK; + // The attack rate is correctly enabled during second cycle of attack phase + rate = adsrtable[attack]; + counter_enabled = true; + } + break; + case DECAY_SUSTAIN: + if (state_pipeline == 0) + { + state = DECAY_SUSTAIN; + rate = adsrtable[decay]; + } + break; + case RELEASE: + if (((state == ATTACK) && (state_pipeline == 0)) + || ((state == DECAY_SUSTAIN) && (state_pipeline == 1))) + { + state = RELEASE; + rate = adsrtable[release]; + } + break; + } +} + +RESID_INLINE +void EnvelopeGenerator::set_exponential_counter() +{ + // Check for change of exponential counter period. + // + // For a detailed description see: + // http://ploguechipsounds.blogspot.it/2010/03/sid-6581r3-adsr-tables-up-close.html + switch (envelope_counter) + { + case 0xff: + case 0x00: + new_exponential_counter_period = 1; + break; + + case 0x5d: + new_exponential_counter_period = 2; + break; + + case 0x36: + new_exponential_counter_period = 4; + break; + + case 0x1a: + new_exponential_counter_period = 8; + break; + + case 0x0e: + new_exponential_counter_period = 16; + break; + + case 0x06: + new_exponential_counter_period = 30; + break; + } +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/ExternalFilter.cpp b/src/engine/platform/sound/c64_fp/ExternalFilter.cpp new file mode 100644 index 00000000..eac790b3 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/ExternalFilter.cpp @@ -0,0 +1,68 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define EXTERNALFILTER_CPP + +#include "ExternalFilter.h" + +namespace reSIDfp +{ + +/** + * Get the 3 dB attenuation point. + * + * @param res the resistance value in Ohms + * @param cap the capacitance value in Farads + */ +inline double getRC(double res, double cap) +{ + return res * cap; +} + +ExternalFilter::ExternalFilter() : + w0lp_1_s7(0), + w0hp_1_s17(0) +{ + reset(); +} + +void ExternalFilter::setClockFrequency(double frequency) +{ + const double dt = 1. / frequency; + + // Low-pass: R = 10kOhm, C = 1000pF; w0l = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-9) = 0.091 + // Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-9 = 15915.5 Hz + w0lp_1_s7 = static_cast((dt / (dt + getRC(10e3, 1000e-12))) * (1 << 7) + 0.5); + + // High-pass: R = 10kOhm, C = 10uF; w0h = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-5) = 0.00000999 + // Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-5 = 1.59155 Hz + w0hp_1_s17 = static_cast((dt / (dt + getRC(10e3, 10e-6))) * (1 << 17) + 0.5); +} + +void ExternalFilter::reset() +{ + // State of filter. + Vlp = 0; //1 << (15 + 11); + Vhp = 0; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/ExternalFilter.h b/src/engine/platform/sound/c64_fp/ExternalFilter.h new file mode 100644 index 00000000..760ee5c2 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/ExternalFilter.h @@ -0,0 +1,125 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 EXTERNALFILTER_H +#define EXTERNALFILTER_H + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * The audio output stage in a Commodore 64 consists of two STC networks, a + * low-pass RC filter with 3 dB frequency 16kHz followed by a DC-blocker which + * acts as a high-pass filter with a cutoff dependent on the attached audio + * equipment impedance. Here we suppose an impedance of 10kOhm resulting + * in a 3 dB attenuation at 1.6Hz. + * To operate properly the 6581 audio output needs a pull-down resistor + *(1KOhm recommended, not needed on 8580) + * + * ~~~ + * 9/12V + * -----+ + * audio| 10k | + * +---o----R---o--------o-----(K) +----- + * out | | | | | |audio + * -----+ R 1k C 1000 | | 10 uF | + * | | pF +-C----o-----C-----+ 10k + * 470 | | + * GND GND pF R 1K | amp + * * * | +----- + * + * GND + * ~~~ + * + * The STC networks are connected with a [BJT] based [common collector] + * used as a voltage follower (featuring a 2SC1815 NPN transistor). + * * The C64c board additionally includes a [bootstrap] condenser to increase + * the input impedance of the common collector. + * + * [BJT]: https://en.wikipedia.org/wiki/Bipolar_junction_transistor + * [common collector]: https://en.wikipedia.org/wiki/Common_collector + * [bootstrap]: https://en.wikipedia.org/wiki/Bootstrapping_(electronics) + */ +class ExternalFilter +{ +private: + /// Lowpass filter voltage + int Vlp; + + /// Highpass filter voltage + int Vhp; + + int w0lp_1_s7; + + int w0hp_1_s17; + +public: + /** + * SID clocking. + * + * @param input + */ + int clock(unsigned short input); + + /** + * Constructor. + */ + ExternalFilter(); + + /** + * Setup of the external filter sampling parameters. + * + * @param frequency the main system clock frequency + */ + void setClockFrequency(double frequency); + + /** + * SID reset. + */ + void reset(); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(EXTERNALFILTER_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +int ExternalFilter::clock(unsigned short input) +{ + const int Vi = (static_cast(input)<<11) - (1 << (11+15)); + const int dVlp = (w0lp_1_s7 * (Vi - Vlp) >> 7); + const int dVhp = (w0hp_1_s17 * (Vlp - Vhp) >> 17); + Vlp += dVlp; + Vhp += dVhp; + return (Vlp - Vhp) >> 11; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Filter.cpp b/src/engine/platform/sound/c64_fp/Filter.cpp new file mode 100644 index 00000000..2a2dd24f --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter.cpp @@ -0,0 +1,90 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 "Filter.h" + +namespace reSIDfp +{ + +void Filter::enable(bool enable) +{ + enabled = enable; + + if (enabled) + { + writeRES_FILT(filt); + } + else + { + filt1 = filt2 = filt3 = filtE = false; + } +} + +void Filter::reset() +{ + writeFC_LO(0); + writeFC_HI(0); + writeMODE_VOL(0); + writeRES_FILT(0); +} + +void Filter::writeFC_LO(unsigned char fc_lo) +{ + fc = (fc & 0x7f8) | (fc_lo & 0x007); + updatedCenterFrequency(); +} + +void Filter::writeFC_HI(unsigned char fc_hi) +{ + fc = (fc_hi << 3 & 0x7f8) | (fc & 0x007); + updatedCenterFrequency(); +} + +void Filter::writeRES_FILT(unsigned char res_filt) +{ + filt = res_filt; + + updateResonance((res_filt >> 4) & 0x0f); + + if (enabled) + { + filt1 = (filt & 0x01) != 0; + filt2 = (filt & 0x02) != 0; + filt3 = (filt & 0x04) != 0; + filtE = (filt & 0x08) != 0; + } + + updatedMixing(); +} + +void Filter::writeMODE_VOL(unsigned char mode_vol) +{ + vol = mode_vol & 0x0f; + lp = (mode_vol & 0x10) != 0; + bp = (mode_vol & 0x20) != 0; + hp = (mode_vol & 0x40) != 0; + voice3off = (mode_vol & 0x80) != 0; + + updatedMixing(); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Filter.h b/src/engine/platform/sound/c64_fp/Filter.h new file mode 100644 index 00000000..4b347336 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter.h @@ -0,0 +1,177 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2017 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 FILTER_H +#define FILTER_H + +namespace reSIDfp +{ + +/** + * SID filter base class + */ +class Filter +{ +protected: + /// Current volume amplifier setting. + unsigned short* currentGain; + + /// Current filter/voice mixer setting. + unsigned short* currentMixer; + + /// Filter input summer setting. + unsigned short* currentSummer; + + /// Filter resonance value. + unsigned short* currentResonance; + + /// Filter highpass state. + int Vhp; + + /// Filter bandpass state. + int Vbp; + + /// Filter lowpass state. + int Vlp; + + /// Filter external input. + int ve; + + /// Filter cutoff frequency. + unsigned int fc; + + /// Routing to filter or outside filter + bool filt1, filt2, filt3, filtE; + + /// Switch voice 3 off. + bool voice3off; + + /// Highpass, bandpass, and lowpass filter modes. + bool hp, bp, lp; + + /// Current volume. + unsigned char vol; + +private: + /// Filter enabled. + bool enabled; + + /// Selects which inputs to route through filter. + unsigned char filt; + +protected: + /** + * Set filter cutoff frequency. + */ + virtual void updatedCenterFrequency() = 0; + + /** + * Set filter resonance. + */ + virtual void updateResonance(unsigned char res) = 0; + + /** + * Mixing configuration modified (offsets change) + */ + virtual void updatedMixing() = 0; + +public: + Filter() : + currentGain(nullptr), + currentMixer(nullptr), + currentSummer(nullptr), + currentResonance(nullptr), + Vhp(0), + Vbp(0), + Vlp(0), + ve(0), + fc(0), + filt1(false), + filt2(false), + filt3(false), + filtE(false), + voice3off(false), + hp(false), + bp(false), + lp(false), + vol(0), + enabled(true), + filt(0) {} + + virtual ~Filter() {} + + /** + * SID clocking - 1 cycle + * + * @param v1 voice 1 in + * @param v2 voice 2 in + * @param v3 voice 3 in + * @return filtered output + */ + virtual unsigned short clock(int v1, int v2, int v3) = 0; + + /** + * Enable filter. + * + * @param enable + */ + void enable(bool enable); + + /** + * SID reset. + */ + void reset(); + + /** + * Write Frequency Cutoff Low register. + * + * @param fc_lo Frequency Cutoff Low-Byte + */ + void writeFC_LO(unsigned char fc_lo); + + /** + * Write Frequency Cutoff High register. + * + * @param fc_hi Frequency Cutoff High-Byte + */ + void writeFC_HI(unsigned char fc_hi); + + /** + * Write Resonance/Filter register. + * + * @param res_filt Resonance/Filter + */ + void writeRES_FILT(unsigned char res_filt); + + /** + * Write filter Mode/Volume register. + * + * @param mode_vol Filter Mode/Volume + */ + void writeMODE_VOL(unsigned char mode_vol); + + virtual void input(int input) = 0; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Filter6581.cpp b/src/engine/platform/sound/c64_fp/Filter6581.cpp new file mode 100644 index 00000000..c064a880 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter6581.cpp @@ -0,0 +1,75 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define FILTER6581_CPP + +#include "Filter6581.h" + +#include "Integrator6581.h" + +namespace reSIDfp +{ + +Filter6581::~Filter6581() +{ + delete [] f0_dac; +} + +void Filter6581::updatedCenterFrequency() +{ + const unsigned short Vw = f0_dac[fc]; + hpIntegrator->setVw(Vw); + bpIntegrator->setVw(Vw); +} + +void Filter6581::updatedMixing() +{ + currentGain = gain_vol[vol]; + + unsigned int ni = 0; + unsigned int no = 0; + + (filt1 ? ni : no)++; + (filt2 ? ni : no)++; + + if (filt3) ni++; + else if (!voice3off) no++; + + (filtE ? ni : no)++; + + currentSummer = summer[ni]; + + if (lp) no++; + if (bp) no++; + if (hp) no++; + + currentMixer = mixer[no]; +} + +void Filter6581::setFilterCurve(double curvePosition) +{ + delete [] f0_dac; + f0_dac = FilterModelConfig6581::getInstance()->getDAC(curvePosition); + updatedCenterFrequency(); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Filter6581.h b/src/engine/platform/sound/c64_fp/Filter6581.h new file mode 100644 index 00000000..7fca331a --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter6581.h @@ -0,0 +1,425 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 FILTER6581_H +#define FILTER6581_H + +#include "siddefs-fp.h" + +#include + +#include "Filter.h" +#include "FilterModelConfig6581.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class Integrator6581; + +/** + * The SID filter is modeled with a two-integrator-loop biquadratic filter, + * which has been confirmed by Bob Yannes to be the actual circuit used in + * the SID chip. + * + * Measurements show that excellent emulation of the SID filter is achieved, + * except when high resonance is combined with high sustain levels. + * In this case the SID op-amps are performing less than ideally and are + * causing some peculiar behavior of the SID filter. This however seems to + * have more effect on the overall amplitude than on the color of the sound. + * + * The theory for the filter circuit can be found in "Microelectric Circuits" + * by Adel S. Sedra and Kenneth C. Smith. + * The circuit is modeled based on the explanation found there except that + * an additional inverter is used in the feedback from the bandpass output, + * allowing the summer op-amp to operate in single-ended mode. This yields + * filter outputs with levels independent of Q, which corresponds with the + * results obtained from a real SID. + * + * We have been able to model the summer and the two integrators of the circuit + * to form components of an IIR filter. + * Vhp is the output of the summer, Vbp is the output of the first integrator, + * and Vlp is the output of the second integrator in the filter circuit. + * + * According to Bob Yannes, the active stages of the SID filter are not really + * op-amps. Rather, simple NMOS inverters are used. By biasing an inverter + * into its region of quasi-linear operation using a feedback resistor from + * input to output, a MOS inverter can be made to act like an op-amp for + * small signals centered around the switching threshold. + * + * In 2008, Michael Huth facilitated closer investigation of the SID 6581 + * filter circuit by publishing high quality microscope photographs of the die. + * Tommi Lempinen has done an impressive work on re-vectorizing and annotating + * the die photographs, substantially simplifying further analysis of the + * filter circuit. + * + * The filter schematics below are reverse engineered from these re-vectorized + * and annotated die photographs. While the filter first depicted in reSID 0.9 + * is a correct model of the basic filter, the schematics are now completed + * with the audio mixer and output stage, including details on intended + * relative resistor values. Also included are schematics for the NMOS FET + * voltage controlled resistors (VCRs) used to control cutoff frequency, the + * DAC which controls the VCRs, the NMOS op-amps, and the output buffer. + * + * + * SID filter / mixer / output + * --------------------------- + * ~~~ + * +---------------------------------------------------+ + * | | + * | +--1R1-- \--+ D7 | + * | +---R1--+ | | | + * | | | o--2R1-- \--o D6 | + * | +---------o----o--Rw--o--[A>--o--Rw--o--[A>--o + * ve (EXT IN) | | | | + * D3 \ ---------------R8--o | | (CAP2A) | (CAP1A) + * | v3 | | vhp | vbp | vlp + * D2 | \ -----------R8--o +-----+ | | + * | | v2 | | | | + * D1 | | \ -------R8--o | +----------------+ | + * | | | v1 | | | | + * D0 | | | \ ---R8--+ | | +---------------------------+ + * | | | | | | | + * R6 R6 R6 R6 R6 R6 R6 + * | | | | $18 | | | $18 + * | \ | | D7: 1=open \ \ \ D6 - D4: 0=open + * | | | | | | | + * +---o---o---o-------------o---o---+ 12V + * | + * | D3 +--/ --1R2--+ | + * | +---R8--+ | | +---R2--+ | + * | | | D2 o--/ --2R2--o | | ||--+ + * +---o--[A>--o------o o--o--[A>--o--|| + * D1 o--/ --4R2--o (4.25R2) ||--+ + * $18 | | | + * 0=open D0 +--/ --8R2--+ (8.75R2) | + * + * vo (AUDIO + * OUT) + * + * + * v1 - voice 1 + * v2 - voice 2 + * v3 - voice 3 + * ve - ext in + * vhp - highpass output + * vbp - bandpass output + * vlp - lowpass output + * vo - audio out + * [A> - single ended inverting op-amp (self-biased NMOS inverter) + * Rn - "resistors", implemented with custom NMOS FETs + * Rw - cutoff frequency resistor (VCR) + * C - capacitor + * ~~~ + * Notes: + * + * R2 ~ 2.0*R1 + * R6 ~ 6.0*R1 + * R8 ~ 8.0*R1 + * R24 ~ 24.0*R1 + * + * The Rn "resistors" in the circuit are implemented with custom NMOS FETs, + * probably because of space constraints on the SID die. The silicon substrate + * is laid out in a narrow strip or "snake", with a strip length proportional + * to the intended resistance. The polysilicon gate electrode covers the entire + * silicon substrate and is fixed at 12V in order for the NMOS FET to operate + * in triode mode (a.k.a. linear mode or ohmic mode). + * + * Even in "linear mode", an NMOS FET is only an approximation of a resistor, + * as the apparant resistance increases with increasing drain-to-source + * voltage. If the drain-to-source voltage should approach the gate voltage + * of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and + * the NMOS FET will not operate anywhere like a resistor. + * + * + * + * NMOS FET voltage controlled resistor (VCR) + * ------------------------------------------ + * ~~~ + * Vw + * + * | + * | + * R1 + * | + * +--R1--o + * | __|__ + * | ----- + * | | | + * vi -----o----+ +--o----- vo + * | | + * +----R24----+ + * + * + * vi - input + * vo - output + * Rn - "resistors", implemented with custom NMOS FETs + * Vw - voltage from 11-bit DAC (frequency cutoff control) + * ~~~ + * Notes: + * + * An approximate value for R24 can be found by using the formula for the + * filter cutoff frequency: + * + * FCmin = 1/(2*pi*Rmax*C) + * + * Assuming that a the setting for minimum cutoff frequency in combination with + * a low level input signal ensures that only negligible current will flow + * through the transistor in the schematics above, values for FCmin and C can + * be substituted in this formula to find Rmax. + * Using C = 470pF and FCmin = 220Hz (measured value), we get: + * + * FCmin = 1/(2*pi*Rmax*C) + * Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm + * + * From this it follows that: + * R24 = Rmax ~ 1.5MOhm + * R1 ~ R24/24 ~ 64kOhm + * R2 ~ 2.0*R1 ~ 128kOhm + * R6 ~ 6.0*R1 ~ 384kOhm + * R8 ~ 8.0*R1 ~ 512kOhm + * + * Note that these are only approximate values for one particular SID chip, + * due to process variations the values can be substantially different in + * other chips. + * + * + * + * Filter frequency cutoff DAC + * --------------------------- + * + * ~~~ + * 12V 10 9 8 7 6 5 4 3 2 1 0 VGND + * | | | | | | | | | | | | | Missing + * 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination + * | | | | | | | | | | | | | + * Vw --o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o- -+ + * + * + * Bit on: 12V + * Bit off: 5V (VGND) + * ~~~ + * As is the case with all MOS 6581 DACs, the termination to (virtual) ground + * at bit 0 is missing. + * + * Furthermore, the control of the two VCRs imposes a load on the DAC output + * which varies with the input signals to the VCRs. This can be seen from the + * VCR figure above. + * + * + * + * "Op-amp" (self-biased NMOS inverter) + * ------------------------------------ + * ~~~ + * + * 12V + * + * | + * +-----------o + * | | + * | +------o + * | | | + * | | ||--+ + * | +--|| + * | ||--+ + * ||--+ | + * vi -----|| o---o----- vo + * ||--+ | | + * | ||--+ | + * |-------|| | + * | ||--+ | + * ||--+ | | + * +--|| | | + * | ||--+ | | + * | | | | + * | +-----------o | + * | | | + * | | + * | GND | + * | | + * +----------------------+ + * + * + * vi - input + * vo - output + * ~~~ + * Notes: + * + * The schematics above are laid out to show that the "op-amp" logically + * consists of two building blocks; a saturated load NMOS inverter (on the + * right hand side of the schematics) with a buffer / bias input stage + * consisting of a variable saturated load NMOS inverter (on the left hand + * side of the schematics). + * + * Provided a reasonably high input impedance and a reasonably low output + * impedance, the "op-amp" can be modeled as a voltage transfer function + * mapping input voltage to output voltage. + * + * + * + * Output buffer (NMOS voltage follower) + * ------------------------------------- + * ~~~ + * + * 12V + * + * | + * | + * ||--+ + * vi -----|| + * ||--+ + * | + * o------ vo + * | (AUDIO + * Rext OUT) + * | + * | + * + * GND + * + * vi - input + * vo - output + * Rext - external resistor, 1kOhm + * ~~~ + * Notes: + * + * The external resistor Rext is needed to complete the NMOS voltage follower, + * this resistor has a recommended value of 1kOhm. + * + * Die photographs show that actually, two NMOS transistors are used in the + * voltage follower. However the two transistors are coupled in parallel (all + * terminals are pairwise common), which implies that we can model the two + * transistors as one. + */ +class Filter6581 final : public Filter +{ +private: + const unsigned short* f0_dac; + + unsigned short** mixer; + unsigned short** summer; + unsigned short** gain_res; + unsigned short** gain_vol; + + const int voiceScaleS11; + const int voiceDC; + + /// VCR + associated capacitor connected to highpass output. + std::unique_ptr const hpIntegrator; + + /// VCR + associated capacitor connected to bandpass output. + std::unique_ptr const bpIntegrator; + +protected: + /** + * Set filter cutoff frequency. + */ + void updatedCenterFrequency() override; + + /** + * Set filter resonance. + * + * In the MOS 6581, 1/Q is controlled linearly by res. + */ + void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; } + + void updatedMixing() override; + +public: + Filter6581() : + f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)), + mixer(FilterModelConfig6581::getInstance()->getMixer()), + summer(FilterModelConfig6581::getInstance()->getSummer()), + gain_res(FilterModelConfig6581::getInstance()->getGainRes()), + gain_vol(FilterModelConfig6581::getInstance()->getGainVol()), + voiceScaleS11(FilterModelConfig6581::getInstance()->getVoiceScaleS11()), + voiceDC(FilterModelConfig6581::getInstance()->getNormalizedVoiceDC()), + hpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()), + bpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()) + { + input(0); + } + + ~Filter6581(); + + unsigned short clock(int voice1, int voice2, int voice3) override; + + void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; } + + /** + * Set filter curve type based on single parameter. + * + * @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5 + */ + void setFilterCurve(double curvePosition); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(FILTER6581_CPP) + +#include "Integrator6581.h" + +namespace reSIDfp +{ + +RESID_INLINE +unsigned short Filter6581::clock(int voice1, int voice2, int voice3) +{ + voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC; + voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC; + // Voice 3 is silenced by voice3off if it is not routed through the filter. + voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0; + + int Vi = 0; + int Vo = 0; + + (filt1 ? Vi : Vo) += voice1; + (filt2 ? Vi : Vo) += voice2; + (filt3 ? Vi : Vo) += voice3; + (filtE ? Vi : Vo) += ve; + + Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; + Vbp = hpIntegrator->solve(Vhp); + Vlp = bpIntegrator->solve(Vbp); + + if (lp) Vo += Vlp; + if (bp) Vo += Vbp; + if (hp) Vo += Vhp; + + return currentGain[currentMixer[Vo]]; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Filter8580.cpp b/src/engine/platform/sound/c64_fp/Filter8580.cpp new file mode 100644 index 00000000..a70285a8 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter8580.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2019 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define FILTER8580_CPP + +#include "Filter8580.h" + +#include "Integrator8580.h" + +namespace reSIDfp +{ + +/** + * W/L ratio of frequency DAC bit 0, + * other bit are proportional. + * When no bit are selected a resistance with half + * W/L ratio is selected. + */ +const double DAC_WL0 = 0.00615; + +Filter8580::~Filter8580() {} + +void Filter8580::updatedCenterFrequency() +{ + double wl; + double dacWL = DAC_WL0; + if (fc) + { + wl = 0.; + for (unsigned int i = 0; i < 11; i++) + { + if (fc & (1 << i)) + { + wl += dacWL; + } + dacWL *= 2.; + } + } + else + { + wl = dacWL/2.; + } + + hpIntegrator->setFc(wl); + bpIntegrator->setFc(wl); +} + +void Filter8580::updatedMixing() +{ + currentGain = gain_vol[vol]; + + unsigned int ni = 0; + unsigned int no = 0; + + (filt1 ? ni : no)++; + (filt2 ? ni : no)++; + + if (filt3) ni++; + else if (!voice3off) no++; + + (filtE ? ni : no)++; + + currentSummer = summer[ni]; + + if (lp) no++; + if (bp) no++; + if (hp) no++; + + currentMixer = mixer[no]; +} + +void Filter8580::setFilterCurve(double curvePosition) +{ + // Adjust cp + // 1.2 <= cp <= 1.8 + cp = 1.8 - curvePosition * 3./5.; + + hpIntegrator->setV(cp); + bpIntegrator->setV(cp); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Filter8580.h b/src/engine/platform/sound/c64_fp/Filter8580.h new file mode 100644 index 00000000..2166ec0d --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter8580.h @@ -0,0 +1,383 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 FILTER8580_H +#define FILTER8580_H + +#include "siddefs-fp.h" + +#include + +#include "Filter.h" +#include "FilterModelConfig8580.h" +#include "Integrator8580.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class Integrator8580; + +/** + * Filter for 8580 chip + * -------------------- + * The 8580 filter stage had been redesigned to be more linear and robust + * against temperature change. It also features real op-amps and a + * revisited resonance model. + * The filter schematics below are reverse engineered from re-vectorized + * and annotated die photographs. Credits to Michael Huth for the microscope + * photographs of the die, Tommi Lempinen for re-vectorizating and annotating + * the images and ttlworks from forum.6502.org for the circuit analysis. + * + * ~~~ + * + * +---------------------------------------------------+ + * | $17 +----Rf-+ | + * | | | | + * | D4&!D5 o- \-R3-o | + * | | | $17 | + * | !D4&!D5 o- \-R2-o | + * | | | +---R8-- \--+ !D6&D7 | + * | D4&!D5 o- \-R1-o | | | + * | | | o---RC-- \--o D6&D7 | + * | +---------o----o--Rfc-o--[A>--o--Rfc-o--[A>--o + * ve (EXT IN) | | | | + * D3 \ --------------R12--o | | (CAP2A) | (CAP1A) + * | v3 | | vhp | vbp | vlp + * D2 | \ -----------R7--o +-----+ | | + * | | v2 | | | | + * D1 | | \ -------R7--o | +----------------+ | + * | | | v1 | | | | + * D0 | | | \ ---R7--+ | | +---------------------------+ + * | | | | | | | + * R9 R5 R5 R5 R5 R5 R5 + * | | | | $18 | | | $18 + * | \ | | D7: 1=open \ \ \ D6 - D4: 0=open + * | | | | | | | + * +---o---o---o-------------o---o---+ + * | + * | D3 +--/ --1R4--+ + * | +---R8--+ | | +---R2--+ + * | | | D2 o--/ --2R4--o | | + * +---o--[A>--o------o o--o--[A>--o-- vo (AUDIO OUT) + * D1 o--/ --4R4--o + * $18 | | + * 0=open D0 +--/ --8R4--+ + * + * + * + * Resonance + * --------- + * For resonance, we have two tiny DACs that controls both the input + * and feedback resistances. + * + * The "resistors" are switched in as follows by bits in register $17: + * + * feedback: + * R1: bit4&!bit5 + * R2: !bit4&bit5 + * R3: bit4&bit5 + * Rf: always on + * + * input: + * R4: bit6&!bit7 + * R8: !bit6&bit7 + * RC: bit6&bit7 + * Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7 + * + * + * The relative "resistor" values are approximately (using channel length): + * + * R1 = 15.3*Ri + * R2 = 7.3*Ri + * R3 = 4.7*Ri + * Rf = 1.4*Ri + * R4 = 1.4*Ri + * R8 = 2.0*Ri + * RC = 2.8*Ri + * + * + * Approximate values for 1/Q can now be found as follows (assuming an + * ideal op-amp): + * + * res feedback input -gain (1/Q) + * --- -------- ----- ---------- + * 0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71 + * 1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78 + * 2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85 + * 3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92 + * 4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00 + * 5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10 + * 6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20 + * 7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30 + * 8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43 + * 9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56 + * A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70 + * B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86 + * C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00 + * D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18 + * E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38 + * F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60 + * + * + * These data indicate that the following function for 1/Q has been + * modeled in the MOS 8580: + * + * 1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8) + * + * + * + * Op-amps + * ------- + * Unlike the 6581, the 8580 has real OpAmps. + * + * Temperature compensated differential amplifier: + * + * 9V + * + * | + * +-------o-o-o-------+ + * | | | | + * | R R | + * +--|| | | ||--+ + * ||---o o---|| + * +--|| | | ||--+ + * | | | | + * o-----+ | | o--- Va + * | | | | | + * +--|| | | | ||--+ + * ||-o-+---+---|| + * +--|| | | ||--+ + * | | | | + * | | + * GND | | GND + * ||--+ +--|| + * in- -----|| ||------ in+ + * ||----o----|| + * | + * 8 Current sink + * | + * + * GND + * + * Inverter + non-inverting output amplifier: + * + * Va ---o---||-------------------o--------------------+ + * | | 9V | + * | +----------+----------+ | | + * | 9V | | 9V | ||--+ | + * | | | 9V | | +-|| | + * | R | | | ||--+ ||--+ | + * | | | ||--+ +--|| o---o--- Vout + * | o---o---|| ||--+ ||--+ + * | | ||--+ o-----|| + * | ||--+ | ||--+ ||--+ + * +-----|| o-----|| | + * ||--+ | ||--+ + * | R | GND + * | + * GND GND + * GND + * + * + * + * Virtual ground + * -------------- + * A PolySi resitive voltage divider provides the voltage + * for the positive input of the filter op-amps. + * + * 5V + * +----------+ + * | | |\ | + * R1 +---|-\ | + * 5V | |A >---o--- Vref + * o-------|+/ + * | | |/ + * R10 R4 + * | | + * o---+ + * | + * R10 + * | + * + * GND + * + * Rn = n*R1 + * + * + * + * Rfc - freq control DAC resistance ladder + * ---------------------------------------- + * The 8580 has 11 bits for frequency control, but 12 bit DACs. + * If those 11 bits would be '0', the impedance of the DACs would be "infinitely high". + * To get around this, there is an 11 input NOR gate below the DACs sensing those 11 bits. + * If all are 0, the NOR gate gives the gate control voltage to the 12 bit DAC LSB. + * + * ----o---o--...--o---o---o--- + * | | | | | + * Rb10 Rb9 ... Rb1 Rb0 R0 + * | | | | | + * ----o---o--...--o---o---o--- + * + * + * + * Crystal stabilized precision switched capacitor voltage divider + * --------------------------------------------------------------- + * There is a FET working as a temperature sensor close to the DACs which changes the gate voltage + * of the frequency control DACs according to the temperature of the DACs, + * to reduce the effects of temperature on the filter curve. + * An asynchronous 3 bit binary counter, running at the speed of PHI2, drives two big capacitors + * whose AC resistance is then used as a voltage divider. + * This implicates that frequency difference between PAL and NTSC might shift the filter curve by 4% or such. + * + * |\ OpAmp has a smaller capacitor than the other OPs + * Vref ---|+\ + * |A >---o--- Vdac + * +-------|-/ | + * | |/ | + * | | + * C1 | C2 | + * +---||---o---+ +---o-----||-------o + * | | | | | | + * o----+ | ----- | | + * | | | ----- +----+ +-----o + * | ----- | | | | + * | ----- | ----- | + * | | | ----- | + * | +-----------+ | | + * | /Q Q | +-------+ + * GND +-----------+ FET close to DAC + * | clk/8 | working as temperature sensor + * +-----------+ + */ +class Filter8580 final : public Filter +{ +private: + unsigned short** mixer; + unsigned short** summer; + unsigned short** gain_res; + unsigned short** gain_vol; + + const int voiceScaleS11; + const int voiceDC; + + double cp; + + /// VCR + associated capacitor connected to highpass output. + std::unique_ptr const hpIntegrator; + + /// VCR + associated capacitor connected to bandpass output. + std::unique_ptr const bpIntegrator; + +protected: + /** + * Set filter cutoff frequency. + */ + void updatedCenterFrequency() override; + + /** + * Set filter resonance. + * + * @param res the new resonance value + */ + void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; } + + void updatedMixing() override; + +public: + Filter8580() : + mixer(FilterModelConfig8580::getInstance()->getMixer()), + summer(FilterModelConfig8580::getInstance()->getSummer()), + gain_res(FilterModelConfig8580::getInstance()->getGainRes()), + gain_vol(FilterModelConfig8580::getInstance()->getGainVol()), + voiceScaleS11(FilterModelConfig8580::getInstance()->getVoiceScaleS11()), + voiceDC(FilterModelConfig8580::getInstance()->getNormalizedVoiceDC()), + cp(0.5), + hpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()), + bpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()) + { + setFilterCurve(cp); + input(0); + } + + ~Filter8580(); + + unsigned short clock(int voice1, int voice2, int voice3) override; + + void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; } + + /** + * Set filter curve type based on single parameter. + * + * @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5 + */ + void setFilterCurve(double curvePosition); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(FILTER8580_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +unsigned short Filter8580::clock(int voice1, int voice2, int voice3) +{ + voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC; + voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC; + // Voice 3 is silenced by voice3off if it is not routed through the filter. + voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0; + + int Vi = 0; + int Vo = 0; + + (filt1 ? Vi : Vo) += voice1; + (filt2 ? Vi : Vo) += voice2; + (filt3 ? Vi : Vo) += voice3; + (filtE ? Vi : Vo) += ve; + + Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; + Vbp = hpIntegrator->solve(Vhp); + Vlp = bpIntegrator->solve(Vbp); + + if (lp) Vo += Vlp; + if (bp) Vo += Vbp; + if (hp) Vo += Vhp; + + return currentGain[currentMixer[Vo]]; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig.cpp b/src/engine/platform/sound/c64_fp/FilterModelConfig.cpp new file mode 100644 index 00000000..8fb76238 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig.cpp @@ -0,0 +1,79 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 "FilterModelConfig.h" + +#include + +namespace reSIDfp +{ + +FilterModelConfig::FilterModelConfig( + double vvr, + double vdv, + double c, + double vdd, + double vth, + double ucox, + const Spline::Point *opamp_voltage, + int opamp_size +) : + voice_voltage_range(vvr), + voice_DC_voltage(vdv), + C(c), + Vdd(vdd), + Vth(vth), + Ut(26.0e-3), + uCox(ucox), + Vddt(Vdd - Vth), + vmin(opamp_voltage[0].x), + vmax(std::max(Vddt, opamp_voltage[0].y)), + denorm(vmax - vmin), + norm(1.0 / denorm), + N16(norm * ((1 << 16) - 1)), + currFactorCoeff(denorm * (uCox / 2. * 1.0e-6 / C)) +{ + // Convert op-amp voltage transfer to 16 bit values. + + std::vector scaled_voltage(opamp_size); + + for (int i = 0; i < opamp_size; i++) + { + scaled_voltage[i].x = N16 * (opamp_voltage[i].x - opamp_voltage[i].y + denorm) / 2.; + scaled_voltage[i].y = N16 * (opamp_voltage[i].x - vmin); + } + + // Create lookup table mapping capacitor voltage to op-amp input voltage: + + Spline s(scaled_voltage); + + for (int x = 0; x < (1 << 16); x++) + { + const Spline::Point out = s.evaluate(x); + // If Vmax > max opamp_voltage the first elements may be negative + double tmp = out.x > 0. ? out.x : 0.; + assert(tmp < 65535.5); + opamp_rev[x] = static_cast(tmp + 0.5); + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig.h b/src/engine/platform/sound/c64_fp/FilterModelConfig.h new file mode 100644 index 00000000..d8ae77ab --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig.h @@ -0,0 +1,166 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 FILTERMODELCONFIG_H +#define FILTERMODELCONFIG_H + +#include +#include + +#include "Spline.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class FilterModelConfig +{ +protected: + const double voice_voltage_range; + const double voice_DC_voltage; + + /// Capacitor value. + const double C; + + /// Transistor parameters. + //@{ + const double Vdd; + const double Vth; ///< Threshold voltage + const double Ut; ///< Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV + const double uCox; ///< Transconductance coefficient: u*Cox + const double Vddt; ///< Vdd - Vth + //@} + + // Derived stuff + const double vmin, vmax; + const double denorm, norm; + + /// Fixed point scaling for 16 bit op-amp output. + const double N16; + + /// Current factor coefficient for op-amp integrators. + const double currFactorCoeff; + + /// Lookup tables for gain and summer op-amps in output stage / filter. + //@{ + unsigned short* mixer[8]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* summer[5]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* gain_vol[16]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* gain_res[16]; //-V730_NOINIT this is initialized in the derived class constructor + //@} + + /// Reverse op-amp transfer function. + unsigned short opamp_rev[1 << 16]; //-V730_NOINIT this is initialized in the derived class constructor + +private: + FilterModelConfig (const FilterModelConfig&) DELETE; + FilterModelConfig& operator= (const FilterModelConfig&) DELETE; + +protected: + /** + * @param vvr voice voltage range + * @param vdv voice DC voltage + * @param c capacitor value + * @param vdd Vdd + * @param vth threshold voltage + * @param ucox u*Cox + * @param ominv opamp min voltage + * @param omaxv opamp max voltage + */ + FilterModelConfig( + double vvr, + double vdv, + double c, + double vdd, + double vth, + double ucox, + const Spline::Point *opamp_voltage, + int opamp_size + ); + + ~FilterModelConfig() + { + for (int i = 0; i < 8; i++) + { + delete [] mixer[i]; + } + + for (int i = 0; i < 5; i++) + { + delete [] summer[i]; + } + + for (int i = 0; i < 16; i++) + { + delete [] gain_vol[i]; + delete [] gain_res[i]; + } + } + +public: + unsigned short** getGainVol() { return gain_vol; } + unsigned short** getGainRes() { return gain_res; } + unsigned short** getSummer() { return summer; } + unsigned short** getMixer() { return mixer; } + + /** + * The digital range of one voice is 20 bits; create a scaling term + * for multiplication which fits in 11 bits. + */ + int getVoiceScaleS11() const { return static_cast((norm * ((1 << 11) - 1)) * voice_voltage_range); } + + /** + * The "zero" output level of the voices. + */ + int getNormalizedVoiceDC() const { return static_cast(N16 * (voice_DC_voltage - vmin)); } + + inline unsigned short getOpampRev(int i) const { return opamp_rev[i]; } + inline double getVddt() const { return Vddt; } + inline double getVth() const { return Vth; } + inline double getVoiceDCVoltage() const { return voice_DC_voltage; } + + // helper functions + inline unsigned short getNormalizedValue(double value) const + { + const double tmp = N16 * (value - vmin); + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } + + inline unsigned short getNormalizedCurrentFactor(double wl) const + { + const double tmp = (1 << 13) * currFactorCoeff * wl; + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } + + inline unsigned short getNVmin() const { + const double tmp = N16 * vmin; + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp new file mode 100644 index 00000000..3d86bdcf --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp @@ -0,0 +1,263 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2010 Dag Lem + * + * 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 "FilterModelConfig6581.h" + +#include + +#include "Integrator6581.h" +#include "OpAmp.h" + +namespace reSIDfp +{ + +#ifndef HAVE_CXX11 +/** + * Compute log(1+x) without losing precision for small values of x + * + * @note when compiling with -ffastm-math the compiler will + * optimize the expression away leaving a plain log(1. + x) + */ +inline double log1p(double x) +{ + return log(1. + x) - (((1. + x) - 1.) - x) / (1. + x); +} +#endif + +const unsigned int OPAMP_SIZE = 33; + +/** + * This is the SID 6581 op-amp voltage transfer function, measured on + * CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14. + * All measured chips have op-amps with output voltages (and thus input + * voltages) within the range of 0.81V - 10.31V. + */ +const Spline::Point opamp_voltage[OPAMP_SIZE] = +{ + { 0.81, 10.31 }, // Approximate start of actual range + { 2.40, 10.31 }, + { 2.60, 10.30 }, + { 2.70, 10.29 }, + { 2.80, 10.26 }, + { 2.90, 10.17 }, + { 3.00, 10.04 }, + { 3.10, 9.83 }, + { 3.20, 9.58 }, + { 3.30, 9.32 }, + { 3.50, 8.69 }, + { 3.70, 8.00 }, + { 4.00, 6.89 }, + { 4.40, 5.21 }, + { 4.54, 4.54 }, // Working point (vi = vo) + { 4.60, 4.19 }, + { 4.80, 3.00 }, + { 4.90, 2.30 }, // Change of curvature + { 4.95, 2.03 }, + { 5.00, 1.88 }, + { 5.05, 1.77 }, + { 5.10, 1.69 }, + { 5.20, 1.58 }, + { 5.40, 1.44 }, + { 5.60, 1.33 }, + { 5.80, 1.26 }, + { 6.00, 1.21 }, + { 6.40, 1.12 }, + { 7.00, 1.02 }, + { 7.50, 0.97 }, + { 8.50, 0.89 }, + { 10.00, 0.81 }, + { 10.31, 0.81 }, // Approximate end of actual range +}; + +std::unique_ptr FilterModelConfig6581::instance(nullptr); + +FilterModelConfig6581* FilterModelConfig6581::getInstance() +{ + if (!instance.get()) + { + instance.reset(new FilterModelConfig6581()); + } + + return instance.get(); +} + +FilterModelConfig6581::FilterModelConfig6581() : + FilterModelConfig( + 1.5, // voice voltage range + 5.075, // voice DC voltage + 470e-12, // capacitor value + 12.18, // Vdd + 1.31, // Vth + 20e-6, // uCox + opamp_voltage, + OPAMP_SIZE + ), + WL_vcr(9.0 / 1.0), + WL_snake(1.0 / 115.0), + dac_zero(6.65), + dac_scale(2.63), + dac(DAC_BITS) +{ + dac.kinkedDac(MOS6581); + + // Create lookup tables for gains / summers. + + OpAmp opampModel(std::vector(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt); + + // The filter summer operates at n ~ 1, and has 5 fundamentally different + // input configurations (2 - 6 input "resistors"). + // + // Note that all "on" transistors are modeled as one. This is not + // entirely accurate, since the input for each transistor is different, + // and transistors are not linear components. However modeling all + // transistors separately would be extremely costly. + for (int i = 0; i < 5; i++) + { + const int idiv = 2 + i; // 2 - 6 input "resistors". + const int size = idiv << 16; + const double n = idiv; + opampModel.reset(); + summer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // The audio mixer operates at n ~ 8/6, and has 8 fundamentally different + // input configurations (0 - 7 input "resistors"). + // + // All "on", transistors are modeled as one - see comments above for + // the filter summer. + for (int i = 0; i < 8; i++) + { + const int idiv = (i == 0) ? 1 : i; + const int size = (i == 0) ? 1 : i << 16; + const double n = i * 8.0 / 6.0; + opampModel.reset(); + mixer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the audio + // output gain necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that gain ~ vol/12 (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = n8 / 12.0; + opampModel.reset(); + gain_vol[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the bandpass resonance gain + // necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that 1/Q ~ ~res/8 (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = (~n8 & 0xf) / 8.0; + opampModel.reset(); + gain_res[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_res[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + const double nVddt = N16 * (Vddt - vmin); + + for (unsigned int i = 0; i < (1 << 16); i++) + { + // The table index is right-shifted 16 times in order to fit in + // 16 bits; the argument to sqrt is thus multiplied by (1 << 16). + const double tmp = nVddt - sqrt(static_cast(i << 16)); + assert(tmp > -0.5 && tmp < 65535.5); + vcr_nVg[i] = static_cast(tmp + 0.5); + } + + // EKV model: + // + // Ids = Is * (if - ir) + // Is = (2 * u*Cox * Ut^2)/k * W/L + // if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + // ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + + // moderate inversion characteristic current + const double Is = (2. * uCox * Ut * Ut) * WL_vcr; + + // Normalized current factor for 1 cycle at 1MHz. + const double N15 = norm * ((1 << 15) - 1); + const double n_Is = N15 * 1.0e-6 / C * Is; + + // kVgt_Vx = k*(Vg - Vt) - Vx + // I.e. if k != 1.0, Vg must be scaled accordingly. + for (int kVgt_Vx = 0; kVgt_Vx < (1 << 16); kVgt_Vx++) + { + const double log_term = log1p(exp((kVgt_Vx / N16) / (2. * Ut))); + // Scaled by m*2^15 + const double tmp = n_Is * log_term * log_term; + assert(tmp > -0.5 && tmp < 65535.5); + vcr_n_Ids_term[kVgt_Vx] = static_cast(tmp + 0.5); + } +} + +unsigned short* FilterModelConfig6581::getDAC(double adjustment) const +{ + const double dac_zero = getDacZero(adjustment); + + unsigned short* f0_dac = new unsigned short[1 << DAC_BITS]; + + for (unsigned int i = 0; i < (1 << DAC_BITS); i++) + { + const double fcd = dac.getOutput(i); + f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale / (1 << DAC_BITS)); + } + + return f0_dac; +} + +std::unique_ptr FilterModelConfig6581::buildIntegrator() +{ + return MAKE_UNIQUE(Integrator6581, this, WL_snake); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig6581.h b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.h new file mode 100644 index 00000000..85cbd43f --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.h @@ -0,0 +1,112 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 FILTERMODELCONFIG6581_H +#define FILTERMODELCONFIG6581_H + +#include "FilterModelConfig.h" + +#include + +#include "Dac.h" + +#include "sidcxx14.h" + +namespace reSIDfp +{ + +class Integrator6581; + +/** + * Calculate parameters for 6581 filter emulation. + */ +class FilterModelConfig6581 final : public FilterModelConfig +{ +private: + static const unsigned int DAC_BITS = 11; + +private: + static std::unique_ptr instance; + // This allows access to the private constructor +#ifdef HAVE_CXX11 + friend std::unique_ptr::deleter_type; +#else + friend class std::auto_ptr; +#endif + + /// Transistor parameters. + //@{ + const double WL_vcr; ///< W/L for VCR + const double WL_snake; ///< W/L for "snake" + //@} + + /// DAC parameters. + //@{ + const double dac_zero; + const double dac_scale; + //@} + + /// DAC lookup table + Dac dac; + + /// VCR - 6581 only. + //@{ + unsigned short vcr_nVg[1 << 16]; + unsigned short vcr_n_Ids_term[1 << 16]; + //@} + +private: + double getDacZero(double adjustment) const { return dac_zero + (1. - adjustment); } + + FilterModelConfig6581(); + ~FilterModelConfig6581() DEFAULT; + +public: + static FilterModelConfig6581* getInstance(); + + /** + * Construct an 11 bit cutoff frequency DAC output voltage table. + * Ownership is transferred to the requester which becomes responsible + * of freeing the object when done. + * + * @param adjustment + * @return the DAC table + */ + unsigned short* getDAC(double adjustment) const; + + /** + * Construct an integrator solver. + * + * @return the integrator + */ + std::unique_ptr buildIntegrator(); + + inline unsigned short getVcr_nVg(int i) const { return vcr_nVg[i]; } + inline unsigned short getVcr_n_Ids_term(int i) const { return vcr_n_Ids_term[i]; } + // only used if SLOPE_FACTOR is defined + inline double getUt() const { return Ut; } + inline double getN16() const { return N16; } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp new file mode 100644 index 00000000..fd2a16fa --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp @@ -0,0 +1,222 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2010 Dag Lem + * + * 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 "FilterModelConfig8580.h" + +#include "Integrator8580.h" +#include "OpAmp.h" + +namespace reSIDfp +{ + +/* + * R1 = 15.3*Ri + * R2 = 7.3*Ri + * R3 = 4.7*Ri + * Rf = 1.4*Ri + * R4 = 1.4*Ri + * R8 = 2.0*Ri + * RC = 2.8*Ri + * + * res feedback input + * --- -------- ----- + * 0 Rf Ri + * 1 Rf|R1 Ri + * 2 Rf|R2 Ri + * 3 Rf|R3 Ri + * 4 Rf R4 + * 5 Rf|R1 R4 + * 6 Rf|R2 R4 + * 7 Rf|R3 R4 + * 8 Rf R8 + * 9 Rf|R1 R8 + * A Rf|R2 R8 + * B Rf|R3 R8 + * C Rf RC + * D Rf|R1 RC + * E Rf|R2 RC + * F Rf|R3 RC + */ +const double resGain[16] = +{ + 1.4/1.0, // Rf/Ri 1.4 + ((1.4*15.3)/(1.4+15.3))/1.0, // (Rf|R1)/Ri 1.28263 + ((1.4*7.3)/(1.4+7.3))/1.0, // (Rf|R2)/Ri 1.17471 + ((1.4*4.7)/(1.4+4.7))/1.0, // (Rf|R3)/Ri 1.07869 + 1.4/1.4, // Rf/R4 1 + ((1.4*15.3)/(1.4+15.3))/1.4, // (Rf|R1)/R4 0.916168 + ((1.4*7.3)/(1.4+7.3))/1.4, // (Rf|R2)/R4 0.83908 + ((1.4*4.7)/(1.4+4.7))/1.4, // (Rf|R3)/R4 0.770492 + 1.4/2.0, // Rf/R8 0.7 + ((1.4*15.3)/(1.4+15.3))/2.0, // (Rf|R1)/R8 0.641317 + ((1.4*7.3)/(1.4+7.3))/2.0, // (Rf|R2)/R8 0.587356 + ((1.4*4.7)/(1.4+4.7))/2.0, // (Rf|R3)/R8 0.539344 + 1.4/2.8, // Rf/RC 0.5 + ((1.4*15.3)/(1.4+15.3))/2.8, // (Rf|R1)/RC 0.458084 + ((1.4*7.3)/(1.4+7.3))/2.8, // (Rf|R2)/RC 0.41954 + ((1.4*4.7)/(1.4+4.7))/2.8, // (Rf|R3)/RC 0.385246 +}; + +const unsigned int OPAMP_SIZE = 21; + +/** + * This is the SID 8580 op-amp voltage transfer function, measured on + * CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25. + */ +const Spline::Point opamp_voltage[OPAMP_SIZE] = +{ + { 1.30, 8.91 }, // Approximate start of actual range + { 4.76, 8.91 }, + { 4.77, 8.90 }, + { 4.78, 8.88 }, + { 4.785, 8.86 }, + { 4.79, 8.80 }, + { 4.795, 8.60 }, + { 4.80, 8.25 }, + { 4.805, 7.50 }, + { 4.81, 6.10 }, + { 4.815, 4.05 }, // Change of curvature + { 4.82, 2.27 }, + { 4.825, 1.65 }, + { 4.83, 1.55 }, + { 4.84, 1.47 }, + { 4.85, 1.43 }, + { 4.87, 1.37 }, + { 4.90, 1.34 }, + { 5.00, 1.30 }, + { 5.10, 1.30 }, + { 8.91, 1.30 }, // Approximate end of actual range +}; + +std::unique_ptr FilterModelConfig8580::instance(nullptr); + +FilterModelConfig8580* FilterModelConfig8580::getInstance() +{ + if (!instance.get()) + { + instance.reset(new FilterModelConfig8580()); + } + + return instance.get(); +} + +FilterModelConfig8580::FilterModelConfig8580() : + FilterModelConfig( + 0.25, // voice voltage range FIXME measure + 4.80, // voice DC voltage FIXME was 4.76 + 22e-9, // capacitor value + 9.09, // Vdd + 0.80, // Vth + 100e-6, // uCox + opamp_voltage, + OPAMP_SIZE + ) +{ + // Create lookup tables for gains / summers. + + OpAmp opampModel(std::vector(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt); + + // The filter summer operates at n ~ 1, and has 5 fundamentally different + // input configurations (2 - 6 input "resistors"). + // + // Note that all "on" transistors are modeled as one. This is not + // entirely accurate, since the input for each transistor is different, + // and transistors are not linear components. However modeling all + // transistors separately would be extremely costly. + for (int i = 0; i < 5; i++) + { + const int idiv = 2 + i; // 2 - 6 input "resistors". + const int size = idiv << 16; + const double n = idiv; + opampModel.reset(); + summer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // The audio mixer operates at n ~ 8/5, and has 8 fundamentally different + // input configurations (0 - 7 input "resistors"). + // + // All "on", transistors are modeled as one - see comments above for + // the filter summer. + for (int i = 0; i < 8; i++) + { + const int idiv = (i == 0) ? 1 : i; + const int size = (i == 0) ? 1 : i << 16; + const double n = i * 8.0 / 5.0; + opampModel.reset(); + mixer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the audio output gain + // necessitate 16 gain tables. + // From die photographs of the volume "resistor" ladders + // it follows that gain ~ vol/16 (assuming ideal op-amps + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = n8 / 16.0; + opampModel.reset(); + gain_vol[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the bandpass resonance gain + // necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that 1/Q ~ 2^((4 - res)/8) (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + opampModel.reset(); + gain_res[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_res[n8][vi] = getNormalizedValue(opampModel.solve(resGain[n8], vin)); + } + } +} + +std::unique_ptr FilterModelConfig8580::buildIntegrator() +{ + return MAKE_UNIQUE(Integrator8580, this); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig8580.h b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.h new file mode 100644 index 00000000..ee2b4008 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.h @@ -0,0 +1,68 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 FILTERMODELCONFIG8580_H +#define FILTERMODELCONFIG8580_H + +#include "FilterModelConfig.h" + +#include + +#include "sidcxx14.h" + +namespace reSIDfp +{ + +class Integrator8580; + +/** + * Calculate parameters for 8580 filter emulation. + */ +class FilterModelConfig8580 final : public FilterModelConfig +{ +private: + static std::unique_ptr instance; + // This allows access to the private constructor +#ifdef HAVE_CXX11 + friend std::unique_ptr::deleter_type; +#else + friend class std::auto_ptr; +#endif + +private: + FilterModelConfig8580(); + ~FilterModelConfig8580() DEFAULT; + +public: + static FilterModelConfig8580* getInstance(); + + /** + * Construct an integrator solver. + * + * @return the integrator + */ + std::unique_ptr buildIntegrator(); +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Integrator6581.cpp b/src/engine/platform/sound/c64_fp/Integrator6581.cpp new file mode 100644 index 00000000..490be9b5 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator6581.cpp @@ -0,0 +1,25 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014 Leandro Nini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define INTEGRATOR_CPP + +#include "Integrator6581.h" + +// This is needed when compiling with --disable-inline diff --git a/src/engine/platform/sound/c64_fp/Integrator6581.h b/src/engine/platform/sound/c64_fp/Integrator6581.h new file mode 100644 index 00000000..99ac3bea --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator6581.h @@ -0,0 +1,285 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004, 2010 Dag Lem + * + * 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 INTEGRATOR6581_H +#define INTEGRATOR6581_H + +#include "FilterModelConfig6581.h" + +#include +#include + +// uncomment to enable use of the slope factor +// in the EKV model +// actually produces worse results, needs investigation +//#define SLOPE_FACTOR + +#ifdef SLOPE_FACTOR +# include +#endif + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * Find output voltage in inverting integrator SID op-amp circuits, using a + * single fixpoint iteration step. + * + * A circuit diagram of a MOS 6581 integrator is shown below. + * + * +---C---+ + * | | + * vi --o--Rw--o-o--[A>--o-- vo + * | | vx + * +--Rs--+ + * + * From Kirchoff's current law it follows that + * + * IRw + IRs + ICr = 0 + * + * Using the formula for current through a capacitor, i = C*dv/dt, we get + * + * IRw + IRs + C*(vc - vc0)/dt = 0 + * dt/C*(IRw + IRs) + vc - vc0 = 0 + * vc = vc0 - n*(IRw(vi,vx) + IRs(vi,vx)) + * + * which may be rewritten as the following iterative fixpoint function: + * + * vc = vc0 - n*(IRw(vi,g(vc)) + IRs(vi,g(vc))) + * + * To accurately calculate the currents through Rs and Rw, we need to use + * transistor models. Rs has a gate voltage of Vdd = 12V, and can be + * assumed to always be in triode mode. For Rw, the situation is rather + * more complex, as it turns out that this transistor will operate in + * both subthreshold, triode, and saturation modes. + * + * The Shichman-Hodges transistor model routinely used in textbooks may + * be written as follows: + * + * Ids = 0 , Vgst < 0 (subthreshold mode) + * Ids = K*W/L*(2*Vgst - Vds)*Vds , Vgst >= 0, Vds < Vgst (triode mode) + * Ids = K*W/L*Vgst^2 , Vgst >= 0, Vds >= Vgst (saturation mode) + * + * where + * K = u*Cox/2 (transconductance coefficient) + * W/L = ratio between substrate width and length + * Vgst = Vg - Vs - Vt (overdrive voltage) + * + * This transistor model is also called the quadratic model. + * + * Note that the equation for the triode mode can be reformulated as + * independent terms depending on Vgs and Vgd, respectively, by the + * following substitution: + * + * Vds = Vgst - (Vgst - Vds) = Vgst - Vgdt + * + * Ids = K*W/L*(2*Vgst - Vds)*Vds + * = K*W/L*(2*Vgst - (Vgst - Vgdt)*(Vgst - Vgdt) + * = K*W/L*(Vgst + Vgdt)*(Vgst - Vgdt) + * = K*W/L*(Vgst^2 - Vgdt^2) + * + * This turns out to be a general equation which covers both the triode + * and saturation modes (where the second term is 0 in saturation mode). + * The equation is also symmetrical, i.e. it can calculate negative + * currents without any change of parameters (since the terms for drain + * and source are identical except for the sign). + * + * FIXME: Subthreshold as function of Vgs, Vgd. + * + * Ids = I0*W/L*e^(Vgst/(Ut/k)) , Vgst < 0 (subthreshold mode) + * + * where + * I0 = (2 * uCox * Ut^2) / k + * + * The remaining problem with the textbook model is that the transition + * from subthreshold the triode/saturation is not continuous. + * + * Realizing that the subthreshold and triode/saturation modes may both + * be defined by independent (and equal) terms of Vgs and Vds, + * respectively, the corresponding terms can be blended into (equal) + * continuous functions suitable for table lookup. + * + * The EKV model (Enz, Krummenacher and Vittoz) essentially performs this + * blending using an elegant mathematical formulation: + * + * Ids = Is * (if - ir) + * Is = ((2 * u*Cox * Ut^2)/k) * W/L + * if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + * ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + * + * For our purposes, the EKV model preserves two important properties + * discussed above: + * + * - It consists of two independent terms, which can be represented by + * the same lookup table. + * - It is symmetrical, i.e. it calculates current in both directions, + * facilitating a branch-free implementation. + * + * Rw in the circuit diagram above is a VCR (voltage controlled resistor), + * as shown in the circuit diagram below. + * + * + * Vdd + * | + * Vdd _|_ + * | +---+ +---- Vw + * _|_ | + * +--+ +---o Vg + * | __|__ + * | ----- Rw + * | | | + * vi -----o------+ +-------- vo + * + * + * In order to calculalate the current through the VCR, its gate voltage + * must be determined. + * + * Assuming triode mode and applying Kirchoff's current law, we get the + * following equation for Vg: + * + * u*Cox/2*W/L*((nVddt - Vg)^2 - (nVddt - vi)^2 + (nVddt - Vg)^2 - (nVddt - Vw)^2) = 0 + * 2*(nVddt - Vg)^2 - (nVddt - vi)^2 - (nVddt - Vw)^2 = 0 + * (nVddt - Vg) = sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2) + * + * Vg = nVddt - sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2) + */ +class Integrator6581 +{ +private: + unsigned int nVddt_Vw_2; + mutable int vx; + mutable int vc; + +#ifdef SLOPE_FACTOR + // Slope factor n = 1/k + // where k is the gate coupling coefficient + // k = Cox/(Cox+Cdep) ~ 0.7 (depends on gate voltage) + mutable double n; +#endif + const unsigned short nVddt; + const unsigned short nVt; + const unsigned short nVmin; + const unsigned short nSnake; + + const FilterModelConfig6581* fmc; + +public: + Integrator6581(const FilterModelConfig6581* fmc, + double WL_snake) : + nVddt_Vw_2(0), + vx(0), + vc(0), +#ifdef SLOPE_FACTOR + n(1.4), +#endif + nVddt(fmc->getNormalizedValue(fmc->getVddt())), + nVt(fmc->getNormalizedValue(fmc->getVth())), + nVmin(fmc->getNVmin()), + nSnake(fmc->getNormalizedCurrentFactor(WL_snake)), + fmc(fmc) {} + + void setVw(unsigned short Vw) { nVddt_Vw_2 = ((nVddt - Vw) * (nVddt - Vw)) >> 1; } + + int solve(int vi) const; +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(INTEGRATOR_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +int Integrator6581::solve(int vi) const +{ + // Make sure Vgst>0 so we're not in subthreshold mode + assert(vx < nVddt); + + // Check that transistor is actually in triode mode + // Vds < Vgs - Vth + assert(vi < nVddt); + + // "Snake" voltages for triode mode calculation. + const unsigned int Vgst = nVddt - vx; + const unsigned int Vgdt = nVddt - vi; + + const unsigned int Vgst_2 = Vgst * Vgst; + const unsigned int Vgdt_2 = Vgdt * Vgdt; + + // "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + const int n_I_snake = nSnake * (static_cast(Vgst_2 - Vgdt_2) >> 15); + + // VCR gate voltage. // Scaled by m*2^16 + // Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2) + const int nVg = static_cast(fmc->getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16)); +#ifdef SLOPE_FACTOR + const double nVp = static_cast(nVg - nVt) / n; // Pinch-off voltage + const int kVg = static_cast(nVp + 0.5) - nVmin; +#else + const int kVg = (nVg - nVt) - nVmin; +#endif + + // VCR voltages for EKV model table lookup. + const int kVgt_Vs = (vx < kVg) ? kVg - vx : 0; + assert(kVgt_Vs < (1 << 16)); + const int kVgt_Vd = (vi < kVg) ? kVg - vi : 0; + assert(kVgt_Vd < (1 << 16)); + + // VCR current, scaled by m*2^15*2^15 = m*2^30 + const unsigned int If = static_cast(fmc->getVcr_n_Ids_term(kVgt_Vs)) << 15; + const unsigned int Ir = static_cast(fmc->getVcr_n_Ids_term(kVgt_Vd)) << 15; +#ifdef SLOPE_FACTOR + const double iVcr = static_cast(If - Ir); + const int n_I_vcr = static_cast((iVcr * n) + 0.5); +#else + const int n_I_vcr = If - Ir; +#endif + +#ifdef SLOPE_FACTOR + // estimate new slope factor based on gate voltage + const double gamma = 1.0; // body effect factor + const double phi = 0.8; // bulk Fermi potential + const double Vp = nVp / fmc->getN16(); + n = 1. + (gamma / (2. * sqrt(Vp + phi + 4. * fmc->getUt()))); + assert((n > 1.2) && (n < 1.8)); +#endif + + // Change in capacitor charge. + vc += n_I_snake + n_I_vcr; + + // vx = g(vc) + const int tmp = (vc >> 15) + (1 << 15); + assert(tmp < (1 << 16)); + vx = fmc->getOpampRev(tmp); + + // Return vo. + return vx - (vc >> 14); +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Integrator8580.cpp b/src/engine/platform/sound/c64_fp/Integrator8580.cpp new file mode 100644 index 00000000..6fba9521 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator8580.cpp @@ -0,0 +1,25 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014-2016 Leandro Nini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define INTEGRATOR8580_CPP + +#include "Integrator8580.h" + +// This is needed when compiling with --disable-inline diff --git a/src/engine/platform/sound/c64_fp/Integrator8580.h b/src/engine/platform/sound/c64_fp/Integrator8580.h new file mode 100644 index 00000000..7137e940 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator8580.h @@ -0,0 +1,142 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004, 2010 Dag Lem + * + * 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 INTEGRATOR8580_H +#define INTEGRATOR8580_H + +#include "FilterModelConfig8580.h" + +#include +#include + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * 8580 integrator + * + * +---C---+ + * | | + * vi -----Rfc---o--[A>--o-- vo + * vx + * + * IRfc + ICr = 0 + * IRfc + C*(vc - vc0)/dt = 0 + * dt/C*(IRfc) + vc - vc0 = 0 + * vc = vc0 - n*(IRfc(vi,vx)) + * vc = vc0 - n*(IRfc(vi,g(vc))) + * + * IRfc = K*W/L*(Vgst^2 - Vgdt^2) = n*((Vddt - vx)^2 - (Vddt - vi)^2) + * + * Rfc gate voltage is generated by an OP Amp and depends on chip temperature. + */ +class Integrator8580 +{ +private: + mutable int vx; + mutable int vc; + + unsigned short nVgt; + unsigned short n_dac; + + const FilterModelConfig8580* fmc; + +public: + Integrator8580(const FilterModelConfig8580* fmc) : + vx(0), + vc(0), + fmc(fmc) + { + setV(1.5); + } + + /** + * Set Filter Cutoff resistor ratio. + */ + void setFc(double wl) + { + // Normalized current factor, 1 cycle at 1MHz. + // Fit in 5 bits. + n_dac = fmc->getNormalizedCurrentFactor(wl); + } + + /** + * Set FC gate voltage multiplier. + */ + void setV(double v) + { + // Gate voltage is controlled by the switched capacitor voltage divider + // Ua = Ue * v = 4.76v 1 1.0 && v < 2.0); + const double Vg = fmc->getVoiceDCVoltage() * v; + const double Vgt = Vg - fmc->getVth(); + + // Vg - Vth, normalized so that translated values can be subtracted: + // Vgt - x = (Vgt - t) - (x - t) + nVgt = fmc->getNormalizedValue(Vgt); + } + + int solve(int vi) const; +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(INTEGRATOR8580_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +int Integrator8580::solve(int vi) const +{ + // Make sure we're not in subthreshold mode + assert(vx < nVgt); + + // DAC voltages + const unsigned int Vgst = nVgt - vx; + const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode + + const unsigned int Vgst_2 = Vgst * Vgst; + const unsigned int Vgdt_2 = Vgdt * Vgdt; + + // DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + const int n_I_dac = n_dac * (static_cast(Vgst_2 - Vgdt_2) >> 15); + + // Change in capacitor charge. + vc += n_I_dac; + + // vx = g(vc) + const int tmp = (vc >> 15) + (1 << 15); + assert(tmp < (1 << 16)); + vx = fmc->getOpampRev(tmp); + + // Return vo. + return vx - (vc >> 14); +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/OpAmp.cpp b/src/engine/platform/sound/c64_fp/OpAmp.cpp new file mode 100644 index 00000000..b26b2efc --- /dev/null +++ b/src/engine/platform/sound/c64_fp/OpAmp.cpp @@ -0,0 +1,84 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 "OpAmp.h" + +#include + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +const double EPSILON = 1e-8; + +double OpAmp::solve(double n, double vi) const +{ + // Start off with an estimate of x and a root bracket [ak, bk]. + // f is decreasing, so that f(ak) > 0 and f(bk) < 0. + double ak = vmin; + double bk = vmax; + + const double a = n + 1.; + const double b = Vddt; + const double b_vi = (b > vi) ? (b - vi) : 0.; + const double c = n * (b_vi * b_vi); + + for (;;) + { + const double xk = x; + + // Calculate f and df. + + Spline::Point out = opamp->evaluate(x); + const double vo = out.x; + const double dvo = out.y; + + const double b_vx = (b > x) ? b - x : 0.; + const double b_vo = (b > vo) ? b - vo : 0.; + + // f = a*(b - vx)^2 - c - (b - vo)^2 + const double f = a * (b_vx * b_vx) - c - (b_vo * b_vo); + + // df = 2*((b - vo)*dvo - a*(b - vx)) + const double df = 2. * (b_vo * dvo - a * b_vx); + + // Newton-Raphson step: xk1 = xk - f(xk)/f'(xk) + x -= f / df; + + if (unlikely(fabs(x - xk) < EPSILON)) + { + out = opamp->evaluate(x); + return out.x; + } + + // Narrow down root bracket. + (f < 0. ? bk : ak) = xk; + + if (unlikely(x <= ak) || unlikely(x >= bk)) + { + // Bisection step (ala Dekker's method). + x = (ak + bk) * 0.5; + } + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/OpAmp.h b/src/engine/platform/sound/c64_fp/OpAmp.h new file mode 100644 index 00000000..9d2c8f16 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/OpAmp.h @@ -0,0 +1,113 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 OPAMP_H +#define OPAMP_H + +#include +#include + +#include "Spline.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Find output voltage in inverting gain and inverting summer SID op-amp + * circuits, using a combination of Newton-Raphson and bisection. + * + * +---R2--+ + * | | + * vi ---R1--o--[A>--o-- vo + * vx + * + * From Kirchoff's current law it follows that + * + * IR1f + IR2r = 0 + * + * Substituting the triode mode transistor model K*W/L*(Vgst^2 - Vgdt^2) + * for the currents, we get: + * + * n*((Vddt - vx)^2 - (Vddt - vi)^2) + (Vddt - vx)^2 - (Vddt - vo)^2 = 0 + * + * Our root function f can thus be written as: + * + * f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - vo)^2 = 0 + * + * Using substitution constants + * + * a = n + 1 + * b = Vddt + * c = n*(Vddt - vi)^2 + * + * the equations for the root function and its derivative can be written as: + * + * f = a*(b - vx)^2 - c - (b - vo)^2 + * df = 2*((b - vo)*dvo - a*(b - vx)) + */ +class OpAmp +{ +private: + /// Current root position (cached as guess to speed up next iteration) + mutable double x; + + const double Vddt; + const double vmin; + const double vmax; + + std::unique_ptr const opamp; + +public: + /** + * Opamp input -> output voltage conversion + * + * @param opamp opamp mapping table as pairs of points (in -> out) + * @param opamplength length of the opamp array + * @param kVddt transistor dt parameter (in volts) + */ + OpAmp(const std::vector &opamp, double Vddt) : + x(0.), + Vddt(Vddt), + vmin(opamp.front().x), + vmax(opamp.back().x), + opamp(new Spline(opamp)) {} + + void reset() const + { + x = vmin; + } + + /** + * Solve the opamp equation for input vi in loading context n + * + * @param n the ratio of input/output loading + * @param vi input + * @return vo + */ + double solve(double n, double vi) const; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Potentiometer.h b/src/engine/platform/sound/c64_fp/Potentiometer.h new file mode 100644 index 00000000..8b63df13 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Potentiometer.h @@ -0,0 +1,50 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright (C) 2004 Dag Lem + * + * 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 POTENTIOMETER_H +#define POTENTIOMETER_H + +namespace reSIDfp +{ + +/** + * Potentiometer representation. + * + * This class will probably never be implemented in any real way. + * + * @author Ken Händel + * @author Dag Lem + */ +class Potentiometer +{ +public: + /** + * Read paddle value. Not modeled. + * + * @return paddle value (always 0xff) + */ + unsigned char readPOT() const { return 0xff; } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/README b/src/engine/platform/sound/c64_fp/README new file mode 100644 index 00000000..45d4bfb9 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/README @@ -0,0 +1,20 @@ +reSIDfp is a fork of Dag Lem's reSID 0.16, a reverse engineered software emulation +of the MOS6581/8580 SID (Sound Interface Device). + +The project was started by Antti S. Lankila in order to improve SID emulation +with special focus on the 6581 filter. +The codebase has been later on ported to java by Ken Händel within the jsidplay2 project +and has seen further work by Antti Lankila. +It was then ported back to c++ and integrated with improvements from reSID 1.0 by Leandro Nini. + + +Main differences from reSID: + +* combined waveforms are emulated by a parametrized model based on samplings from Kevtris; +* envelope generator is implemented like in the real machine with a shift register; +* high quality resampling is done in two steps to allow computational savings using lower order filters; +* part of the calculations are done with floats instead of fixed point; +* interpolation is accomplished with Fritsch-Carlson method to preserve monotonicity. + + +reSIDfp is free software. See the file COPYING for copying permission. diff --git a/src/engine/platform/sound/c64_fp/SID.cpp b/src/engine/platform/sound/c64_fp/SID.cpp new file mode 100644 index 00000000..a996d223 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/SID.cpp @@ -0,0 +1,504 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define SID_CPP + +#include "SID.h" + +#include + +#include "array.h" +#include "Dac.h" +#include "Filter6581.h" +#include "Filter8580.h" +#include "Potentiometer.h" +#include "WaveformCalculator.h" +#include "resample/TwoPassSincResampler.h" +#include "resample/ZeroOrderResampler.h" + +namespace reSIDfp +{ + +const unsigned int ENV_DAC_BITS = 8; +const unsigned int OSC_DAC_BITS = 12; + +/** + * The waveform D/A converter introduces a DC offset in the signal + * to the envelope multiplying D/A converter. The "zero" level of + * the waveform D/A converter can be found as follows: + * + * Measure the "zero" voltage of voice 3 on the SID audio output + * pin, routing only voice 3 to the mixer ($d417 = $0b, $d418 = + * $0f, all other registers zeroed). + * + * Then set the sustain level for voice 3 to maximum and search for + * the waveform output value yielding the same voltage as found + * above. This is done by trying out different waveform output + * values until the correct value is found, e.g. with the following + * program: + * + * lda #$08 + * sta $d412 + * lda #$0b + * sta $d417 + * lda #$0f + * sta $d418 + * lda #$f0 + * sta $d414 + * lda #$21 + * sta $d412 + * lda #$01 + * sta $d40e + * + * ldx #$00 + * lda #$38 ; Tweak this to find the "zero" level + *l cmp $d41b + * bne l + * stx $d40e ; Stop frequency counter - freeze waveform output + * brk + * + * The waveform output range is 0x000 to 0xfff, so the "zero" + * level should ideally have been 0x800. In the measured chip, the + * waveform output "zero" level was found to be 0x380 (i.e. $d41b + * = 0x38) at an audio output voltage of 5.94V. + * + * With knowledge of the mixer op-amp characteristics, further estimates + * of waveform voltages can be obtained by sampling the EXT IN pin. + * From EXT IN samples, the corresponding waveform output can be found by + * using the model for the mixer. + * + * Such measurements have been done on a chip marked MOS 6581R4AR + * 0687 14, and the following results have been obtained: + * * The full range of one voice is approximately 1.5V. + * * The "zero" level rides at approximately 5.0V. + * + * + * zero-x did the measuring on the 8580 (https://sourceforge.net/p/vice-emu/bugs/1036/#c5b3): + * When it sits on basic from powerup it's at 4.72 + * Run 1.prg and check the output pin level. + * Then run 2.prg andadjust it until the output level is the same... + * 0x94-0xA8 gives me the same 4.72 1.prg shows. + * On another 8580 it's 0x90-0x9C + * Third chip 0x94-0xA8 + * Fourth chip 0x90-0xA4 + * On the 8580 that plays digis the output is 4.66 and 0x93 is the only value to reach that. + * To me that seems as regular 8580s have somewhat wide 0-level range, + * whereas that digi-compatible 8580 has it very narrow. + * On my 6581R4AR has 0x3A as the only value giving the same output level as 1.prg + */ +//@{ +unsigned int constexpr OFFSET_6581 = 0x380; +unsigned int constexpr OFFSET_8580 = 0x9c0; +//@} + +/** + * Bus value stays alive for some time after each operation. + * Values differs between chip models, the timings used here + * are taken from VICE [1]. + * See also the discussion "How do I reliably detect 6581/8580 sid?" on CSDb [2]. + * + * Results from real C64 (testprogs/SID/bitfade/delayfrq0.prg): + * + * (new SID) (250469/8580R5) (250469/8580R5) + * delayfrq0 ~7a000 ~108000 + * + * (old SID) (250407/6581) + * delayfrq0 ~01d00 + * + * [1]: http://sourceforge.net/p/vice-emu/patches/99/ + * [2]: http://noname.c64.org/csdb/forums/?roomid=11&topicid=29025&showallposts=1 + */ +//@{ +int constexpr BUS_TTL_6581 = 0x01d00; +int constexpr BUS_TTL_8580 = 0xa2000; +//@} + +SID::SID() : + filter6581(new Filter6581()), + filter8580(new Filter8580()), + externalFilter(new ExternalFilter()), + resampler(nullptr), + potX(new Potentiometer()), + potY(new Potentiometer()) +{ + voice[0].reset(new Voice()); + voice[1].reset(new Voice()); + voice[2].reset(new Voice()); + + muted[0] = muted[1] = muted[2] = false; + + reset(); + setChipModel(MOS8580); +} + +SID::~SID() +{ + // Needed to delete auto_ptr with complete type +} + +void SID::setFilter6581Curve(double filterCurve) +{ + filter6581->setFilterCurve(filterCurve); +} + +void SID::setFilter8580Curve(double filterCurve) +{ + filter8580->setFilterCurve(filterCurve); +} + +void SID::enableFilter(bool enable) +{ + filter6581->enable(enable); + filter8580->enable(enable); +} + +void SID::voiceSync(bool sync) +{ + if (sync) + { + // Synchronize the 3 waveform generators. + for (int i = 0; i < 3; i++) + { + voice[i]->wave()->synchronize(voice[(i + 1) % 3]->wave(), voice[(i + 2) % 3]->wave()); + } + } + + // Calculate the time to next voice sync + nextVoiceSync = std::numeric_limits::max(); + + for (int i = 0; i < 3; i++) + { + WaveformGenerator* const wave = voice[i]->wave(); + const unsigned int freq = wave->readFreq(); + + if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3]->wave()->readSync()) + { + continue; + } + + const unsigned int accumulator = wave->readAccumulator(); + const unsigned int thisVoiceSync = ((0x7fffff - accumulator) & 0xffffff) / freq + 1; + + if (thisVoiceSync < nextVoiceSync) + { + nextVoiceSync = thisVoiceSync; + } + } +} + +void SID::setChipModel(ChipModel model) +{ + switch (model) + { + case MOS6581: + filter = filter6581.get(); + modelTTL = BUS_TTL_6581; + break; + + case MOS8580: + filter = filter8580.get(); + modelTTL = BUS_TTL_8580; + break; + + default: + throw SIDError("Unknown chip type"); + } + + this->model = model; + + // calculate waveform-related tables + matrix_t* tables = WaveformCalculator::getInstance()->buildTable(model); + + // calculate envelope DAC table + { + Dac dacBuilder(ENV_DAC_BITS); + dacBuilder.kinkedDac(model); + + for (unsigned int i = 0; i < (1 << ENV_DAC_BITS); i++) + { + envDAC[i] = static_cast(dacBuilder.getOutput(i)); + } + } + + // calculate oscillator DAC table + const bool is6581 = model == MOS6581; + + { + Dac dacBuilder(OSC_DAC_BITS); + dacBuilder.kinkedDac(model); + + const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580); + + for (unsigned int i = 0; i < (1 << OSC_DAC_BITS); i++) + { + const double dacValue = dacBuilder.getOutput(i); + oscDAC[i] = static_cast(dacValue - offset); + } + } + + // set voice tables + for (int i = 0; i < 3; i++) + { + voice[i]->setEnvDAC(envDAC); + voice[i]->setWavDAC(oscDAC); + voice[i]->wave()->setModel(is6581); + voice[i]->wave()->setWaveformModels(tables); + } +} + +void SID::reset() +{ + for (int i = 0; i < 3; i++) + { + voice[i]->reset(); + } + + filter6581->reset(); + filter8580->reset(); + externalFilter->reset(); + + if (resampler.get()) + { + resampler->reset(); + } + + busValue = 0; + busValueTtl = 0; + voiceSync(false); +} + +void SID::input(int value) +{ + filter6581->input(value); + filter8580->input(value); +} + +unsigned char SID::read(int offset) +{ + switch (offset) + { + case 0x19: // X value of paddle + busValue = potX->readPOT(); + busValueTtl = modelTTL; + break; + + case 0x1a: // Y value of paddle + busValue = potY->readPOT(); + busValueTtl = modelTTL; + break; + + case 0x1b: // Voice #3 waveform output + busValue = voice[2]->wave()->readOSC(); + busValueTtl = modelTTL; + break; + + case 0x1c: // Voice #3 ADSR output + busValue = voice[2]->envelope()->readENV(); + busValueTtl = modelTTL; + break; + + default: + // Reading from a write-only or non-existing register + // makes the bus discharge faster. + // Emulate this by halving the residual TTL. + busValueTtl /= 2; + break; + } + + return busValue; +} + +void SID::write(int offset, unsigned char value) +{ + busValue = value; + busValueTtl = modelTTL; + + switch (offset) + { + case 0x00: // Voice #1 frequency (Low-byte) + voice[0]->wave()->writeFREQ_LO(value); + break; + + case 0x01: // Voice #1 frequency (High-byte) + voice[0]->wave()->writeFREQ_HI(value); + break; + + case 0x02: // Voice #1 pulse width (Low-byte) + voice[0]->wave()->writePW_LO(value); + break; + + case 0x03: // Voice #1 pulse width (bits #8-#15) + voice[0]->wave()->writePW_HI(value); + break; + + case 0x04: // Voice #1 control register + voice[0]->writeCONTROL_REG(muted[0] ? 0 : value); + break; + + case 0x05: // Voice #1 Attack and Decay length + voice[0]->envelope()->writeATTACK_DECAY(value); + break; + + case 0x06: // Voice #1 Sustain volume and Release length + voice[0]->envelope()->writeSUSTAIN_RELEASE(value); + break; + + case 0x07: // Voice #2 frequency (Low-byte) + voice[1]->wave()->writeFREQ_LO(value); + break; + + case 0x08: // Voice #2 frequency (High-byte) + voice[1]->wave()->writeFREQ_HI(value); + break; + + case 0x09: // Voice #2 pulse width (Low-byte) + voice[1]->wave()->writePW_LO(value); + break; + + case 0x0a: // Voice #2 pulse width (bits #8-#15) + voice[1]->wave()->writePW_HI(value); + break; + + case 0x0b: // Voice #2 control register + voice[1]->writeCONTROL_REG(muted[1] ? 0 : value); + break; + + case 0x0c: // Voice #2 Attack and Decay length + voice[1]->envelope()->writeATTACK_DECAY(value); + break; + + case 0x0d: // Voice #2 Sustain volume and Release length + voice[1]->envelope()->writeSUSTAIN_RELEASE(value); + break; + + case 0x0e: // Voice #3 frequency (Low-byte) + voice[2]->wave()->writeFREQ_LO(value); + break; + + case 0x0f: // Voice #3 frequency (High-byte) + voice[2]->wave()->writeFREQ_HI(value); + break; + + case 0x10: // Voice #3 pulse width (Low-byte) + voice[2]->wave()->writePW_LO(value); + break; + + case 0x11: // Voice #3 pulse width (bits #8-#15) + voice[2]->wave()->writePW_HI(value); + break; + + case 0x12: // Voice #3 control register + voice[2]->writeCONTROL_REG(muted[2] ? 0 : value); + break; + + case 0x13: // Voice #3 Attack and Decay length + voice[2]->envelope()->writeATTACK_DECAY(value); + break; + + case 0x14: // Voice #3 Sustain volume and Release length + voice[2]->envelope()->writeSUSTAIN_RELEASE(value); + break; + + case 0x15: // Filter cut off frequency (bits #0-#2) + filter6581->writeFC_LO(value); + filter8580->writeFC_LO(value); + break; + + case 0x16: // Filter cut off frequency (bits #3-#10) + filter6581->writeFC_HI(value); + filter8580->writeFC_HI(value); + break; + + case 0x17: // Filter control + filter6581->writeRES_FILT(value); + filter8580->writeRES_FILT(value); + break; + + case 0x18: // Volume and filter modes + filter6581->writeMODE_VOL(value); + filter8580->writeMODE_VOL(value); + break; + + default: + break; + } + + // Update voicesync just in case. + voiceSync(false); +} + +void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency) +{ + externalFilter->setClockFrequency(clockFrequency); + + switch (method) + { + case DECIMATE: + resampler.reset(new ZeroOrderResampler(clockFrequency, samplingFrequency)); + break; + + case RESAMPLE: + resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency, highestAccurateFrequency)); + break; + + default: + throw SIDError("Unknown sampling method"); + } +} + +void SID::clockSilent(unsigned int cycles) +{ + ageBusValue(cycles); + + while (cycles != 0) + { + int delta_t = std::min(nextVoiceSync, cycles); + + if (delta_t > 0) + { + for (int i = 0; i < delta_t; i++) + { + // clock waveform generators (can affect OSC3) + voice[0]->wave()->clock(); + voice[1]->wave()->clock(); + voice[2]->wave()->clock(); + + voice[0]->wave()->output(voice[2]->wave()); + voice[1]->wave()->output(voice[0]->wave()); + voice[2]->wave()->output(voice[1]->wave()); + + // clock ENV3 only + voice[2]->envelope()->clock(); + } + + cycles -= delta_t; + nextVoiceSync -= delta_t; + } + + if (nextVoiceSync == 0) + { + voiceSync(true); + } + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/SID.h b/src/engine/platform/sound/c64_fp/SID.h new file mode 100644 index 00000000..05ad83c3 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/SID.h @@ -0,0 +1,372 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 SIDFP_H +#define SIDFP_H + +#include + +#include "siddefs-fp.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class Filter; +class Filter6581; +class Filter8580; +class ExternalFilter; +class Potentiometer; +class Voice; +class Resampler; + +/** + * SID error exception. + */ +class SIDError +{ +private: + const char* message; + +public: + SIDError(const char* msg) : + message(msg) {} + const char* getMessage() const { return message; } +}; + +/** + * MOS6581/MOS8580 emulation. + */ +class SID +{ +private: + /// Currently active filter + Filter* filter; + + /// Filter used, if model is set to 6581 + std::unique_ptr const filter6581; + + /// Filter used, if model is set to 8580 + std::unique_ptr const filter8580; + + /** + * External filter that provides high-pass and low-pass filtering + * to adjust sound tone slightly. + */ + std::unique_ptr const externalFilter; + + /// Resampler used by audio generation code. + std::unique_ptr resampler; + + /// Paddle X register support + std::unique_ptr const potX; + + /// Paddle Y register support + std::unique_ptr const potY; + + /// SID voices + std::unique_ptr voice[3]; + + /// Time to live for the last written value + int busValueTtl; + + /// Current chip model's bus value TTL + int modelTTL; + + /// Time until #voiceSync must be run. + unsigned int nextVoiceSync; + + /// Currently active chip model. + ChipModel model; + + /// Last written value + unsigned char busValue; + + /// Flags for muted channels + bool muted[3]; + + /** + * Emulated nonlinearity of the envelope DAC. + * + * @See Dac + */ + float envDAC[256]; + + /** + * Emulated nonlinearity of the oscillator DAC. + * + * @See Dac + */ + float oscDAC[4096]; + +private: + /** + * Age the bus value and zero it if it's TTL has expired. + * + * @param n the number of cycles + */ + void ageBusValue(unsigned int n); + + /** + * Get output sample. + * + * @return the output sample + */ + int output() const; + + /** + * Calculate the numebr of cycles according to current parameters + * that it takes to reach sync. + * + * @param sync whether to do the actual voice synchronization + */ + void voiceSync(bool sync); + +public: + SID(); + ~SID(); + + /** + * Set chip model. + * + * @param model chip model to use + * @throw SIDError + */ + void setChipModel(ChipModel model); + + /** + * Get currently emulated chip model. + */ + ChipModel getChipModel() const { return model; } + + /** + * SID reset. + */ + void reset(); + + /** + * 16-bit input (EXT IN). Write 16-bit sample to audio input. NB! The caller + * is responsible for keeping the value within 16 bits. Note that to mix in + * an external audio signal, the signal should be resampled to 1MHz first to + * avoid sampling noise. + * + * @param value input level to set + */ + void input(int value); + + /** + * Read registers. + * + * Reading a write only register returns the last char written to any SID register. + * The individual bits in this value start to fade down towards zero after a few cycles. + * All bits reach zero within approximately $2000 - $4000 cycles. + * It has been claimed that this fading happens in an orderly fashion, + * however sampling of write only registers reveals that this is not the case. + * NOTE: This is not correctly modeled. + * The actual use of write only registers has largely been made + * in the belief that all SID registers are readable. + * To support this belief the read would have to be done immediately + * after a write to the same register (remember that an intermediate write + * to another register would yield that value instead). + * With this in mind we return the last value written to any SID register + * for $2000 cycles without modeling the bit fading. + * + * @param offset SID register to read + * @return value read from chip + */ + unsigned char read(int offset); + + /** + * Write registers. + * + * @param offset chip register to write + * @param value value to write + */ + void write(int offset, unsigned char value); + + /** + * SID voice muting. + * + * @param channel channel to modify + * @param enable is muted? + */ + void mute(int channel, bool enable) { muted[channel] = enable; } + + /** + * Setting of SID sampling parameters. + * + * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. + * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 + * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies. + * + * For resampling, the ratio between the clock frequency and the sample frequency + * is limited as follows: 125*clock_freq/sample_freq < 16384 + * E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not be set + * lower than ~ 8kHz. A lower sample frequency would make the resampling code + * overfill its 16k sample ring buffer. + * + * The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2 + * + * E.g. for a 44.1kHz sampling rate the end of passband frequency + * is limited to slightly below 20kHz. + * This constraint ensures that the FIR table is not overfilled. + * + * @param clockFrequency System clock frequency at Hz + * @param method sampling method to use + * @param samplingFrequency Desired output sampling rate + * @param highestAccurateFrequency + * @throw SIDError + */ + void setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency); + + /** + * Clock SID forward using chosen output sampling algorithm. + * + * @param cycles c64 clocks to clock + * @param buf audio output buffer + * @return number of samples produced + */ + int clock(unsigned int cycles, short* buf); + + /** + * Clock SID forward with no audio production. + * + * _Warning_: + * You can't mix this method of clocking with the audio-producing + * clock() because components that don't affect OSC3/ENV3 are not + * emulated. + * + * @param cycles c64 clocks to clock. + */ + void clockSilent(unsigned int cycles); + + /** + * Set filter curve parameter for 6581 model. + * + * @see Filter6581::setFilterCurve(double) + */ + void setFilter6581Curve(double filterCurve); + + /** + * Set filter curve parameter for 8580 model. + * + * @see Filter8580::setFilterCurve(double) + */ + void setFilter8580Curve(double filterCurve); + + /** + * Enable filter emulation. + * + * @param enable false to turn off filter emulation + */ + void enableFilter(bool enable); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(SID_CPP) + +#include + +#include "Filter.h" +#include "ExternalFilter.h" +#include "Voice.h" +#include "resample/Resampler.h" + +namespace reSIDfp +{ + +RESID_INLINE +void SID::ageBusValue(unsigned int n) +{ + if (likely(busValueTtl != 0)) + { + busValueTtl -= n; + + if (unlikely(busValueTtl <= 0)) + { + busValue = 0; + busValueTtl = 0; + } + } +} + +RESID_INLINE +int SID::output() const +{ + const int v1 = voice[0]->output(voice[2]->wave()); + const int v2 = voice[1]->output(voice[0]->wave()); + const int v3 = voice[2]->output(voice[1]->wave()); + + return externalFilter->clock(filter->clock(v1, v2, v3)); +} + + +RESID_INLINE +int SID::clock(unsigned int cycles, short* buf) +{ + ageBusValue(cycles); + int s = 0; + + while (cycles != 0) + { + unsigned int delta_t = std::min(nextVoiceSync, cycles); + + if (likely(delta_t > 0)) + { + for (unsigned int i = 0; i < delta_t; i++) + { + // clock waveform generators + voice[0]->wave()->clock(); + voice[1]->wave()->clock(); + voice[2]->wave()->clock(); + + // clock envelope generators + voice[0]->envelope()->clock(); + voice[1]->envelope()->clock(); + voice[2]->envelope()->clock(); + + if (unlikely(resampler->input(output()))) + { + buf[s++] = resampler->getOutput(); + } + } + + cycles -= delta_t; + nextVoiceSync -= delta_t; + } + + if (unlikely(nextVoiceSync == 0)) + { + voiceSync(true); + } + } + + return s; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Spline.cpp b/src/engine/platform/sound/c64_fp/Spline.cpp new file mode 100644 index 00000000..50d55fef --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Spline.cpp @@ -0,0 +1,119 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 "Spline.h" + +#include +#include + +namespace reSIDfp +{ + +Spline::Spline(const std::vector &input) : + params(input.size()), + c(¶ms[0]) +{ + assert(input.size() > 2); + + const size_t coeffLength = input.size() - 1; + + std::vector dxs(coeffLength); + std::vector ms(coeffLength); + + // Get consecutive differences and slopes + for (size_t i = 0; i < coeffLength; i++) + { + assert(input[i].x < input[i + 1].x); + + const double dx = input[i + 1].x - input[i].x; + const double dy = input[i + 1].y - input[i].y; + dxs[i] = dx; + ms[i] = dy/dx; + } + + // Get degree-1 coefficients + params[0].c = ms[0]; + for (size_t i = 1; i < coeffLength; i++) + { + const double m = ms[i - 1]; + const double mNext = ms[i]; + if (m * mNext <= 0) + { + params[i].c = 0.0; + } + else + { + const double dx = dxs[i - 1]; + const double dxNext = dxs[i]; + const double common = dx + dxNext; + params[i].c = 3.0 * common / ((common + dxNext) / m + (common + dx) / mNext); + } + } + params[coeffLength].c = ms[coeffLength - 1]; + + // Get degree-2 and degree-3 coefficients + for (size_t i = 0; i < coeffLength; i++) + { + params[i].x1 = input[i].x; + params[i].x2 = input[i + 1].x; + params[i].d = input[i].y; + + const double c1 = params[i].c; + const double m = ms[i]; + const double invDx = 1.0 / dxs[i]; + const double common = c1 + params[i + 1].c - m - m; + params[i].b = (m - c1 - common) * invDx; + params[i].a = common * invDx * invDx; + } + + // Fix the upper range, because we interpolate outside original bounds if necessary. + params[coeffLength - 1].x2 = std::numeric_limits::max(); +} + +Spline::Point Spline::evaluate(double x) const +{ + if ((x < c->x1) || (x > c->x2)) + { + for (size_t i = 0; i < params.size(); i++) + { + if (x <= params[i].x2) + { + c = ¶ms[i]; + break; + } + } + } + + // Interpolate + const double diff = x - c->x1; + + Point out; + + // y = a*x^3 + b*x^2 + c*x + d + out.x = ((c->a * diff + c->b) * diff + c->c) * diff + c->d; + + // dy = 3*a*x^2 + 2*b*x + c + out.y = (3.0 * c->a * diff + 2.0 * c->b) * diff + c->c; + + return out; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Spline.h b/src/engine/platform/sound/c64_fp/Spline.h new file mode 100644 index 00000000..6cc2b1ed --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Spline.h @@ -0,0 +1,78 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 SPLINE_H +#define SPLINE_H + +#include +#include + +namespace reSIDfp +{ + +/** + * Fritsch-Carlson monotone cubic spline interpolation. + * + * Based on the implementation from the [Monotone cubic interpolation] wikipedia page. + * + * [Monotone cubic interpolation]: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + */ +class Spline +{ +public: + typedef struct + { + double x; + double y; + } Point; + +private: + typedef struct + { + double x1; + double x2; + double a; + double b; + double c; + double d; + } Param; + + typedef std::vector ParamVector; + +private: + /// Interpolation parameters + ParamVector params; + + /// Last used parameters, cached for speed up + mutable ParamVector::const_pointer c; + +public: + Spline(const std::vector &input); + + /** + * Evaluate y and its derivative at given point x. + */ + Point evaluate(double x) const; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Voice.h b/src/engine/platform/sound/c64_fp/Voice.h new file mode 100644 index 00000000..fc7ed41b --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Voice.h @@ -0,0 +1,130 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 VOICE_H +#define VOICE_H + +#include + +#include "siddefs-fp.h" +#include "WaveformGenerator.h" +#include "EnvelopeGenerator.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Representation of SID voice block. + */ +class Voice +{ +private: + std::unique_ptr const waveformGenerator; + + std::unique_ptr const envelopeGenerator; + + /// The DAC LUT for analog waveform output + float* wavDAC; //-V730_NOINIT this is initialized in the SID constructor + + /// The DAC LUT for analog envelope output + float* envDAC; //-V730_NOINIT this is initialized in the SID constructor + +public: + /** + * Amplitude modulated waveform output. + * + * The waveform DAC generates a voltage between virtual ground and Vdd + * (5-12 V for the 6581 and 4.75-9 V for the 8580) + * corresponding to oscillator state 0 .. 4095. + * + * The envelope DAC generates a voltage between waveform gen output and + * the virtual ground level, corresponding to envelope state 0 .. 255. + * + * Ideal range [-2048*255, 2047*255]. + * + * @param ringModulator Ring-modulator for waveform + * @return the voice analog output + */ + RESID_INLINE + int output(const WaveformGenerator* ringModulator) const + { + unsigned int const wav = waveformGenerator->output(ringModulator); + unsigned int const env = envelopeGenerator->output(); + + // DAC imperfections are emulated by using the digital output + // as an index into a DAC lookup table. + return static_cast(wavDAC[wav] * envDAC[env]); + } + + /** + * Constructor. + */ + Voice() : + waveformGenerator(new WaveformGenerator()), + envelopeGenerator(new EnvelopeGenerator()) {} + + /** + * Set the analog DAC emulation for waveform generator. + * Must be called before any operation. + * + * @param dac + */ + void setWavDAC(float* dac) { wavDAC = dac; } + + /** + * Set the analog DAC emulation for envelope. + * Must be called before any operation. + * + * @param dac + */ + void setEnvDAC(float* dac) { envDAC = dac; } + + WaveformGenerator* wave() const { return waveformGenerator.get(); } + + EnvelopeGenerator* envelope() const { return envelopeGenerator.get(); } + + /** + * Write control register. + * + * @param control Control register value. + */ + void writeCONTROL_REG(unsigned char control) + { + waveformGenerator->writeCONTROL_REG(control); + envelopeGenerator->writeCONTROL_REG(control); + } + + /** + * SID reset. + */ + void reset() + { + waveformGenerator->reset(); + envelopeGenerator->reset(); + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/WaveformCalculator.cpp b/src/engine/platform/sound/c64_fp/WaveformCalculator.cpp new file mode 100644 index 00000000..fe5030fa --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformCalculator.cpp @@ -0,0 +1,204 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 "WaveformCalculator.h" + +#include + +namespace reSIDfp +{ + +WaveformCalculator* WaveformCalculator::getInstance() +{ + static WaveformCalculator instance; + return &instance; +} + +/** + * Parameters derived with the Monte Carlo method based on + * samplings by kevtris. Code and data available in the project repository [1]. + * + * The score here reported is the acoustic error + * calculated XORing the estimated and the sampled values. + * In parentheses the number of mispredicted bits + * on a total of 32768. + * + * [1] https://github.com/libsidplayfp/combined-waveforms + */ +const CombinedWaveformConfig config[2][4] = +{ + { /* kevtris chip G (6581 R2) */ + {0.90251f, 0.f, 0.f, 1.9147f, 1.6747f, 0.62376f }, // error 1689 (280) + {0.93088f, 2.4843f, 0.f, 1.0353f, 1.1484f, 0.f }, // error 6128 (130) + {0.90988f, 2.26303f, 1.13126f, 1.0035f, 1.13801f, 0.f }, // error 14243 (632) + {0.91f, 1.192f, 0.f, 1.0169f, 1.2f, 0.637f }, // error 64 (2) + }, + { /* kevtris chip V (8580 R5) */ + {0.9632f, 0.f, 0.975f, 1.7467f, 2.36132f, 0.975395f}, // error 1380 (169) + {0.92886f, 1.67696f, 0.f, 1.1014f, 1.4352f, 0.f }, // error 8007 (218) + {0.94043f, 1.7937f, 0.981f, 1.1213f, 1.4259f, 0.f }, // error 11957 (362) + {0.96211f, 0.98695f, 1.00387f, 1.46499f, 1.98375f, 0.77777f }, // error 2369 (89) + }, +}; + +/** + * Generate bitstate based on emulation of combined waves. + * + * @param config model parameters matrix + * @param waveform the waveform to emulate, 1 .. 7 + * @param accumulator the high bits of the accumulator value + */ +short calculateCombinedWaveform(const CombinedWaveformConfig& config, int waveform, int accumulator) +{ + float o[12]; + + // Saw + for (unsigned int i = 0; i < 12; i++) + { + o[i] = (accumulator & (1 << i)) != 0 ? 1.f : 0.f; + } + + // convert to Triangle + if ((waveform & 3) == 1) + { + const bool top = (accumulator & 0x800) != 0; + + for (int i = 11; i > 0; i--) + { + o[i] = top ? 1.0f - o[i - 1] : o[i - 1]; + } + + o[0] = 0.f; + } + + // or to Saw+Triangle + else if ((waveform & 3) == 3) + { + // bottom bit is grounded via T waveform selector + o[0] *= config.stmix; + + for (int i = 1; i < 12; i++) + { + /* + * Enabling the S waveform pulls the XOR circuit selector transistor down + * (which would normally make the descending ramp of the triangle waveform), + * so ST does not actually have a sawtooth and triangle waveform combined, + * but merely combines two sawtooths, one rising double the speed the other. + * + * http://www.lemon64.com/forum/viewtopic.php?t=25442&postdays=0&postorder=asc&start=165 + */ + o[i] = o[i - 1] * (1.f - config.stmix) + o[i] * config.stmix; + } + } + + // topbit for Saw + if ((waveform & 2) == 2) + { + o[11] *= config.topbit; + } + + // ST, P* waveforms + if (waveform == 3 || waveform > 4) + { + float distancetable[12 * 2 + 1]; + distancetable[12] = 1.f; + for (int i = 12; i > 0; i--) + { + distancetable[12-i] = 1.0f / pow(config.distance1, i); + distancetable[12+i] = 1.0f / pow(config.distance2, i); + } + + float tmp[12]; + + for (int i = 0; i < 12; i++) + { + float avg = 0.f; + float n = 0.f; + + for (int j = 0; j < 12; j++) + { + const float weight = distancetable[i - j + 12]; + avg += o[j] * weight; + n += weight; + } + + // pulse control bit + if (waveform > 4) + { + const float weight = distancetable[i - 12 + 12]; + avg += config.pulsestrength * weight; + n += weight; + } + + tmp[i] = (o[i] + avg / n) * 0.5f; + } + + for (int i = 0; i < 12; i++) + { + o[i] = tmp[i]; + } + } + + short value = 0; + + for (unsigned int i = 0; i < 12; i++) + { + if (o[i] > config.bias) + { + value |= 1 << i; + } + } + + return value; +} + +matrix_t* WaveformCalculator::buildTable(ChipModel model) +{ + const CombinedWaveformConfig* cfgArray = config[model == MOS6581 ? 0 : 1]; + + cw_cache_t::iterator lb = CACHE.lower_bound(cfgArray); + + if (lb != CACHE.end() && !(CACHE.key_comp()(cfgArray, lb->first))) + { + return &(lb->second); + } + + matrix_t wftable(8, 4096); + + for (unsigned int idx = 0; idx < 1 << 12; idx++) + { + wftable[0][idx] = 0xfff; + wftable[1][idx] = static_cast((idx & 0x800) == 0 ? idx << 1 : (idx ^ 0xfff) << 1); + wftable[2][idx] = static_cast(idx); + wftable[3][idx] = calculateCombinedWaveform(cfgArray[0], 3, idx); + wftable[4][idx] = 0xfff; + wftable[5][idx] = calculateCombinedWaveform(cfgArray[1], 5, idx); + wftable[6][idx] = calculateCombinedWaveform(cfgArray[2], 6, idx); + wftable[7][idx] = calculateCombinedWaveform(cfgArray[3], 7, idx); + } +#ifdef HAVE_CXX11 + return &(CACHE.emplace_hint(lb, cw_cache_t::value_type(cfgArray, wftable))->second); +#else + return &(CACHE.insert(lb, cw_cache_t::value_type(cfgArray, wftable))->second); +#endif +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/WaveformCalculator.h b/src/engine/platform/sound/c64_fp/WaveformCalculator.h new file mode 100644 index 00000000..f9183c5d --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformCalculator.h @@ -0,0 +1,128 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 WAVEFORMCALCULATOR_h +#define WAVEFORMCALCULATOR_h + +#include + +#include "array.h" +#include "sidcxx11.h" +#include "siddefs-fp.h" + + +namespace reSIDfp +{ + +/** + * Combined waveform model parameters. + */ +typedef struct +{ + float bias; + float pulsestrength; + float topbit; + float distance1; + float distance2; + float stmix; +} CombinedWaveformConfig; + +/** + * Combined waveform calculator for WaveformGenerator. + * By combining waveforms, the bits of each waveform are effectively short + * circuited. A zero bit in one waveform will result in a zero output bit + * (thus the infamous claim that the waveforms are AND'ed). + * However, a zero bit in one waveform may also affect the neighboring bits + * in the output. + * + * Example: + * + * 1 1 + * Bit # 1 0 9 8 7 6 5 4 3 2 1 0 + * ----------------------- + * Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0 + * + * Triangle 0 0 1 1 1 1 1 1 0 0 0 0 + * + * AND 0 0 0 1 1 1 1 1 0 0 0 0 + * + * Output 0 0 0 0 1 1 1 0 0 0 0 0 + * + * + * Re-vectorized die photographs reveal the mechanism behind this behavior. + * Each waveform selector bit acts as a switch, which directly connects + * internal outputs into the waveform DAC inputs as follows: + * + * - Noise outputs the shift register bits to DAC inputs as described above. + * Each output is also used as input to the next bit when the shift register + * is shifted. Lower four bits are grounded. + * - Pulse connects a single line to all DAC inputs. The line is connected to + * either 5V (pulse on) or 0V (pulse off) at bit 11, and ends at bit 0. + * - Triangle connects the upper 11 bits of the (MSB EOR'ed) accumulator to the + * DAC inputs, so that DAC bit 0 = 0, DAC bit n = accumulator bit n - 1. + * - Sawtooth connects the upper 12 bits of the accumulator to the DAC inputs, + * so that DAC bit n = accumulator bit n. Sawtooth blocks out the MSB from + * the EOR used to generate the triangle waveform. + * + * We can thus draw the following conclusions: + * + * - The shift register may be written to by combined waveforms. + * - The pulse waveform interconnects all bits in combined waveforms via the + * pulse line. + * - The combination of triangle and sawtooth interconnects neighboring bits + * of the sawtooth waveform. + * + * Also in the 6581 the MSB of the oscillator, used as input for the + * triangle xor logic and the pulse adder's last bit, is connected directly + * to the waveform selector, while in the 8580 it is latched at sid_clk2 + * before being forwarded to the selector. Thus in the 6581 if the sawtooth MSB + * is pulled down it might affect the oscillator's adder + * driving the top bit low. + * + */ +class WaveformCalculator +{ +private: + typedef std::map cw_cache_t; + +private: + cw_cache_t CACHE; + + WaveformCalculator() DEFAULT; + +public: + /** + * Get the singleton instance. + */ + static WaveformCalculator* getInstance(); + + /** + * Build waveform tables for use by WaveformGenerator. + * + * @param model Chip model to use + * @return Waveform table + */ + matrix_t* buildTable(ChipModel model); +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp b/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp new file mode 100644 index 00000000..16a9eb6b --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp @@ -0,0 +1,357 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2021 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define WAVEFORMGENERATOR_CPP + +#include "WaveformGenerator.h" + +/* + * This fixes tests + * SID/wb_testsuite/noise_writeback_check_8_to_C_old + * SID/wb_testsuite/noise_writeback_check_9_to_C_old + * SID/wb_testsuite/noise_writeback_check_A_to_C_old + * SID/wb_testsuite/noise_writeback_check_C_to_C_old + * + * but breaks SID/wf12nsr/wf12nsr + * + * needs more digging... + */ +//#define NO_WB_NOI_PUL + +namespace reSIDfp +{ + +/** + * Number of cycles after which the waveform output fades to 0 when setting + * the waveform register to 0. + * Values measured on warm chips (6581R3/R4 and 8580R5) + * checking OSC3. + * Times vary wildly with temperature and may differ + * from chip to chip so the numbers here represent + * only the big difference between the old and new models. + * + * See [VICE Bug #290](http://sourceforge.net/p/vice-emu/bugs/290/) + * and [VICE Bug #1128](http://sourceforge.net/p/vice-emu/bugs/1128/) + */ +// ~95ms +const unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000; +const unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400; +// ~1s +const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000; +// ~1s +const unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000; +const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; + +/** + * Number of cycles after which the shift register is reset + * when the test bit is set. + * Values measured on warm chips (6581R3/R4 and 8580R5) + * checking OSC3. + * Times vary wildly with temperature and may differ + * from chip to chip so the numbers here represent + * only the big difference between the old and new models. + */ +// ~210ms +const unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000; +const unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000; +// ~2.15s +const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000; +// ~2.8s +const unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000; +const unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300; + +/* + * This is what happens when the lfsr is clocked: + * + * cycle 0: bit 19 of the accumulator goes from low to high, the noise register acts normally, + * the output may overwrite a bit; + * + * cycle 1: first phase of the shift, the bits are interconnected and the output of each bit + * is latched into the following. The output may overwrite the latched value. + * + * cycle 2: second phase of the shift, the latched value becomes active in the first + * half of the clock and from the second half the register returns to normal operation. + * + * When the test or reset lines are active the first phase is executed at every cyle + * until the signal is released triggering the second phase. + */ +void WaveformGenerator::clock_shift_register(unsigned int bit0) +{ + shift_register = (shift_register >> 1) | bit0; + + // New noise waveform output. + set_noise_output(); +} + +unsigned int WaveformGenerator::get_noise_writeback() +{ + return + ~( + (1 << 2) | // Bit 20 + (1 << 4) | // Bit 18 + (1 << 8) | // Bit 14 + (1 << 11) | // Bit 11 + (1 << 13) | // Bit 9 + (1 << 17) | // Bit 5 + (1 << 20) | // Bit 2 + (1 << 22) // Bit 0 + ) | + ((waveform_output & (1 << 11)) >> 9) | // Bit 11 -> bit 20 + ((waveform_output & (1 << 10)) >> 6) | // Bit 10 -> bit 18 + ((waveform_output & (1 << 9)) >> 1) | // Bit 9 -> bit 14 + ((waveform_output & (1 << 8)) << 3) | // Bit 8 -> bit 11 + ((waveform_output & (1 << 7)) << 6) | // Bit 7 -> bit 9 + ((waveform_output & (1 << 6)) << 11) | // Bit 6 -> bit 5 + ((waveform_output & (1 << 5)) << 15) | // Bit 5 -> bit 2 + ((waveform_output & (1 << 4)) << 18); // Bit 4 -> bit 0 +} + +void WaveformGenerator::write_shift_register() +{ + if (unlikely(waveform > 0x8) && likely(!test) && likely(shift_pipeline != 1)) + { + // Write changes to the shift register output caused by combined waveforms + // back into the shift register. This happens only when the register is clocked + // (see $D1+$81_wave_test [1]) or when the test bit is falling. + // A bit once set to zero cannot be changed, hence the and'ing. + // + // [1] ftp://ftp.untergrund.net/users/nata/sid_test/$D1+$81_wave_test.7z + // + // FIXME: Write test program to check the effect of 1 bits and whether + // neighboring bits are affected. + +#ifdef NO_WB_NOI_PUL + if (waveform == 0xc) + return; +#endif + shift_register &= get_noise_writeback(); + + noise_output &= waveform_output; + set_no_noise_or_noise_output(); + } +} + +void WaveformGenerator::set_noise_output() +{ + noise_output = + ((shift_register & (1 << 2)) << 9) | // Bit 20 -> bit 11 + ((shift_register & (1 << 4)) << 6) | // Bit 18 -> bit 10 + ((shift_register & (1 << 8)) << 1) | // Bit 14 -> bit 9 + ((shift_register & (1 << 11)) >> 3) | // Bit 11 -> bit 8 + ((shift_register & (1 << 13)) >> 6) | // Bit 9 -> bit 7 + ((shift_register & (1 << 17)) >> 11) | // Bit 5 -> bit 6 + ((shift_register & (1 << 20)) >> 15) | // Bit 2 -> bit 5 + ((shift_register & (1 << 22)) >> 18); // Bit 0 -> bit 4 + + set_no_noise_or_noise_output(); +} + +void WaveformGenerator::setWaveformModels(matrix_t* models) +{ + model_wave = models; +} + +void WaveformGenerator::synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const +{ + // A special case occurs when a sync source is synced itself on the same + // cycle as when its MSB is set high. In this case the destination will + // not be synced. This has been verified by sampling OSC3. + if (unlikely(msb_rising) && syncDest->sync && !(sync && syncSource->msb_rising)) + { + syncDest->accumulator = 0; + } +} + +bool do_pre_writeback(unsigned int waveform_prev, unsigned int waveform, bool is6581) +{ + // no writeback without combined waveforms + if (likely(waveform_prev <= 0x8)) + return false; + // no writeback when changing to noise + if (waveform == 8) + return false; + // What's happening here? + if (is6581 && + ((((waveform_prev & 0x3) == 0x1) && ((waveform & 0x3) == 0x2)) + || (((waveform_prev & 0x3) == 0x2) && ((waveform & 0x3) == 0x1)))) + return false; + if (waveform_prev == 0xc) + { + if (is6581) + return false; + else if ((waveform != 0x9) && (waveform != 0xe)) + return false; + } +#ifdef NO_WB_NOI_PUL + if (waveform == 0xc) + return false; +#endif + // ok do the writeback + return true; +} + +/* + * When noise and pulse are combined all the bits are + * connected and the four lower ones are grounded. + * This causes the adjacent bits to be pulled down, + * with different strength depending on model. + * + * This is just a rough attempt at modelling the effect. + */ + +static unsigned int noise_pulse6581(unsigned int noise) +{ + return (noise < 0xf00) ? 0x000 : noise & (noise << 1) & (noise << 2); +} + +static unsigned int noise_pulse8580(unsigned int noise) +{ + return (noise < 0xfc0) ? noise & (noise << 1) : 0xfc0; +} + +void WaveformGenerator::set_no_noise_or_noise_output() +{ + no_noise_or_noise_output = no_noise | noise_output; + + // pulse+noise + if (unlikely((waveform & 0xc) == 0xc)) + no_noise_or_noise_output = is6581 + ? noise_pulse6581(no_noise_or_noise_output) + : noise_pulse8580(no_noise_or_noise_output); + +} + +void WaveformGenerator::writeCONTROL_REG(unsigned char control) +{ + const unsigned int waveform_prev = waveform; + const bool test_prev = test; + + waveform = (control >> 4) & 0x0f; + test = (control & 0x08) != 0; + sync = (control & 0x02) != 0; + + // Substitution of accumulator MSB when sawtooth = 0, ring_mod = 1. + ring_msb_mask = ((~control >> 5) & (control >> 2) & 0x1) << 23; + + if (waveform != waveform_prev) + { + // Set up waveform table. + wave = (*model_wave)[waveform & 0x7]; + + // no_noise and no_pulse are used in set_waveform_output() as bitmasks to + // only let the noise or pulse influence the output when the noise or pulse + // waveforms are selected. + no_noise = (waveform & 0x8) != 0 ? 0x000 : 0xfff; + set_no_noise_or_noise_output(); + no_pulse = (waveform & 0x4) != 0 ? 0x000 : 0xfff; + + if (waveform == 0) + { + // Change to floating DAC input. + // Reset fading time for floating DAC input. + floating_output_ttl = is6581 ? FLOATING_OUTPUT_TTL_6581R3 : FLOATING_OUTPUT_TTL_8580R5; + } + } + + if (test != test_prev) + { + if (test) + { + // Reset accumulator. + accumulator = 0; + + // Flush shift pipeline. + shift_pipeline = 0; + + // Set reset time for shift register. + shift_register_reset = is6581 ? SHIFT_REGISTER_RESET_6581R3 : SHIFT_REGISTER_RESET_8580R5; + } + else + { + // When the test bit is falling, the second phase of the shift is + // completed by enabling SRAM write. + + // During first phase of the shift the bits are interconnected + // and the output of each bit is latched into the following. + // The output may overwrite the latched value. + if (do_pre_writeback(waveform_prev, waveform, is6581)) + { + shift_register &= get_noise_writeback(); + } + + // bit0 = (bit22 | test) ^ bit17 = 1 ^ bit17 = ~bit17 + clock_shift_register((~shift_register << 17) & (1 << 22)); + } + } +} + +void WaveformGenerator::waveBitfade() +{ + waveform_output &= waveform_output >> 1; + osc3 = waveform_output; + if (waveform_output != 0) + floating_output_ttl = is6581 ? FLOATING_OUTPUT_FADE_6581R3 : FLOATING_OUTPUT_FADE_8580R5; +} + +void WaveformGenerator::shiftregBitfade() +{ + shift_register |= shift_register >> 1; + shift_register |= 0x400000; + if (shift_register != 0x7fffff) + shift_register_reset = is6581 ? SHIFT_REGISTER_FADE_6581R3 : SHIFT_REGISTER_FADE_8580R5; +} + +void WaveformGenerator::reset() +{ + // accumulator is not changed on reset + freq = 0; + pw = 0; + + msb_rising = false; + + waveform = 0; + osc3 = 0; + + test = false; + sync = false; + + wave = model_wave ? (*model_wave)[0] : nullptr; + + ring_msb_mask = 0; + no_noise = 0xfff; + no_pulse = 0xfff; + pulse_output = 0xfff; + + shift_register_reset = 0; + shift_register = 0x7fffff; + // when reset is released the shift register is clocked once + // so the lower bit is zeroed out + // bit0 = (bit22 | test) ^ bit17 = 1 ^ 1 = 0 + clock_shift_register(0); + + shift_pipeline = 0; + + waveform_output = 0; + floating_output_ttl = 0; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/WaveformGenerator.h b/src/engine/platform/sound/c64_fp/WaveformGenerator.h new file mode 100644 index 00000000..9fd617f6 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformGenerator.h @@ -0,0 +1,396 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 WAVEFORMGENERATOR_H +#define WAVEFORMGENERATOR_H + +#include "siddefs-fp.h" +#include "array.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +/** + * A 24 bit accumulator is the basis for waveform generation. + * FREQ is added to the lower 16 bits of the accumulator each cycle. + * The accumulator is set to zero when TEST is set, and starts counting + * when TEST is cleared. + * + * Waveforms are generated as follows: + * + * - No waveform: + * When no waveform is selected, the DAC input is floating. + * + * + * - Triangle: + * The upper 12 bits of the accumulator are used. + * The MSB is used to create the falling edge of the triangle by inverting + * the lower 11 bits. The MSB is thrown away and the lower 11 bits are + * left-shifted (half the resolution, full amplitude). + * Ring modulation substitutes the MSB with MSB EOR NOT sync_source MSB. + * + * + * - Sawtooth: + * The output is identical to the upper 12 bits of the accumulator. + * + * + * - Pulse: + * The upper 12 bits of the accumulator are used. + * These bits are compared to the pulse width register by a 12 bit digital + * comparator; output is either all one or all zero bits. + * The pulse setting is delayed one cycle after the compare. + * The test bit, when set to one, holds the pulse waveform output at 0xfff + * regardless of the pulse width setting. + * + * + * - Noise: + * The noise output is taken from intermediate bits of a 23-bit shift register + * which is clocked by bit 19 of the accumulator. + * The shift is delayed 2 cycles after bit 19 is set high. + * + * Operation: Calculate EOR result, shift register, set bit 0 = result. + * + * reset +--------------------------------------------+ + * | | | + * test--OR-->EOR<--+ | + * | | | + * 2 2 2 1 1 1 1 1 1 1 1 1 1 | + * Register bits: 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 <---+ + * | | | | | | | | + * Waveform bits: 1 1 9 8 7 6 5 4 + * 1 0 + * + * The low 4 waveform bits are zero (grounded). + */ +class WaveformGenerator +{ +private: + matrix_t* model_wave; + + short* wave; + + // PWout = (PWn/40.95)% + unsigned int pw; + + unsigned int shift_register; + + /// Emulation of pipeline causing bit 19 to clock the shift register. + int shift_pipeline; + + unsigned int ring_msb_mask; + unsigned int no_noise; + unsigned int noise_output; + unsigned int no_noise_or_noise_output; + unsigned int no_pulse; + unsigned int pulse_output; + + /// The control register right-shifted 4 bits; used for output function table lookup. + unsigned int waveform; + + unsigned int waveform_output; + + /// Current accumulator value. + unsigned int accumulator; + + // Fout = (Fn*Fclk/16777216)Hz + unsigned int freq; + + /// 8580 tri/saw pipeline + unsigned int tri_saw_pipeline; + + /// The OSC3 value + unsigned int osc3; + + /// Remaining time to fully reset shift register. + unsigned int shift_register_reset; + + // The wave signal TTL when no waveform is selected + unsigned int floating_output_ttl; + + /// The control register bits. Gate is handled by EnvelopeGenerator. + //@{ + bool test; + bool sync; + //@} + + /// Tell whether the accumulator MSB was set high on this cycle. + bool msb_rising; + + bool is6581; //-V730_NOINIT this is initialized in the SID constructor + +private: + void clock_shift_register(unsigned int bit0); + + unsigned int get_noise_writeback(); + + void write_shift_register(); + + void set_noise_output(); + + void set_no_noise_or_noise_output(); + + void waveBitfade(); + + void shiftregBitfade(); + +public: + void setWaveformModels(matrix_t* models); + + /** + * Set the chip model. + * Must be called before any operation. + * + * @param is6581 true if MOS6581, false if CSG8580 + */ + void setModel(bool is6581) { this->is6581 = is6581; } + + /** + * SID clocking. + */ + void clock(); + + /** + * Synchronize oscillators. + * This must be done after all the oscillators have been clock()'ed, + * so that they are in the same state. + * + * @param syncDest The oscillator that will be synced + * @param syncSource The sync source oscillator + */ + void synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const; + + /** + * Constructor. + */ + WaveformGenerator() : + model_wave(nullptr), + wave(nullptr), + pw(0), + shift_register(0), + shift_pipeline(0), + ring_msb_mask(0), + no_noise(0), + noise_output(0), + no_noise_or_noise_output(0), + no_pulse(0), + pulse_output(0), + waveform(0), + waveform_output(0), + accumulator(0x555555), // Accumulator's even bits are high on powerup + freq(0), + tri_saw_pipeline(0x555), + osc3(0), + shift_register_reset(0), + floating_output_ttl(0), + test(false), + sync(false), + msb_rising(false) {} + + /** + * Write FREQ LO register. + * + * @param freq_lo low 8 bits of frequency + */ + void writeFREQ_LO(unsigned char freq_lo) { freq = (freq & 0xff00) | (freq_lo & 0xff); } + + /** + * Write FREQ HI register. + * + * @param freq_hi high 8 bits of frequency + */ + void writeFREQ_HI(unsigned char freq_hi) { freq = (freq_hi << 8 & 0xff00) | (freq & 0xff); } + + /** + * Write PW LO register. + * + * @param pw_lo low 8 bits of pulse width + */ + void writePW_LO(unsigned char pw_lo) { pw = (pw & 0xf00) | (pw_lo & 0x0ff); } + + /** + * Write PW HI register. + * + * @param pw_hi high 8 bits of pulse width + */ + void writePW_HI(unsigned char pw_hi) { pw = (pw_hi << 8 & 0xf00) | (pw & 0x0ff); } + + /** + * Write CONTROL REGISTER register. + * + * @param control control register value + */ + void writeCONTROL_REG(unsigned char control); + + /** + * SID reset. + */ + void reset(); + + /** + * 12-bit waveform output. + * + * @param ringModulator The oscillator ring-modulating current one. + * @return the waveform generator digital output + */ + unsigned int output(const WaveformGenerator* ringModulator); + + /** + * Read OSC3 value. + */ + unsigned char readOSC() const { return static_cast(osc3 >> 4); } + + /** + * Read accumulator value. + */ + unsigned int readAccumulator() const { return accumulator; } + + /** + * Read freq value. + */ + unsigned int readFreq() const { return freq; } + + /** + * Read test value. + */ + bool readTest() const { return test; } + + /** + * Read sync value. + */ + bool readSync() const { return sync; } +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(WAVEFORMGENERATOR_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +void WaveformGenerator::clock() +{ + if (unlikely(test)) + { + if (unlikely(shift_register_reset != 0) && unlikely(--shift_register_reset == 0)) + { + shiftregBitfade(); + + // New noise waveform output. + set_noise_output(); + } + + // The test bit sets pulse high. + pulse_output = 0xfff; + } + else + { + // Calculate new accumulator value; + const unsigned int accumulator_old = accumulator; + accumulator = (accumulator + freq) & 0xffffff; + + // Check which bit have changed + const unsigned int accumulator_bits_set = ~accumulator_old & accumulator; + + // Check whether the MSB is set high. This is used for synchronization. + msb_rising = (accumulator_bits_set & 0x800000) != 0; + + // Shift noise register once for each time accumulator bit 19 is set high. + // The shift is delayed 2 cycles. + if (unlikely((accumulator_bits_set & 0x080000) != 0)) + { + // Pipeline: Detect rising bit, shift phase 1, shift phase 2. + shift_pipeline = 2; + } + else if (unlikely(shift_pipeline != 0) && --shift_pipeline == 0) + { + // bit0 = (bit22 | test) ^ bit17 + clock_shift_register(((shift_register << 22) ^ (shift_register << 17)) & (1 << 22)); + } + } +} + +RESID_INLINE +unsigned int WaveformGenerator::output(const WaveformGenerator* ringModulator) +{ + // Set output value. + if (likely(waveform != 0)) + { + const unsigned int ix = (accumulator ^ (~ringModulator->accumulator & ring_msb_mask)) >> 12; + + // The bit masks no_pulse and no_noise are used to achieve branch-free + // calculation of the output value. + waveform_output = wave[ix] & (no_pulse | pulse_output) & no_noise_or_noise_output; + + // Triangle/Sawtooth output is delayed half cycle on 8580. + // This will appear as a one cycle delay on OSC3 as it is latched first phase of the clock. + if ((waveform & 3) && !is6581) + { + osc3 = tri_saw_pipeline & (no_pulse | pulse_output) & no_noise_or_noise_output; + tri_saw_pipeline = wave[ix]; + } + else + { + osc3 = waveform_output; + } + + // In the 6581 the top bit of the accumulator may be driven low by combined waveforms + // when the sawtooth is selected + // FIXME doesn't seem to always happen + if ((waveform & 2) && unlikely(waveform & 0xd) && is6581) + accumulator &= (waveform_output << 12) | 0x7fffff; + + write_shift_register(); + } + else + { + // Age floating DAC input. + if (likely(floating_output_ttl != 0) && unlikely(--floating_output_ttl == 0)) + { + waveBitfade(); + } + } + + // The pulse level is defined as (accumulator >> 12) >= pw ? 0xfff : 0x000. + // The expression -((accumulator >> 12) >= pw) & 0xfff yields the same + // results without any branching (and thus without any pipeline stalls). + // NB! This expression relies on that the result of a boolean expression + // is either 0 or 1, and furthermore requires two's complement integer. + // A few more cycles may be saved by storing the pulse width left shifted + // 12 bits, and dropping the and with 0xfff (this is valid since pulse is + // used as a bit mask on 12 bit values), yielding the expression + // -(accumulator >= pw24). However this only results in negligible savings. + + // The result of the pulse width compare is delayed one cycle. + // Push next pulse level into pulse level pipeline. + pulse_output = ((accumulator >> 12) >= pw) ? 0xfff : 0x000; + + return waveform_output; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/array.h b/src/engine/platform/sound/c64_fp/array.h new file mode 100644 index 00000000..5291938c --- /dev/null +++ b/src/engine/platform/sound/c64_fp/array.h @@ -0,0 +1,73 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright (C) 2011-2014 Leandro Nini + * + * 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 ARRAY_H +#define ARRAY_H + +/** + * Counter. + */ +class counter +{ +private: + unsigned int c; + +public: + counter() : c(1) {} + void increase() { ++c; } + unsigned int decrease() { return --c; } +}; + +/** + * Reference counted pointer to matrix wrapper, for use with standard containers. + */ +template +class matrix +{ +private: + T* data; + counter* count; + const unsigned int x, y; + +public: + matrix(unsigned int x, unsigned int y) : + data(new T[x * y]), + count(new counter()), + x(x), + y(y) {} + + matrix(const matrix& p) : + data(p.data), + count(p.count), + x(p.x), + y(p.y) { count->increase(); } + + ~matrix() { if (count->decrease() == 0) { delete count; delete [] data; } } + + unsigned int length() const { return x * y; } + + T* operator[](unsigned int a) { return &data[a * y]; } + + T const* operator[](unsigned int a) const { return &data[a * y]; } +}; + +typedef matrix matrix_t; + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/Resampler.h b/src/engine/platform/sound/c64_fp/resample/Resampler.h new file mode 100644 index 00000000..904f6545 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/Resampler.h @@ -0,0 +1,86 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 RESAMPLER_H +#define RESAMPLER_H + +#include + +#include "../sidcxx11.h" + +#include "../siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * Abstraction of a resampling process. Given enough input, produces output. + * Constructors take additional arguments that configure these objects. + */ +class Resampler +{ +protected: + inline short softClip(int x) const + { + constexpr int threshold = 28000; + if (likely(x < threshold)) + return x; + + constexpr double t = threshold / 32768.; + constexpr double a = 1. - t; + constexpr double b = 1. / a; + + double value = static_cast(x - threshold) / 32768.; + value = t + a * tanh(b * value); + return static_cast(value * 32768.); + } + + virtual int output() const = 0; + + Resampler() {} + +public: + virtual ~Resampler() {} + + /** + * Input a sample into resampler. Output "true" when resampler is ready with new sample. + * + * @param sample input sample + * @return true when a sample is ready + */ + virtual bool input(int sample) = 0; + + /** + * Output a sample from resampler. + * + * @return resampled sample + */ + short getOutput() const + { + return softClip(output()); + } + + virtual void reset() = 0; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/SincResampler.cpp b/src/engine/platform/sound/c64_fp/resample/SincResampler.cpp new file mode 100755 index 00000000..adb17f9e --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/SincResampler.cpp @@ -0,0 +1,393 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 "SincResampler.h" + +#include +#include +#include +#include +#include + +#include "../siddefs-fp.h" + +#ifdef HAVE_EMMINTRIN_H +# include +#elif defined HAVE_MMINTRIN_H +# include +#elif defined(HAVE_ARM_NEON_H) +# include +#endif + +namespace reSIDfp +{ + +typedef std::map fir_cache_t; + +/// Cache for the expensive FIR table computation results. +fir_cache_t FIR_CACHE; + +/// Maximum error acceptable in I0 is 1e-6, or ~96 dB. +const double I0E = 1e-6; + +const int BITS = 16; + +/** + * Compute the 0th order modified Bessel function of the first kind. + * This function is originally from resample-1.5/filterkit.c by J. O. Smith. + * It is used to build the Kaiser window for resampling. + * + * @param x evaluate I0 at x + * @return value of I0 at x. + */ +double I0(double x) +{ + double sum = 1.; + double u = 1.; + double n = 1.; + const double halfx = x / 2.; + + do + { + const double temp = halfx / n; + u *= temp * temp; + sum += u; + n += 1.; + } + while (u >= I0E * sum); + + return sum; +} + +/** + * Calculate convolution with sample and sinc. + * + * @param a sample buffer input + * @param b sinc buffer + * @param bLength length of the sinc buffer + * @return convolved result + */ +int convolve(const short* a, const short* b, int bLength) +{ +#ifdef HAVE_EMMINTRIN_H + int out = 0; + + const uintptr_t offset = (uintptr_t)(a) & 0x0f; + + // check for aligned accesses + if (offset == ((uintptr_t)(b) & 0x0f)) + { + if (offset) + { + const int l = (0x10 - offset)/2; + + for (int i = 0; i < l; i++) + { + out += *a++ * *b++; + } + + bLength -= offset; + } + + __m128i acc = _mm_setzero_si128(); + + const int n = bLength / 8; + + for (int i = 0; i < n; i++) + { + const __m128i tmp = _mm_madd_epi16(*(__m128i*)a, *(__m128i*)b); + acc = _mm_add_epi16(acc, tmp); + a += 8; + b += 8; + } + + __m128i vsum = _mm_add_epi32(acc, _mm_srli_si128(acc, 8)); + vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 4)); + out += _mm_cvtsi128_si32(vsum); + + bLength &= 7; + } +#elif defined HAVE_MMINTRIN_H + __m64 acc = _mm_setzero_si64(); + + const int n = bLength / 4; + + for (int i = 0; i < n; i++) + { + const __m64 tmp = _mm_madd_pi16(*(__m64*)a, *(__m64*)b); + acc = _mm_add_pi16(acc, tmp); + a += 4; + b += 4; + } + + int out = _mm_cvtsi64_si32(acc) + _mm_cvtsi64_si32(_mm_srli_si64(acc, 32)); + _mm_empty(); + + bLength &= 3; +#elif defined(HAVE_ARM_NEON_H) +#if (defined(__arm64__) && defined(__APPLE__)) || defined(__aarch64__) + int32x4_t acc1Low = vdupq_n_s32(0); + int32x4_t acc1High = vdupq_n_s32(0); + int32x4_t acc2Low = vdupq_n_s32(0); + int32x4_t acc2High = vdupq_n_s32(0); + + const int n = bLength / 16; + + for (int i = 0; i < n; i++) + { + int16x8_t v11 = vld1q_s16(a); + int16x8_t v12 = vld1q_s16(a + 8); + int16x8_t v21 = vld1q_s16(b); + int16x8_t v22 = vld1q_s16(b + 8); + + acc1Low = vmlal_s16(acc1Low, vget_low_s16(v11), vget_low_s16(v21)); + acc1High = vmlal_high_s16(acc1High, v11, v21); + acc2Low = vmlal_s16(acc2Low, vget_low_s16(v12), vget_low_s16(v22)); + acc2High = vmlal_high_s16(acc2High, v12, v22); + + a += 16; + b += 16; + } + + bLength &= 15; + + if (bLength >= 8) + { + int16x8_t v1 = vld1q_s16(a); + int16x8_t v2 = vld1q_s16(b); + + acc1Low = vmlal_s16(acc1Low, vget_low_s16(v1), vget_low_s16(v2)); + acc1High = vmlal_high_s16(acc1High, v1, v2); + + a += 8; + b += 8; + } + + bLength &= 7; + + if (bLength >= 4) + { + int16x4_t v1 = vld1_s16(a); + int16x4_t v2 = vld1_s16(b); + + acc1Low = vmlal_s16(acc1Low, v1, v2); + + a += 4; + b += 4; + } + + int32x4_t accSumsNeon = vaddq_s32(acc1Low, acc1High); + accSumsNeon = vaddq_s32(accSumsNeon, acc2Low); + accSumsNeon = vaddq_s32(accSumsNeon, acc2High); + + int out = vaddvq_s32(accSumsNeon); + + bLength &= 3; +#else + int32x4_t acc = vdupq_n_s32(0); + + const int n = bLength / 4; + + for (int i = 0; i < n; i++) + { + const int16x4_t h_vec = vld1_s16(a); + const int16x4_t x_vec = vld1_s16(b); + acc = vmlal_s16(acc, h_vec, x_vec); + a += 4; + b += 4; + } + + int out = vgetq_lane_s32(acc, 0) + + vgetq_lane_s32(acc, 1) + + vgetq_lane_s32(acc, 2) + + vgetq_lane_s32(acc, 3); + + bLength &= 3; +#endif +#else + int out = 0; +#endif + + for (int i = 0; i < bLength; i++) + { + out += *a++ * *b++; + } + + return (out + (1 << 14)) >> 15; +} + +int SincResampler::fir(int subcycle) +{ + // Find the first of the nearest fir tables close to the phase + int firTableFirst = (subcycle * firRES >> 10); + const int firTableOffset = (subcycle * firRES) & 0x3ff; + + // Find firN most recent samples, plus one extra in case the FIR wraps. + int sampleStart = sampleIndex - firN + RINGSIZE - 1; + + const int v1 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN); + + // Use next FIR table, wrap around to first FIR table using + // previous sample. + if (unlikely(++firTableFirst == firRES)) + { + firTableFirst = 0; + ++sampleStart; + } + + const int v2 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN); + + // Linear interpolation between the sinc tables yields good + // approximation for the exact value. + return v1 + (firTableOffset * (v2 - v1) >> 10); +} + +SincResampler::SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) : + sampleIndex(0), + cyclesPerSample(static_cast(clockFrequency / samplingFrequency * 1024.)), + sampleOffset(0), + outputValue(0) +{ + // 16 bits -> -96dB stopband attenuation. + const double A = -20. * log10(1.0 / (1 << BITS)); + // A fraction of the bandwidth is allocated to the transition band, which we double + // because we design the filter to transition halfway at nyquist. + const double dw = (1. - 2.*highestAccurateFrequency / samplingFrequency) * M_PI * 2.; + + // For calculation of beta and N see the reference for the kaiserord + // function in the MATLAB Signal Processing Toolbox: + // http://www.mathworks.com/help/signal/ref/kaiserord.html + const double beta = 0.1102 * (A - 8.7); + const double I0beta = I0(beta); + const double cyclesPerSampleD = clockFrequency / samplingFrequency; + + { + // The filter order will maximally be 124 with the current constraints. + // N >= (96.33 - 7.95)/(2 * pi * 2.285 * (maxfreq - passbandfreq) >= 123 + // The filter order is equal to the number of zero crossings, i.e. + // it should be an even number (sinc is symmetric with respect to x = 0). + int N = static_cast((A - 7.95) / (2.285 * dw) + 0.5); + N += N & 1; + + // The filter length is equal to the filter order + 1. + // The filter length must be an odd number (sinc is symmetric with respect to + // x = 0). + firN = static_cast(N * cyclesPerSampleD) + 1; + firN |= 1; + + // Check whether the sample ring buffer would overflow. + assert(firN < RINGSIZE); + + // Error is bounded by err < 1.234 / L^2, so L = sqrt(1.234 / (2^-16)) = sqrt(1.234 * 2^16). + firRES = static_cast(ceil(sqrt(1.234 * (1 << BITS)) / cyclesPerSampleD)); + + // firN*firRES represent the total resolution of the sinc sampling. JOS + // recommends a length of 2^BITS, but we don't quite use that good a filter. + // The filter test program indicates that the filter performs well, though. + } + + // Create the map key + std::ostringstream o; + o << firN << "," << firRES << "," << cyclesPerSampleD; + const std::string firKey = o.str(); + fir_cache_t::iterator lb = FIR_CACHE.lower_bound(firKey); + + // The FIR computation is expensive and we set sampling parameters often, but + // from a very small set of choices. Thus, caching is used to speed initialization. + if (lb != FIR_CACHE.end() && !(FIR_CACHE.key_comp()(firKey, lb->first))) + { + firTable = &(lb->second); + } + else + { + // Allocate memory for FIR tables. + matrix_t tempTable(firRES, firN); +#ifdef HAVE_CXX11 + firTable = &(FIR_CACHE.emplace_hint(lb, fir_cache_t::value_type(firKey, tempTable))->second); +#else + firTable = &(FIR_CACHE.insert(lb, fir_cache_t::value_type(firKey, tempTable))->second); +#endif + + // The cutoff frequency is midway through the transition band, in effect the same as nyquist. + const double wc = M_PI; + + // Calculate the sinc tables. + const double scale = 32768.0 * wc / cyclesPerSampleD / M_PI; + + // we're not interested in the fractional part + // so use int division before converting to double + const int tmp = firN / 2; + const double firN_2 = static_cast(tmp); + + for (int i = 0; i < firRES; i++) + { + const double jPhase = (double) i / firRES + firN_2; + + for (int j = 0; j < firN; j++) + { + const double x = j - jPhase; + + const double xt = x / firN_2; + const double kaiserXt = fabs(xt) < 1. ? I0(beta * sqrt(1. - xt * xt)) / I0beta : 0.; + + const double wt = wc * x / cyclesPerSampleD; + const double sincWt = fabs(wt) >= 1e-8 ? sin(wt) / wt : 1.; + + (*firTable)[i][j] = static_cast(scale * sincWt * kaiserXt); + } + } + } +} + +bool SincResampler::input(int input) +{ + bool ready = false; + + /* + * Clip the input as it may overflow the 16 bit range. + * + * Approximate measured input ranges: + * 6581: [-24262,+25080] (Kawasaki_Synthesizer_Demo) + * 8580: [-21514,+35232] (64_Forever, Drum_Fool) + */ + sample[sampleIndex] = sample[sampleIndex + RINGSIZE] = softClip(input); + sampleIndex = (sampleIndex + 1) & (RINGSIZE - 1); + + if (sampleOffset < 1024) + { + outputValue = fir(sampleOffset); + ready = true; + sampleOffset += cyclesPerSample; + } + + sampleOffset -= 1024; + + return ready; +} + +void SincResampler::reset() +{ + memset(sample, 0, sizeof(sample)); + sampleOffset = 0; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/resample/SincResampler.h b/src/engine/platform/sound/c64_fp/resample/SincResampler.h new file mode 100644 index 00000000..7502d96f --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/SincResampler.h @@ -0,0 +1,114 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 SINCRESAMPLER_H +#define SINCRESAMPLER_H + +#include "Resampler.h" + +#include +#include + +#include "../array.h" + +#include "../sidcxx11.h" + +namespace reSIDfp +{ + +/** + * This is the theoretically correct (and computationally intensive) audio sample generation. + * The samples are generated by resampling to the specified sampling frequency. + * The work rate is inversely proportional to the percentage of the bandwidth + * allocated to the filter transition band. + * + * This implementation is based on the paper "A Flexible Sampling-Rate Conversion Method", + * by J. O. Smith and P. Gosset, or rather on the expanded tutorial on the + * [Digital Audio Resampling Home Page](http://www-ccrma.stanford.edu/~jos/resample/). + * + * By building shifted FIR tables with samples according to the sampling frequency, + * this implementation dramatically reduces the computational effort in the + * filter convolutions, without any loss of accuracy. + * The filter convolutions are also vectorizable on current hardware. + */ +class SincResampler final : public Resampler +{ +private: + /// Size of the ring buffer, must be a power of 2 + static const int RINGSIZE = 2048; + +private: + /// Table of the fir filter coefficients + matrix_t* firTable; + + int sampleIndex; + + /// Filter resolution + int firRES; + + /// Filter length + int firN; + + const int cyclesPerSample; + + int sampleOffset; + + int outputValue; + + short sample[RINGSIZE * 2]; + +private: + int fir(int subcycle); + +public: + /** + * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. + * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 + * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies. + * + * For resampling, the ratio between the clock frequency and the sample frequency + * is limited as follows: 125*clock_freq/sample_freq < 16384 + * E.g. provided a clock frequency of ~ 1MHz, the sample frequency + * can not be set lower than ~ 8kHz. + * A lower sample frequency would make the resampling code overfill its 16k sample ring buffer. + * + * The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2 + * + * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited + * to slightly below 20kHz. This constraint ensures that the FIR table is not overfilled. + * + * @param clockFrequency System clock frequency at Hz + * @param samplingFrequency Desired output sampling rate + * @param highestAccurateFrequency + */ + SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency); + + bool input(int input) override; + + int output() const override { return outputValue; } + + void reset() override; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h b/src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h new file mode 100644 index 00000000..81659193 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h @@ -0,0 +1,83 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 TWOPASSSINCRESAMPLER_H +#define TWOPASSSINCRESAMPLER_H + +#include + +#include + +#include "Resampler.h" +#include "SincResampler.h" + +#include "../sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Compose a more efficient SINC from chaining two other SINCs. + */ +class TwoPassSincResampler final : public Resampler +{ +private: + std::unique_ptr const s1; + std::unique_ptr const s2; + +private: + TwoPassSincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency, double intermediateFrequency) : + s1(new SincResampler(clockFrequency, intermediateFrequency, highestAccurateFrequency)), + s2(new SincResampler(intermediateFrequency, samplingFrequency, highestAccurateFrequency)) + {} + +public: + // Named constructor + static TwoPassSincResampler* create(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) + { + // Calculation according to Laurent Ganier. It evaluates to about 120 kHz at typical settings. + // Some testing around the chosen value seems to confirm that this does work. + double const intermediateFrequency = 2. * highestAccurateFrequency + + sqrt(2. * highestAccurateFrequency * clockFrequency + * (samplingFrequency - 2. * highestAccurateFrequency) / samplingFrequency); + return new TwoPassSincResampler(clockFrequency, samplingFrequency, highestAccurateFrequency, intermediateFrequency); + } + + bool input(int sample) override + { + return s1->input(sample) && s2->input(s1->output()); + } + + int output() const override + { + return s2->output(); + } + + void reset() override + { + s1->reset(); + s2->reset(); + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h b/src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h new file mode 100644 index 00000000..2bc80cde --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h @@ -0,0 +1,88 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 ZEROORDER_RESAMPLER_H +#define ZEROORDER_RESAMPLER_H + +#include "Resampler.h" + +#include "../sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Return sample with linear interpolation. + * + * @author Antti Lankila + */ +class ZeroOrderResampler final : public Resampler +{ + +private: + /// Last sample + int cachedSample; + + /// Number of cycles per sample + const int cyclesPerSample; + + int sampleOffset; + + /// Calculated sample + int outputValue; + +public: + ZeroOrderResampler(double clockFrequency, double samplingFrequency) : + cachedSample(0), + cyclesPerSample(static_cast(clockFrequency / samplingFrequency * 1024.)), + sampleOffset(0), + outputValue(0) {} + + bool input(int sample) override + { + bool ready = false; + + if (sampleOffset < 1024) + { + outputValue = cachedSample + (sampleOffset * (sample - cachedSample) >> 10); + ready = true; + sampleOffset += cyclesPerSample; + } + + sampleOffset -= 1024; + + cachedSample = sample; + + return ready; + } + + int output() const override { return outputValue; } + + void reset() override + { + sampleOffset = 0; + cachedSample = 0; + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/test.cpp b/src/engine/platform/sound/c64_fp/resample/test.cpp new file mode 100644 index 00000000..5e5026ff --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/test.cpp @@ -0,0 +1,87 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2012-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 +#include +#include +#include +#include +#include + +#include "siddefs-fp.h" + +#include "Resampler.h" +#include "TwoPassSincResampler.h" + +/** + * Simple sin waveform in, power output measurement function. + * It would be far better to use FFT. + */ +int main(int argc, const char* argv[]) +{ + const double RATE = 985248.4; + const int RINGSIZE = 2048; + + std::auto_ptr r(reSIDfp::TwoPassSincResampler::create(RATE, 48000.0, 20000.0)); + + std::map results; + clock_t start = clock(); + + for (double freq = 1000.; freq < RATE / 2.; freq *= 1.01) + { + /* prefill resampler buffer */ + int k = 0; + double omega = 2 * M_PI * freq / RATE; + + for (int j = 0; j < RINGSIZE; j ++) + { + int signal = static_cast(32768.0 * sin(k++ * omega) * sqrt(2)); + r->input(signal); + } + + int n = 0; + float pwr = 0; + + /* Now, during measurement stage, put 100 cycles of waveform through filter. */ + for (int j = 0; j < 100000; j ++) + { + int signal = static_cast(32768.0 * sin(k++ * omega) * sqrt(2)); + + if (r->input(signal)) + { + float out = r->output(); + pwr += out * out; + n += 1; + } + } + + results.insert(std::make_pair(freq, 10 * log10(pwr / n))); + } + + clock_t end = clock(); + + for (std::map::iterator it = results.begin(); it != results.end(); ++it) + { + std::cout << std::fixed << std::setprecision(0) << std::setw(6) << (*it).first << " Hz " << (*it).second << " dB" << std::endl; + } + + std::cout << "Filtering time " << (end - start) * 1000. / CLOCKS_PER_SEC << " ms" << std::endl; +} diff --git a/src/engine/platform/sound/c64_fp/sidcxx11.h b/src/engine/platform/sound/c64_fp/sidcxx11.h new file mode 100644 index 00000000..18eadf4a --- /dev/null +++ b/src/engine/platform/sound/c64_fp/sidcxx11.h @@ -0,0 +1,29 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014-2015 Leandro Nini + * + * 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 SIDCXX11_H +#define SIDCXX11_H + +#define DEFAULT = default +#define DELETE = delete + +#define HAVE_CXX11 + +#endif diff --git a/src/engine/platform/sound/c64_fp/sidcxx14.h b/src/engine/platform/sound/c64_fp/sidcxx14.h new file mode 100644 index 00000000..5078a0b1 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/sidcxx14.h @@ -0,0 +1,29 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014-2015 Leandro Nini + * + * 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 SIDCXX14_H +#define SIDCXX14_H + +#include "sidcxx11.h" + +#define MAKE_UNIQUE(type, ...) std::make_unique(__VA_ARGS__) +#define HAVE_CXX14 + +#endif diff --git a/src/engine/platform/sound/c64_fp/siddefs-fp.h b/src/engine/platform/sound/c64_fp/siddefs-fp.h new file mode 100644 index 00000000..43900862 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/siddefs-fp.h @@ -0,0 +1,62 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 1999 Dag Lem +// +// 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 SIDDEFS_FP_H +#define SIDDEFS_FP_H + +// Compilation configuration. +#define RESID_BRANCH_HINTS 0 + +// Compiler specifics. +#define HAVE_BUILTIN_EXPECT 0 + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +// Branch prediction macros, lifted off the Linux kernel. +#if RESID_BRANCH_HINTS && HAVE_BUILTIN_EXPECT +# define likely(x) __builtin_expect(!!(x), 1) +# define unlikely(x) __builtin_expect(!!(x), 0) +#else +# define likely(x) (x) +# define unlikely(x) (x) +#endif + +namespace reSIDfp { + +typedef enum { MOS6581=1, MOS8580 } ChipModel; + +typedef enum { DECIMATE=1, RESAMPLE } SamplingMethod; +} + +extern "C" +{ +#ifndef __VERSION_CC__ +extern const char* residfp_version_string; +#else +const char* residfp_version_string = "furnace"; +#endif +} + +// Inlining on/off. +#define RESID_INLINING 1 +#define RESID_INLINE inline + +#endif // SIDDEFS_FP_H diff --git a/src/engine/platform/sound/c64_fp/version.cc b/src/engine/platform/sound/c64_fp/version.cc new file mode 100644 index 00000000..3ed8b449 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/version.cc @@ -0,0 +1,21 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2004 Dag Lem +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// --------------------------------------------------------------------------- + +#define __VERSION_CC__ +#include "siddefs-fp.h" diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 65baf4fd..a79948b9 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -133,6 +133,8 @@ const char* aboutLine[]={ "puNES (NES, MMC5 and FDS) by FHorse", "NSFPlay (NES and FDS) by Brad Smith and Brezza", "reSID by Dag Lem", + "reSIDfp by Dag Lem, Antti Lankila", + "and Leandro Nini", "Stella by Stella Team", "QSound emulator by superctr and Valley Bell", "VICE VIC-20 sound core by Rami Rasanen and viznut", diff --git a/src/gui/gui.h b/src/gui/gui.h index 48b8e12c..2cd85020 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1049,6 +1049,7 @@ class FurnaceGUI { int snCore; int nesCore; int fdsCore; + int c64Core; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1168,6 +1169,7 @@ class FurnaceGUI { snCore(0), nesCore(0), fdsCore(0), + c64Core(1), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1e9a1445..27720112 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -97,6 +97,11 @@ const char* nesCores[]={ "NSFplay" }; +const char* c64Cores[]={ + "reSID", + "reSIDfp" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -972,6 +977,10 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2); + ImGui::Text("SID core"); + ImGui::SameLine(); + ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2); + ImGui::Separator(); ImGui::Text("PC Speaker strategy"); @@ -2155,6 +2164,7 @@ void FurnaceGUI::syncSettings() { settings.snCore=e->getConfInt("snCore",0); settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); + settings.c64Core=e->getConfInt("c64Core",1); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.yrw801Path=e->getConfString("yrw801Path",""); settings.tg100Path=e->getConfString("tg100Path",""); @@ -2267,6 +2277,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.snCore,0,1); clampSetting(settings.nesCore,0,1); clampSetting(settings.fdsCore,0,1); + clampSetting(settings.c64Core,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -2402,6 +2413,7 @@ void FurnaceGUI::commitSettings() { e->setConf("snCore",settings.snCore); e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); + e->setConf("c64Core",settings.c64Core); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); e->setConf("yrw801Path",settings.yrw801Path); e->setConf("tg100Path",settings.tg100Path); diff --git a/src/main.cpp b/src/main.cpp index 39c9e6ca..a1856092 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -181,6 +181,7 @@ TAParamResult pVersion(String) { printf("- puNES by FHorse (GPLv2)\n"); printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n"); printf("- reSID by Dag Lem (GPLv2)\n"); + printf("- reSIDfp by Dag Lem, Antti Lankila and Leandro Nini (GPLv2)\n"); printf("- Stella by Stella Team (GPLv2)\n"); printf("- vgsound_emu (first version) by cam900 (BSD 3-clause)\n"); return TA_PARAM_QUIT; From d485af439db9f71daf699dae2601a88965151355 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 28 Aug 2022 15:36:12 -0500 Subject: [PATCH 07/93] fix macOS build --- src/engine/platform/sound/c64_fp/WaveformGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp b/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp index 16a9eb6b..e5e544e7 100644 --- a/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp +++ b/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp @@ -56,7 +56,7 @@ namespace reSIDfp const unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000; const unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400; // ~1s -const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000; +//const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000; // ~1s const unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000; const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; @@ -74,7 +74,7 @@ const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; const unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000; const unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000; // ~2.15s -const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000; +//const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000; // ~2.8s const unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000; const unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300; From 6041b8f14f366104a7f231086e81fb5025ecc266 Mon Sep 17 00:00:00 2001 From: Lunathir <18320914+lunathir@users.noreply.github.com> Date: Sun, 28 Aug 2022 13:54:05 -0700 Subject: [PATCH 08/93] Change references to some chips (#658) * Update sysDef.cpp * Update sysDef.cpp * Update sysDef.cpp * Update sysDef.cpp --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 1c63431f..78af262a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1370,7 +1370,7 @@ void DivEngine::registerSystems() { // to Grauw: feel free to change this to 24 during development of OPL4's PCM part. sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( - "Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false, + "Yamaha YMF278B (OPL4)", NULL, 0xae, 0, 42, true, true, 0, false, "like OPL3, but this time it also has a 24-channel version of MultiPCM.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, @@ -1379,7 +1379,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef( - "Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false, + "Yamaha YMF278B (OPL4) with drums", NULL, 0xaf, 0, 44, true, true, 0, false, "the OPL4 but with drums mode turned on.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick/FM 16", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, From 32050a211ff17ac57c705825a886ac145f08c3c7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 28 Aug 2022 15:54:21 -0500 Subject: [PATCH 09/93] GUI: update credits --- src/gui/about.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index a79948b9..39cb1192 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -102,6 +102,7 @@ const char* aboutLine[]={ "fd", "GENATARi", "host12prog", + "lunathir", "plane", "TheEssem", "", From c2a7bdd19460ed8d91f932e1ba0a06e495479227 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 29 Aug 2022 11:45:18 +0900 Subject: [PATCH 10/93] Match this to actual channel count --- src/engine/platform/su.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 13650345..730f8d89 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -581,7 +581,7 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned su=new SoundUnit(); setFlags(flags); reset(); - return 6; + return 8; } void DivPlatformSoundUnit::quit() { From 8d280fd9a32496bc53870a5142fbc7af714edc87 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 29 Aug 2022 03:26:49 -0500 Subject: [PATCH 11/93] C64: bind reSIDfp --- src/engine/dispatchContainer.cpp | 2 ++ src/engine/platform/c64.cpp | 59 +++++++++++++++++++++++++------- src/engine/platform/c64.h | 8 +++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8d8db5c5..158bce4e 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -218,10 +218,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_C64_6581: dispatch=new DivPlatformC64; + ((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1); ((DivPlatformC64*)dispatch)->setChipModel(true); break; case DIV_SYSTEM_C64_8580: dispatch=new DivPlatformC64; + ((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1); ((DivPlatformC64*)dispatch)->setChipModel(false); break; case DIV_SYSTEM_YM2151: diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 9825f189..5d1eb53c 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -19,9 +19,10 @@ #include "c64.h" #include "../engine.h" +#include "sound/c64_fp/siddefs-fp.h" #include -#define rWrite(a,v) if (!skipRegisterWrites) {sid.write(a,v); regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {if (isFP) {sid_fp.write(a,v);} else {sid.write(a,v);}; regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 524288 @@ -63,15 +64,19 @@ const char** DivPlatformC64::getRegisterSheet() { } void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) { - int dcOff=sid.get_dc(0); + int dcOff=isFP?0:sid.get_dc(0); for (size_t i=start; i=8) { - writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; + if (isFP) { + sid_fp.clock(4,&bufL[i]); + } else { + sid.clock(); + bufL[i]=sid.output(); + if (++writeOscBuf>=8) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; + } } } } @@ -405,7 +410,11 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - sid.set_is_muted(ch,mute); + if (isFP) { + sid_fp.mute(ch,mute); + } else { + sid.set_is_muted(ch,mute); + } } void DivPlatformC64::forceIns() { @@ -462,13 +471,21 @@ bool DivPlatformC64::getWantPreNote() { return true; } +float DivPlatformC64::getPostAmp() { + return isFP?3.0f:1.0f; +} + void DivPlatformC64::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); chan[i].std.setEngine(parent); } - sid.reset(); + if (isFP) { + sid_fp.reset(); + } else { + sid.reset(); + } memset(regPool,0,32); rWrite(0x18,0x0f); @@ -490,12 +507,24 @@ void DivPlatformC64::poke(std::vector& wlist) { void DivPlatformC64::setChipModel(bool is6581) { if (is6581) { - sid.set_chip_model(MOS6581); + if (isFP) { + sid_fp.setChipModel(reSIDfp::MOS6581); + } else { + sid.set_chip_model(MOS6581); + } } else { - sid.set_chip_model(MOS8580); + if (isFP) { + sid_fp.setChipModel(reSIDfp::MOS8580); + } else { + sid.set_chip_model(MOS8580); + } } } +void DivPlatformC64::setFP(bool fp) { + isFP=fp; +} + void DivPlatformC64::setFlags(unsigned int flags) { switch (flags&0xf) { case 0x0: // NTSC C64 @@ -513,6 +542,10 @@ void DivPlatformC64::setFlags(unsigned int flags) { for (int i=0; i<3; i++) { oscBuf[i]->rate=rate/16; } + if (isFP) { + rate/=4; + sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); + } } int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 79963597..7587730b 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include "sound/c64/sid.h" +#include "sound/c64_fp/SID.h" class DivPlatformC64: public DivDispatch { struct Channel { @@ -76,12 +77,17 @@ class DivPlatformC64: public DivDispatch { unsigned char filtControl, filtRes, vol; unsigned char writeOscBuf; int filtCut, resetTime; + bool isFP; SID sid; + reSIDfp::SID sid_fp; unsigned char regPool[32]; friend void putDispatchChan(void*,int,int); + void acquire_classic(short* bufL, short* bufR, size_t start, size_t len); + void acquire_fp(short* bufL, short* bufR, size_t start, size_t len); + void updateFilter(); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -98,6 +104,7 @@ class DivPlatformC64: public DivDispatch { void notifyInsChange(int ins); bool getDCOffRequired(); bool getWantPreNote(); + float getPostAmp(); DivMacroInt* getChanMacroInt(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); @@ -105,6 +112,7 @@ class DivPlatformC64: public DivDispatch { const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void setChipModel(bool is6581); + void setFP(bool fp); void quit(); ~DivPlatformC64(); }; From 4b4a2540646460ca0dd4777aff24344cc8233563 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 29 Aug 2022 03:54:55 -0500 Subject: [PATCH 12/93] C64: reSIDfp per-chan osc --- src/engine/platform/c64.cpp | 8 +++++++- src/engine/platform/sound/c64_fp/SID.h | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 5d1eb53c..ad111347 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -68,10 +68,16 @@ void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) for (size_t i=start; i=4) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5; + } } else { sid.clock(); bufL[i]=sid.output(); - if (++writeOscBuf>=8) { + if (++writeOscBuf>=16) { writeOscBuf=0; oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; diff --git a/src/engine/platform/sound/c64_fp/SID.h b/src/engine/platform/sound/c64_fp/SID.h index 05ad83c3..85b6a4e4 100644 --- a/src/engine/platform/sound/c64_fp/SID.h +++ b/src/engine/platform/sound/c64_fp/SID.h @@ -132,7 +132,7 @@ private: * * @return the output sample */ - int output() const; + int output(); /** * Calculate the numebr of cycles according to current parameters @@ -146,6 +146,8 @@ public: SID(); ~SID(); + int lastChanOut[3]; + /** * Set chip model. * @@ -312,12 +314,16 @@ void SID::ageBusValue(unsigned int n) } RESID_INLINE -int SID::output() const +int SID::output() { const int v1 = voice[0]->output(voice[2]->wave()); const int v2 = voice[1]->output(voice[0]->wave()); const int v3 = voice[2]->output(voice[1]->wave()); + lastChanOut[0]=v1; + lastChanOut[1]=v2; + lastChanOut[2]=v3; + return externalFilter->clock(filter->clock(v1, v2, v3)); } From 3af287cf9de2bba751244954b303b324c6009b53 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 29 Aug 2022 04:15:53 -0500 Subject: [PATCH 13/93] AY: fix per-chan osc in Sunsoft 5B mode --- src/engine/platform/ay.cpp | 44 ++++++++++++++++++---------- src/engine/platform/sound/ay8910.cpp | 2 ++ src/engine/platform/sound/ay8910.h | 2 ++ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index f45c51ea..23f20c2a 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -69,6 +69,14 @@ const char* regCheatSheetAY8914[]={ NULL }; +// taken from ay8910.cpp +const int sunsoftVolTable[32]={ + 103350, 73770, 52657, 37586, 32125, 27458, 24269, 21451, + 18447, 15864, 14009, 12371, 10506, 8922, 7787, 6796, + 5689, 4763, 4095, 3521, 2909, 2403, 2043, 1737, + 1397, 1123, 925, 762, 578, 438, 332, 251 +}; + const char** DivPlatformAY8910::getRegisterSheet() { return intellivision?regCheatSheetAY8914:regCheatSheetAY; } @@ -93,27 +101,33 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l regPool[w.addr&0x0f]=w.val; writes.pop(); } - ay->sound_stream_update(ayBuf,len); if (sunsoft) { for (size_t i=0; isound_stream_update(ayBuf,1); + bufL[i+start]=ayBuf[0][0]; bufR[i+start]=bufL[i+start]; - } - } else if (stereo) { - for (size_t i=0; idata[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3; + oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3; + oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3; } } else { - for (size_t i=0; isound_stream_update(ayBuf,len); + if (stereo) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + for (int ch=0; ch<3; ch++) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + } } } } diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 2c11b6e3..c7be503e 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -924,6 +924,7 @@ float ay8910_device::mix_3D() indx |= tone_mask | (m_vol_enabled[chan] ? tone_volume(tone) << (chan*5) : 0); } } + lastIndx=indx; return m_vol3d_table[indx]; } @@ -1359,6 +1360,7 @@ unsigned char ay8910_device::ay8910_read_ym() void ay8910_device::device_reset() { + lastIndx=0; ay8910_reset_ym(); } diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index 314383f5..6f4c6f31 100644 --- a/src/engine/platform/sound/ay8910.h +++ b/src/engine/platform/sound/ay8910.h @@ -146,6 +146,8 @@ public: double m_Kn[32]; }; + int lastIndx; + // internal interface for PSG component of YM device // FIXME: these should be private, but vector06 accesses them directly From af1b684c08f87488b7971a6c5418516e701f8257 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 30 Aug 2022 15:47:12 -0500 Subject: [PATCH 14/93] NES: #655 --- src/engine/platform/nes.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index ab466f1b..50fcd5ca 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -115,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_ bufL[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=nes->S1.output<<11; - oscBuf[1]->data[oscBuf[1]->needle++]=nes->S2.output<<11; - oscBuf[2]->data[oscBuf[2]->needle++]=nes->TR.output<<11; - oscBuf[3]->data[oscBuf[3]->needle++]=nes->NS.output<<11; - oscBuf[4]->data[oscBuf[4]->needle++]=nes->DMC.output<<8; + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11); + oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11); + oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8); } } } From 95db5624144503a7a8f38e38b7560868f2da8359 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 30 Aug 2022 16:28:05 -0500 Subject: [PATCH 15/93] fix Android build --- src/engine/config.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/engine/config.cpp b/src/engine/config.cpp index f404c0a4..862cd4c0 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -36,12 +36,20 @@ #define CONFIG_FILE "/furnace.cfg" #endif +#ifdef IS_MOBILE +#ifdef HAVE_SDL2 +#include +#else +#error "Furnace mobile requires SDL2!" +#endif +#endif + void DivEngine::initConfDir() { #ifdef _WIN32 // maybe move this function in here instead? configPath=getWinConfigPath(); -#elif defined (IS_MOBILE) - configPath=SDL_GetPrefPath(); +#elif defined(IS_MOBILE) + configPath=SDL_GetPrefPath("tildearrow","furnace"); #else #ifdef __HAIKU__ char userSettingsDir[PATH_MAX]; From 7f01eaec9d0b08966eaab2c3179d282c3c933f4b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 30 Aug 2022 23:59:38 -0500 Subject: [PATCH 16/93] convert note/macro rel to note off on .dmf save --- src/engine/fileOps.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 0e0ceb8b..66ca9d46 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -4342,14 +4342,25 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } } + bool relWarning=false; + for (int i=0; iwriteC(curPat[i].effectCols); for (int j=0; jordersLen; j++) { DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); for (int k=0; kpatLen; k++) { - w->writeS(pat->data[k][0]); // note - w->writeS(pat->data[k][1]); // octave + if ((pat->data[k][0]==101 || pat->data[k][0]==102) && pat->data[k][1]==0) { + w->writeS(100); + w->writeS(0); + if (!relWarning) { + relWarning=true; + addWarning("note/macro release will be converted to note off!"); + } + } else { + w->writeS(pat->data[k][0]); // note + w->writeS(pat->data[k][1]); // octave + } w->writeS(pat->data[k][3]); // volume #ifdef TA_BIG_ENDIAN for (int l=0; l Date: Wed, 31 Aug 2022 00:51:08 -0500 Subject: [PATCH 17/93] WonderSwan: fix possible hang when seeking --- src/engine/platform/swan.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 7f1e10a0..7ac26629 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -59,7 +59,8 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len DivSample* s=parent->getSample(dacSample); if (s->samples<=0) { dacSample=-1; - continue; + dacPeriod=0; + break; } rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); if (s->isLoopable() && dacPos>=s->getEndPosition()) { From f3c3d82e4d8321684346c4f8ab92c591ec2305fe Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 31 Aug 2022 02:52:35 -0500 Subject: [PATCH 18/93] fix hang when swapping chips fixes #660 --- src/engine/engine.cpp | 47 ++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1230cb59..bee07751 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1140,7 +1140,7 @@ void DivEngine::swapChannels(int src, int dest) { String prevChanName=curSubSong->chanName[src]; String prevChanShortName=curSubSong->chanShortName[src]; bool prevChanShow=curSubSong->chanShow[src]; - bool prevChanCollapse=curSubSong->chanCollapse[src]; + unsigned char prevChanCollapse=curSubSong->chanCollapse[src]; curSubSong->chanName[src]=curSubSong->chanName[dest]; curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; @@ -1445,25 +1445,44 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) { } } + // swap channels logV("swap list:"); for (int i=0; i %d",unswappedChannels[i],swappedChannels[i]); } - // swap channels - bool allComplete=false; - while (!allComplete) { - logD("doing swap..."); - allComplete=true; - for (int i=0; i %d -> %d",unswappedChannels[i],unswappedChannels[swappedChannels[i]]); - unswappedChannels[i]^=unswappedChannels[swappedChannels[i]]; - unswappedChannels[swappedChannels[i]]^=unswappedChannels[i]; - unswappedChannels[i]^=unswappedChannels[swappedChannels[i]]; + for (size_t i=0; iorders; + DivPattern* prevPat[DIV_MAX_CHANS][256]; + unsigned char prevEffectCols[DIV_MAX_CHANS]; + String prevChanName[DIV_MAX_CHANS]; + String prevChanShortName[DIV_MAX_CHANS]; + bool prevChanShow[DIV_MAX_CHANS]; + unsigned char prevChanCollapse[DIV_MAX_CHANS]; + + for (int j=0; jpat[j].data[k]; } + prevEffectCols[j]=song.subsong[i]->pat[j].effectCols; + + prevChanName[j]=song.subsong[i]->chanName[j]; + prevChanShortName[j]=song.subsong[i]->chanShortName[j]; + prevChanShow[j]=song.subsong[i]->chanShow[j]; + prevChanCollapse[j]=song.subsong[i]->chanCollapse[j]; + } + + for (int j=0; jorders.ord[j][k]=prevOrders.ord[swappedChannels[j]][k]; + song.subsong[i]->pat[j].data[k]=prevPat[swappedChannels[j]][k]; + } + + song.subsong[i]->pat[j].effectCols=prevEffectCols[swappedChannels[j]]; + song.subsong[i]->chanName[j]=prevChanName[swappedChannels[j]]; + song.subsong[i]->chanShortName[j]=prevChanShortName[swappedChannels[j]]; + song.subsong[i]->chanShow[j]=prevChanShow[swappedChannels[j]]; + song.subsong[i]->chanCollapse[j]=prevChanCollapse[swappedChannels[j]]; } } } From 820b23ecdbb9f902f279a233a8efc1291e6722ea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 31 Aug 2022 03:05:06 -0500 Subject: [PATCH 19/93] fix macros sometimes being out of sync when seekin --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index bee07751..4a8b7f5b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1666,7 +1666,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { } } int oldOrder=curOrder; - while (playing && curRow1)) { if (nextTick(preserveDrift)) { skipping=false; return; From 16eba9ec96fbbd29daaaa15dfddd64284bb61e8e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 31 Aug 2022 03:11:02 -0500 Subject: [PATCH 20/93] fix macro delay not working on first note --- src/engine/macroInt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index ad761ba5..5208dc54 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -50,7 +50,7 @@ struct DivMacroStruct { finished(false), will(false), linger(false), - began(false), + began(true), mode(0) {} }; From a33e6e3989f6cd12f3ca9b3ab39d42bd33f8c4d7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 31 Aug 2022 03:34:13 -0500 Subject: [PATCH 21/93] GUI: add "create wave from selection" option in sample editor --- src/gui/doAction.cpp | 26 ++++++++++++++++++++++++++ src/gui/gui.h | 1 + src/gui/guiConst.cpp | 1 + src/gui/sampleEdit.cpp | 3 +++ 4 files changed, 31 insertions(+) diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index e0f0ca30..91916669 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1295,6 +1295,32 @@ void FurnaceGUI::doAction(int what) { MARK_MODIFIED; break; } + case GUI_ACTION_SAMPLE_CREATE_WAVE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + SAMPLE_OP_BEGIN; + if (end-start<1) { + showError("select at least one sample!"); + } else if (end-start>256) { + showError("maximum size is 256 samples!"); + } else { + curWave=e->addWave(); + if (curWave==-1) { + showError("too many wavetables!"); + } else { + DivWavetable* wave=e->song.wave[curWave]; + wave->min=0; + wave->max=255; + wave->len=end-start; + for (unsigned int i=start; idata[i-start]=(sample->data8[i]&0xff)^0x80; + } + nextWindow=GUI_WINDOW_WAVE_EDIT; + MARK_MODIFIED; + } + } + break; + } case GUI_ACTION_ORDERS_UP: if (curOrder>0) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 2cd85020..9938e3cc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -525,6 +525,7 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_ZOOM_AUTO, GUI_ACTION_SAMPLE_MAKE_INS, GUI_ACTION_SAMPLE_SET_LOOP, + GUI_ACTION_SAMPLE_CREATE_WAVE, GUI_ACTION_SAMPLE_MAX, GUI_ACTION_ORDERS_MIN, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index d1ef835c..bf075f2f 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -641,6 +641,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_ZOOM_AUTO", "Toggle auto-zoom", FURKMOD_CMD|SDLK_0), D("SAMPLE_MAKE_INS", "Create instrument from sample", 0), D("SAMPLE_SET_LOOP", "Set loop to selection", FURKMOD_CMD|SDLK_l), + D("SAMPLE_CREATE_WAVE", "Create wavetable from selection", FURKMOD_CMD|SDLK_w), D("SAMPLE_MAX", "", NOT_AN_ACTION), D("ORDERS_MIN", "---Orders", NOT_AN_ACTION), diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index e9b5cfb3..0ce1500e 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -1293,6 +1293,9 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::MenuItem("set loop to selection",BIND_FOR(GUI_ACTION_SAMPLE_SET_LOOP))) { doAction(GUI_ACTION_SAMPLE_SET_LOOP); } + if (ImGui::MenuItem("create wavetable from selection",BIND_FOR(GUI_ACTION_SAMPLE_CREATE_WAVE))) { + doAction(GUI_ACTION_SAMPLE_CREATE_WAVE); + } ImGui::EndPopup(); } From b213368d034b5aa9bbf92248b570d4c7dadd1949 Mon Sep 17 00:00:00 2001 From: brickblock369 <59150779+brickblock369@users.noreply.github.com> Date: Wed, 31 Aug 2022 14:05:47 -0700 Subject: [PATCH 22/93] New instrument proposal --- instruments/FM/guitar/Banjo (Muted).opni | Bin 0 -> 83 bytes instruments/FM/guitar/Banjo.opni | Bin 0 -> 83 bytes instruments/FM/guitar/Koto.opni | Bin 0 -> 83 bytes instruments/FM/guitar/Oud.opni | Bin 0 -> 83 bytes .../FM/guitar/Shamisen (Regular Pluck).opni | Bin 0 -> 83 bytes instruments/FM/guitar/Shamisen (Tsugaru Slap).opni | Bin 0 -> 83 bytes instruments/FM/guitar/Sitar.opni | Bin 0 -> 83 bytes instruments/FM/guitar/Tamboura (Bass Sitar).opni | Bin 0 -> 83 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 instruments/FM/guitar/Banjo (Muted).opni create mode 100644 instruments/FM/guitar/Banjo.opni create mode 100644 instruments/FM/guitar/Koto.opni create mode 100644 instruments/FM/guitar/Oud.opni create mode 100644 instruments/FM/guitar/Shamisen (Regular Pluck).opni create mode 100644 instruments/FM/guitar/Shamisen (Tsugaru Slap).opni create mode 100644 instruments/FM/guitar/Sitar.opni create mode 100644 instruments/FM/guitar/Tamboura (Bass Sitar).opni diff --git a/instruments/FM/guitar/Banjo (Muted).opni b/instruments/FM/guitar/Banjo (Muted).opni new file mode 100644 index 0000000000000000000000000000000000000000..1bf6e88fb38b7fc55ccf6a4f4cb209884906fa1a GIT binary patch literal 83 zcmWId5AZY6_4G3eVPIll@GUJ#O;K=4%*)DWzyXvP0_El9h1eMaRpc2M*csfkQa`|Y E0fsCIE&u=k literal 0 HcmV?d00001 diff --git a/instruments/FM/guitar/Shamisen (Regular Pluck).opni b/instruments/FM/guitar/Shamisen (Regular Pluck).opni new file mode 100644 index 0000000000000000000000000000000000000000..c25acca1e7b9529b36934355efc0f36d3a44d26a GIT binary patch literal 83 zcmWId5AZY6_4G3eVPIll2+l~%%`8sMQ}D|#%1z8+zyg>UQibEiI3pR1)Z{rCeli$o S$a67#XE0)r=jY1=s|Ns}I1Qfw literal 0 HcmV?d00001 diff --git a/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni b/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni new file mode 100644 index 0000000000000000000000000000000000000000..ed9c9497dc5669a04da0d1ead6d9eb352ad2a91e GIT binary patch literal 83 zcmWId5AZY6_4G3eVPIll2+l~%%`8sMQwYvUEMUM2m>5!d;>9>C8I08AIT?O37-`6J QF??q*Vvy(O%LMBH0CS%V-v9sr literal 0 HcmV?d00001 diff --git a/instruments/FM/guitar/Sitar.opni b/instruments/FM/guitar/Sitar.opni new file mode 100644 index 0000000000000000000000000000000000000000..3427fe2a7054ddeafd2eabe054e6806468dbd017 GIT binary patch literal 83 zcmWId5AZY6_4G3eVPIll2+k}?EMmY5m>8s#1bBF>7zE|y**OFmQdLB_d7>B$ndG^7 HG{HInm~02N literal 0 HcmV?d00001 diff --git a/instruments/FM/guitar/Tamboura (Bass Sitar).opni b/instruments/FM/guitar/Tamboura (Bass Sitar).opni new file mode 100644 index 0000000000000000000000000000000000000000..e6514b73c2bbbc08c049d16baf4959512c49ec8a GIT binary patch literal 83 zcmWId5AZY6_4G3eVPIll2uaLM$}cTSWWWuW8MM{7*f=a0L=?rDSTq Date: Wed, 31 Aug 2022 14:11:10 -0700 Subject: [PATCH 23/93] One last instrument proposal --- instruments/FM/percussion/Kalimba.fui | Bin 0 -> 1893 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 instruments/FM/percussion/Kalimba.fui diff --git a/instruments/FM/percussion/Kalimba.fui b/instruments/FM/percussion/Kalimba.fui new file mode 100644 index 0000000000000000000000000000000000000000..128deca3fd33b48c86e15a69a110d7b9bfeb23c2 GIT binary patch literal 1893 zcmdOOD=o@POioqE%quP_($g(qU|>)HVi@rB3l6bmXJ9B`U}W%4%*o76N@QRLN-}{I z$TKi8^D8od1z;MGX?b2YPJVaX^5SAlT>MHvL)egYA=vV4jGX*HyFeCV0sIV14nSuy zFmOOIkU|3TNNj@4|3DS^Fg63w)q|WuEc{UO<8(n@R*oE%r;m#+NPAT<2 zA@gDKqxPVdJFsv8mcpz7tOpnw{sRGn0*(?8p&D=DMNA+g8AS#YZ$ZQlEW<$ Date: Thu, 1 Sep 2022 10:49:49 -0700 Subject: [PATCH 24/93] Update brickblock369 Harpsichord I realized this one was outdated, I'm uploading a much better version --- instruments/FM/keys/brickblock369 Harpsichord.dmp | Bin 51 -> 51 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/instruments/FM/keys/brickblock369 Harpsichord.dmp b/instruments/FM/keys/brickblock369 Harpsichord.dmp index a2aba1f2d7b3b99cfa540071d1c49d4e34639f15..0f5ea587d338e30c838daa10db612ee6662f2bb8 100644 GIT binary patch literal 51 xcmd;PVq^dUZeDo_Hg*OEW(Ec}d3jbwpdgURDkd+($OdGxF)%X7bMkY7MFAzI0cHRI literal 51 xcmd;PVq{=vU|`acXW?gNU;rXcd3jbwMlh3AR9=RW1;}J$U}TWz Date: Sun, 4 Sep 2022 02:26:29 -0500 Subject: [PATCH 25/93] GUI: work around .dmf/.dmp saving issue --- src/gui/dataList.cpp | 32 ++++++++- src/gui/doAction.cpp | 9 +++ src/gui/gui.cpp | 158 ++++++++++++++++++++++++++----------------- src/gui/gui.h | 7 ++ src/gui/guiConst.cpp | 3 + 5 files changed, 144 insertions(+), 65 deletions(-) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 10dda6aa..0ef1f183 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -121,16 +121,30 @@ void FurnaceGUI::drawInsList() { if (ImGui::MenuItem("instrument")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::MenuItem("instrument (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } if (ImGui::MenuItem("wavetable")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (ImGui::MenuItem("wavetable (.dmw)")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("wavetable (raw)")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } if (ImGui::MenuItem("sample")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); } ImGui::EndPopup(); } - } - if (!settings.unifiedDataView) { + } else { + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmp...")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { doAction(GUI_ACTION_INS_LIST_MOVE_UP); @@ -359,6 +373,9 @@ void FurnaceGUI::drawInsList() { if (ImGui::MenuItem("save")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::MenuItem("save (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } if (ImGui::MenuItem("delete")) { doAction(GUI_ACTION_INS_LIST_DELETE); } @@ -430,6 +447,17 @@ void FurnaceGUI::drawWaveList() { if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (!settings.unifiedDataView) { + if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } + } ImGui::SameLine(); if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 91916669..92e12a74 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -599,6 +599,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); break; + case GUI_ACTION_INS_LIST_SAVE_DMP: + if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE_DMP); + break; case GUI_ACTION_INS_LIST_MOVE_UP: if (e->moveInsUp(curIns)) { curIns--; @@ -666,6 +669,12 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); break; + case GUI_ACTION_WAVE_LIST_SAVE_DMW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE_DMW); + break; + case GUI_ACTION_WAVE_LIST_SAVE_RAW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE_RAW); + break; case GUI_ACTION_WAVE_LIST_MOVE_UP: if (e->moveWaveUp(curWave)) { curWave--; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 86c063e9..b0ccd3a3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1258,9 +1258,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( "Save File", - {"Furnace song", "*.fur", - "DefleMask 1.1.3 module", "*.dmf"}, - "Furnace song{.fur},DefleMask 1.1.3 module{.dmf}", + {"Furnace song", "*.fur"}, + "Furnace song{.fur}", + workingDirSong, + dpiScale + ); + break; + case GUI_FILE_SAVE_DMF: + if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save File", + {"DefleMask 1.1.3 module", "*.dmf"}, + "DefleMask 1.1.3 module{.dmf}", workingDirSong, dpiScale ); @@ -1335,9 +1344,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSave( "Save Instrument", - {"Furnace instrument", "*.fui", - "DefleMask preset", "*.dmp"}, - "Furnace instrument{.fui},DefleMask preset{.dmp}", + {"Furnace instrument", "*.fui"}, + "Furnace instrument{.fui}", + workingDirIns, + dpiScale + ); + break; + case GUI_FILE_INS_SAVE_DMP: + if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Instrument", + {"DefleMask preset", "*.dmp"}, + "DefleMask preset{.dmp}", workingDirIns, dpiScale ); @@ -1358,10 +1376,28 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSave( "Save Wavetable", - {"Furnace wavetable", ".fuw", - "DefleMask wavetable", ".dmw", - "raw data", ".raw"}, - "Furnace wavetable{.fuw},DefleMask wavetable{.dmw},raw data{.raw}", + {"Furnace wavetable", ".fuw"}, + "Furnace wavetable{.fuw}", + workingDirWave, + dpiScale + ); + break; + case GUI_FILE_WAVE_SAVE_DMW: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"DefleMask wavetable", ".dmw"}, + "DefleMask wavetable{.dmw}", + workingDirWave, + dpiScale + ); + break; + case GUI_FILE_WAVE_SAVE_RAW: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"raw data", ".raw"}, + "raw data{.raw}", workingDirWave, dpiScale ); @@ -2944,6 +2980,9 @@ 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+)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + openFileDialog(GUI_FILE_SAVE_DMF); + } if (ImGui::MenuItem("save as .dmf (1.0/legacy)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } @@ -3335,17 +3374,21 @@ bool FurnaceGUI::loop() { switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_SAVE: + case GUI_FILE_SAVE_DMF: case GUI_FILE_SAVE_DMF_LEGACY: workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: + case GUI_FILE_INS_SAVE_DMP: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_OPEN_REPLACE: case GUI_FILE_WAVE_SAVE: + case GUI_FILE_WAVE_SAVE_DMW: + case GUI_FILE_WAVE_SAVE_RAW: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: @@ -3409,9 +3452,10 @@ bool FurnaceGUI::loop() { } if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { - // we can't tell whether the user chose .dmf or .fur in the system file picker - const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf"; - checkExtensionDual(".fur",".dmf",fallbackExt); + checkExtension(".fur"); + } + if (curFileDialog==GUI_FILE_SAVE_DMF) { + checkExtension(".dmf"); } if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) { checkExtension(".dmf"); @@ -3423,21 +3467,19 @@ bool FurnaceGUI::loop() { checkExtension(".wav"); } if (curFileDialog==GUI_FILE_INS_SAVE) { - // we can't tell whether the user chose .fui or .dmp in the system file picker - const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace instrument")?".fui":".dmp"; - checkExtensionDual(".fui",".dmp",fallbackExt); + checkExtension(".fui"); + } + if (curFileDialog==GUI_FILE_INS_SAVE_DMP) { + checkExtension(".dmp"); } if (curFileDialog==GUI_FILE_WAVE_SAVE) { - // same thing here - const char* fallbackExt=".fuw"; - if (!settings.sysFileDialog) { - if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="raw data") { - fallbackExt=".raw"; - } else if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="DefleMask wavetable") { - fallbackExt=".dmw"; - } - } - checkExtensionTriple(".fuw",".dmw",".raw",fallbackExt); + checkExtension(".fuw"); + } + if (curFileDialog==GUI_FILE_WAVE_SAVE_DMW) { + checkExtension(".dmw"); + } + if (curFileDialog==GUI_FILE_WAVE_SAVE_RAW) { + checkExtension(".raw"); } if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); @@ -3465,21 +3507,10 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_SAVE: { logD("saving: %s",copyOfName.c_str()); - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } bool saveWasSuccessful=true; - if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) { - if (save(copyOfName,0)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - saveWasSuccessful=false; - } - } else { - if (save(copyOfName,26)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - saveWasSuccessful=false; - } + if (save(copyOfName,0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + saveWasSuccessful=false; } if (saveWasSuccessful && postWarnAction!=GUI_WARN_GENERIC) { switch (postWarnAction) { @@ -3512,6 +3543,12 @@ bool FurnaceGUI::loop() { } break; } + case GUI_FILE_SAVE_DMF: + logD("saving: %s",copyOfName.c_str()); + if (save(copyOfName,26)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } + break; case GUI_FILE_SAVE_DMF_LEGACY: logD("saving: %s",copyOfName.c_str()); if (save(copyOfName,24)>0) { @@ -3520,34 +3557,29 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_INS_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } - if ((lowerCase.size()<4 || lowerCase.rfind(".dmp")!=lowerCase.size()-4)) { - e->song.ins[curIns]->save(copyOfName.c_str()); - } else { - if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { - showError("error while saving instrument! make sure your instrument is compatible."); - } + e->song.ins[curIns]->save(copyOfName.c_str()); + } + break; + case GUI_FILE_INS_SAVE_DMP: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { + showError("error while saving instrument! make sure your instrument is compatible."); } } break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } - if (lowerCase.size()<4) { - e->song.wave[curWave]->save(copyOfName.c_str()); - } else if (lowerCase.rfind(".dmw")==lowerCase.size()-4) { - e->song.wave[curWave]->saveDMW(copyOfName.c_str()); - } else if (lowerCase.rfind(".raw")==lowerCase.size()-4) { - e->song.wave[curWave]->saveRaw(copyOfName.c_str()); - } else { - e->song.wave[curWave]->save(copyOfName.c_str()); - } + e->song.wave[curWave]->save(copyOfName.c_str()); + } + break; + case GUI_FILE_WAVE_SAVE_DMW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->song.wave[curWave]->saveDMW(copyOfName.c_str()); + } + break; + case GUI_FILE_WAVE_SAVE_RAW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->song.wave[curWave]->saveRaw(copyOfName.c_str()); } break; case GUI_FILE_SAMPLE_OPEN: { diff --git a/src/gui/gui.h b/src/gui/gui.h index 9938e3cc..5950ab94 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -263,13 +263,17 @@ enum FurnaceGUIWindows { enum FurnaceGUIFileDialogs { GUI_FILE_OPEN, GUI_FILE_SAVE, + GUI_FILE_SAVE_DMF, GUI_FILE_SAVE_DMF_LEGACY, GUI_FILE_INS_OPEN, GUI_FILE_INS_OPEN_REPLACE, GUI_FILE_INS_SAVE, + GUI_FILE_INS_SAVE_DMP, GUI_FILE_WAVE_OPEN, GUI_FILE_WAVE_OPEN_REPLACE, GUI_FILE_WAVE_SAVE, + GUI_FILE_WAVE_SAVE_DMW, + GUI_FILE_WAVE_SAVE_RAW, GUI_FILE_SAMPLE_OPEN, GUI_FILE_SAMPLE_OPEN_RAW, GUI_FILE_SAMPLE_OPEN_REPLACE, @@ -455,6 +459,7 @@ enum FurnaceGUIActions { GUI_ACTION_INS_LIST_OPEN, GUI_ACTION_INS_LIST_OPEN_REPLACE, GUI_ACTION_INS_LIST_SAVE, + GUI_ACTION_INS_LIST_SAVE_DMP, GUI_ACTION_INS_LIST_MOVE_UP, GUI_ACTION_INS_LIST_MOVE_DOWN, GUI_ACTION_INS_LIST_DELETE, @@ -469,6 +474,8 @@ enum FurnaceGUIActions { GUI_ACTION_WAVE_LIST_OPEN, GUI_ACTION_WAVE_LIST_OPEN_REPLACE, GUI_ACTION_WAVE_LIST_SAVE, + GUI_ACTION_WAVE_LIST_SAVE_DMW, + GUI_ACTION_WAVE_LIST_SAVE_RAW, GUI_ACTION_WAVE_LIST_MOVE_UP, GUI_ACTION_WAVE_LIST_MOVE_DOWN, GUI_ACTION_WAVE_LIST_DELETE, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index bf075f2f..e79de410 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -571,6 +571,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("INS_LIST_OPEN", "Open", 0), D("INS_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("INS_LIST_SAVE", "Save", 0), + D("INS_LIST_SAVE_DMP", "Save (.dmp)", 0), D("INS_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("INS_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("INS_LIST_DELETE", "Delete", 0), @@ -585,6 +586,8 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WAVE_LIST_OPEN", "Open", 0), D("WAVE_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("WAVE_LIST_SAVE", "Save", 0), + D("WAVE_LIST_SAVE_DMW", "Save (.dmw)", 0), + D("WAVE_LIST_SAVE_RAW", "Save (raw)", 0), D("WAVE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("WAVE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("WAVE_LIST_DELETE", "Delete", 0), From 90a0db06f8cb82c121f01b0037d4749f23a62fdc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 4 Sep 2022 02:27:00 -0500 Subject: [PATCH 26/93] GUI: one tiny fix --- 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 b0ccd3a3..f0e50142 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2980,10 +2980,10 @@ 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+)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) { openFileDialog(GUI_FILE_SAVE_DMF); } - if (ImGui::MenuItem("save as .dmf (1.0/legacy)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); From f7bca46a4a6969b9dca80ca20d8c827050436436 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 4 Sep 2022 02:37:43 -0500 Subject: [PATCH 27/93] GUI: implement save as format in editors as well --- src/gui/insEdit.cpp | 6 ++++++ src/gui/waveEdit.cpp | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c89652a3..662448df 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1608,6 +1608,12 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::Button(ICON_FA_FLOPPY_O "##IESave")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmp...")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + ImGui::EndPopup(); + } ImGui::TableNextColumn(); ImGui::Text("Type"); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 7e453ef5..01f67862 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -138,6 +138,15 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::RadioButton("Steps",waveEditStyle==0)) { From 9435ab12b0cb9c9d4d8443216561b45067db82ae Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 4 Sep 2022 04:00:56 -0500 Subject: [PATCH 28/93] GUI: wave generator, part 4 prepare FM --- src/gui/waveEdit.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 01f67862..9fea53c0 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -104,6 +104,9 @@ void FurnaceGUI::doGenerateWave() { } } +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); + void FurnaceGUI::drawWaveEdit() { if (nextWindow==GUI_WINDOW_WAVE_EDIT) { waveEditOpen=true; @@ -321,7 +324,109 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::BeginTabItem("FM")) { waveGenFM=true; - ImGui::Text("FM stuff here"); + if (ImGui::BeginTable("WGFMProps",4)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("Op").x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Op"); + ImGui::TableNextColumn(); + ImGui::Text("Level"); + ImGui::TableNextColumn(); + ImGui::Text("Mult"); + ImGui::TableNextColumn(); + ImGui::Text("FB"); + + for (int i=0; i<4; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) { + doGenerateWave(); + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderInt("##WGMULT",&waveGenMult[i],0,15)) { + doGenerateWave(); + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderFloat("##WGFB",&waveGenFB[i],0.0f,7.0f)) { + doGenerateWave(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + + CENTER_TEXT("Connection Diagram"); + ImGui::Text("Connection Diagram"); + + if (ImGui::BeginTable("WGFMCon",5)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(">>"); + ImGui::TableNextColumn(); + ImGui::Text("2"); + ImGui::TableNextColumn(); + ImGui::Text("3"); + ImGui::TableNextColumn(); + ImGui::Text("4"); + ImGui::TableNextColumn(); + ImGui::Text("Out"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("1"); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con12",&waveGenFMCon1[0]); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con13",&waveGenFMCon1[1]); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con14",&waveGenFMCon1[2]); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con1O",&waveGenFMCon1[3]); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("2"); + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con23",&waveGenFMCon2[0]); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con24",&waveGenFMCon2[1]); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con2O",&waveGenFMCon2[2]); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("3"); + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con34",&waveGenFMCon3[0]); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Con3O",&waveGenFMCon3[1]); + + ImGui::EndTable(); + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Mangle")) { From 17f7647ae7a3064d15f4b377f79b722219c4e438 Mon Sep 17 00:00:00 2001 From: brickblock369 <59150779+brickblock369@users.noreply.github.com> Date: Sun, 4 Sep 2022 13:47:27 -0700 Subject: [PATCH 29/93] Update soundunit.md Clarified that the Sound Unit is able to have 64 KB depending on the configuration. --- papers/doc/7-systems/soundunit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/soundunit.md b/papers/doc/7-systems/soundunit.md index 8c3863b9..34cc16b4 100644 --- a/papers/doc/7-systems/soundunit.md +++ b/papers/doc/7-systems/soundunit.md @@ -1,5 +1,5 @@ # tildearrow Sound Unit -This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB of sample data. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. +This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB or 64KB of sample data, depending on the configuration used. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. # effects From dc58043835ccbd0eec0009cfe099473384ad8151 Mon Sep 17 00:00:00 2001 From: brickblock369 <59150779+brickblock369@users.noreply.github.com> Date: Sun, 4 Sep 2022 13:50:14 -0700 Subject: [PATCH 30/93] Fixing the .dmp files as SMS/NES instead of FM These were oddly loaded as FM instruments while having data intended for SN7 - I've fixed them to load the correct type. --- instruments/other/(SMS) 2-Arp Chord High.dmp | Bin 30 -> 30 bytes instruments/other/(SMS) 2-Arp Major Low.dmp | Bin 30 -> 30 bytes instruments/other/(SMS) 2-Arp Minor Low.dmp | Bin 30 -> 30 bytes instruments/other/(SMS) 3-Arp High.dmp | Bin 26 -> 26 bytes instruments/other/(SMS) 3-Arp Major.dmp | Bin 26 -> 26 bytes instruments/other/(SMS) 3-Arp Minor.dmp | Bin 26 -> 26 bytes instruments/other/(SMS) Arp Snare.dmp | Bin 102 -> 102 bytes instruments/other/(SMS) Attack.dmp | Bin 25 -> 25 bytes instruments/other/(SMS) Buzz Noise.dmp | Bin 142 -> 142 bytes instruments/other/(SMS) Crash.dmp | Bin 306 -> 306 bytes instruments/other/(SMS) Decay Noise.dmp | Bin 158 -> 158 bytes instruments/other/(SMS) Decay.dmp | Bin 21 -> 21 bytes instruments/other/(SMS) Down Slider.dmp | Bin 162 -> 162 bytes instruments/other/(SMS) Hi-Hat & Note.dmp | Bin 39 -> 39 bytes instruments/other/(SMS) Hi-Hat Closed.dmp | Bin 29 -> 29 bytes instruments/other/(SMS) Hi-Hat Open.dmp | Bin 181 -> 181 bytes instruments/other/(SMS) Kick Noise.dmp | Bin 39 -> 39 bytes instruments/other/(SMS) Multi Slider.dmp | Bin 162 -> 162 bytes instruments/other/(SMS) Obvious Crash.dmp | Bin 383 -> 383 bytes instruments/other/(SMS) Record Scratch Down.dmp | Bin 94 -> 94 bytes instruments/other/(SMS) Record Scratch Up.dmp | Bin 94 -> 94 bytes instruments/other/(SMS) Retrig.dmp | Bin 49 -> 49 bytes instruments/other/(SMS) Ride.dmp | Bin 367 -> 367 bytes instruments/other/(SMS) Snare.dmp | Bin 86 -> 86 bytes instruments/other/(SMS) Splash.dmp | Bin 517 -> 517 bytes instruments/other/(SMS) Thump & Note.dmp | Bin 46 -> 46 bytes .../other/(SMS) Tim Follin 6-Arp Fast Major.dmp | Bin 38 -> 38 bytes .../other/(SMS) Tim Follin 6-Arp Fast Minor.dmp | Bin 38 -> 38 bytes .../other/(SMS) Tim Follin 6-Arp Slow Major.dmp | Bin 50 -> 50 bytes .../other/(SMS) Tim Follin 6-Arp Slow Minor.dmp | Bin 50 -> 50 bytes instruments/other/(SMS) Tim Follin Lead.dmp | Bin 26 -> 26 bytes instruments/other/(SMS) Tom A.dmp | Bin 62 -> 62 bytes instruments/other/(SMS) Tom B.dmp | Bin 98 -> 98 bytes instruments/other/(SMS) Up Slider.dmp | Bin 162 -> 162 bytes instruments/other/(SMS) Variable.dmp | Bin 13 -> 13 bytes instruments/other/(SMS) Whistle.dmp | Bin 22 -> 22 bytes 36 files changed, 0 insertions(+), 0 deletions(-) diff --git a/instruments/other/(SMS) 2-Arp Chord High.dmp b/instruments/other/(SMS) 2-Arp Chord High.dmp index 9340173621f7e3df55d0eedca9a936478cbbc9ab..845babb2e886dbbfe7dfe065fbe14b25f9c9638f 100644 GIT binary patch literal 30 acmd;PW?!Gj diff --git a/instruments/other/(SMS) 2-Arp Major Low.dmp b/instruments/other/(SMS) 2-Arp Major Low.dmp index 532c24565f97ed0028b999de53319f0a88748956..f65623f0de3f25178ae4232b3f3ff89c4cdb0965 100644 GIT binary patch literal 30 acmd;PW?Xl literal 30 acmd;PVqoNDU|{&q!UH5hSOAKF6axSpJpoq$ diff --git a/instruments/other/(SMS) 2-Arp Minor Low.dmp b/instruments/other/(SMS) 2-Arp Minor Low.dmp index 608e5483246b3a0aea887d7dd45f030bf9ffbfb8..5345dd459587d2fef6326c6a167011c3a45adfcf 100644 GIT binary patch literal 30 acmd;PW?-IP6axSpDFIag diff --git a/instruments/other/(SMS) 3-Arp High.dmp b/instruments/other/(SMS) 3-Arp High.dmp index a5ebf9759321339d37f27d0e167aa9747d651f19..d0fab8e515c705a17b95645caef854bd3a3b9ccd 100644 GIT binary patch literal 26 acmd;PW?z?5i2;iM literal 26 acmd;PVqoNDU|{&q%mXAPfLH~HK>z?3zyW3e diff --git a/instruments/other/(SMS) 3-Arp Major.dmp b/instruments/other/(SMS) 3-Arp Major.dmp index 3d284983e9765b93b28bdf09b9704b62a00c8fbd..d9f9e3a7d2184580ed42d601256280b53703374b 100644 GIT binary patch literal 26 acmd;PW?z?4p#gFL literal 26 acmd;PVqoNDU|{&q%mX9^fLIuaK>z?2*a1xd diff --git a/instruments/other/(SMS) 3-Arp Minor.dmp b/instruments/other/(SMS) 3-Arp Minor.dmp index a28275fb7769b55277b10f51232e1d7aa1a56c47..f36e80e97089ac614fbc2710e26ca4b3c4b2122f 100644 GIT binary patch literal 26 acmd;PW?z?4l>u=8 literal 26 acmd;PVqoNDU|{&q%mXC(fmj%bK>z?2%mGXQ diff --git a/instruments/other/(SMS) Arp Snare.dmp b/instruments/other/(SMS) Arp Snare.dmp index fa5734d4a977e64978bc583dff2cb0e9396be153..7135954297d16b18671a43bccb37fab9390d02a8 100644 GIT binary patch delta 25 acmYdGW8!9>$P^;L$H2fK2E-CT3<3Zu7y=Lg delta 25 acmYdGW8!9-$P^;L#K6G71;ji+3<3ZsQvvG$ diff --git a/instruments/other/(SMS) Attack.dmp b/instruments/other/(SMS) Attack.dmp index 0be930625decb07ad10b738d1b2fdc8325ef4fc8..ac5d24fffcce3acf2eab543fbc65b013f4c2c202 100644 GIT binary patch literal 25 acmd;PW?*4sU|`?|Vtydz1>*le%m4rhH~~KZ literal 25 acmd;PVqjroU|`?|Vtydz1>*le%m4rhAOSuA diff --git a/instruments/other/(SMS) Buzz Noise.dmp b/instruments/other/(SMS) Buzz Noise.dmp index 8b7dfd97dc9c98035b493acf4aa29439ef4a1f6d..d1bc39212d813f895bcd72ad77c2b0c58718a049 100644 GIT binary patch literal 142 Zcmd;PW>DZ~U|<+X{GWk=5oA9D0|2sy10DbX literal 142 acmd;PVo=~`U|<+X{GWk=ks0U)1_l7Ju>%nR diff --git a/instruments/other/(SMS) Crash.dmp b/instruments/other/(SMS) Crash.dmp index f9855bde354ec99718132f753200ae33c698db0e..6f05ff8020edf2094429d2a4f899eac176254203 100644 GIT binary patch delta 43 ncmdnQw26s@o0-9NqJY6f1)hlxw3?}CW=)XjeW_awqbSVpQ2qC;r&)!&ZVZn(R2PW(ovB8(EKidS` Z4z?|98`##ctzcWiwt#I8+YGkx_W^Km0}lWI diff --git a/instruments/other/(SMS) Hi-Hat & Note.dmp b/instruments/other/(SMS) Hi-Hat & Note.dmp index 3f92dc68e44f20be7565640baac423094d562952..cff02382ff3d2e1785f600462a0f97fecba79eb6 100644 GIT binary patch literal 39 icmd;PW?<%KU|?VaVtye0&y)ouLHIud6G(s$%me^ELc0R delta 10 RcmdnWxRsHKn`t7`W&jU~0>S_Q diff --git a/instruments/other/(SMS) Kick Noise.dmp b/instruments/other/(SMS) Kick Noise.dmp index 8f2d3cbf9a4afe32ff867b34df5da03d0dbe6934..b76fb88614620a661ef038fdb7d14724a36086e3 100644 GIT binary patch literal 39 hcmd;PW?<%LU|;}YJ|O|?3uN{tG`G?atIxT|Nox{NW<`d21aHe_df#wLs|;d diff --git a/instruments/other/(SMS) Record Scratch Down.dmp b/instruments/other/(SMS) Record Scratch Down.dmp index 8e28c41800ba5555b93ec5fbb67d7dc3aeb8f951..b3f4b07fa5820b2dd9592d89e33dad421e0326e1 100644 GIT binary patch literal 94 wcma*dxe)*$48*{jA;SSFn7{ubjY=R`MY1F-l76r$@}&nG3ljq!j)scB1BGM*)c^nh literal 94 wcma*fxe)*$34)c^nh literal 94 vcma*exe)*$3$mGGp&%nSS0mT1-7yu$90?GgY delta 21 ZcmWFwW8!9-$mGGp%)r3F1H}J<7yuz70fLI2IHGo(ih_!(j1OPA~0qg(( literal 38 hcmd;PVqoNDU|{&q#sefJfLH*CWq?>1h~fLI!c)qz+Zh_!(j1OPAm0qOt% literal 38 hcmd;PVqoNDU|{&q#sefJfS4bMrGZ!&h~0igf@ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp index 9536150c65ce3590c7022fec86b4d24542fc3524..11eeafb4a40accc6ca5787c962c0f447befa57f7 100644 GIT binary patch literal 50 jcmd;PW?pjZP)%R{j?kOlz&N#g=S literal 50 jcmd;PVqoNDU|{&q$pa)oSOSOzpjZY-3q!FykOlz&Kav5} diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp index 166a1cb3af789baa0b4d0323a05c5fe50ba2d2dd..3cbc3f380aec5cd7d58b1b968e46bd3f6538ddb4 100644 GIT binary patch literal 50 jcmd;PW?$Yj97!@$5G0mT1-7yumD0)GGi delta 21 XcmcDsW8!9-$Yj9700bc7KM(@|8`lD6 diff --git a/instruments/other/(SMS) Tom B.dmp b/instruments/other/(SMS) Tom B.dmp index 664021f6d7446ed81feb56bdf873736f4a45146c..e06cccb81e19cbb5be00da1fa17108d1f1d286a9 100644 GIT binary patch delta 57 vcmYdFV&Z0=$Yfxk#K6Fy2*e6NEDywTKr9QyGC(X1#8N;k3B(dW{2zz`huj1P delta 57 tcmWN=$qfJ?2mnFC*9*@&sELlOSa*_%(4<%0kBDq8P7Zb&8!HPlA3u3F1Azbl diff --git a/instruments/other/(SMS) Up Slider.dmp b/instruments/other/(SMS) Up Slider.dmp index 765f156e824e56bd8cd569c561fd8f997d44551b..b57cfffeaccb7921c8c89b6586aadec4e56bdca5 100644 GIT binary patch literal 162 zcmWm5Hx7VM5X8`rD9JfzasMNNUA!q)-XsoD7DD*mkxfya*xs;x5a9>1ngpl- literal 162 zcmWm8*A0L$3;wuCU!;yTkT??Fripwl{1a_>UhaIRfhd diff --git a/instruments/other/(SMS) Variable.dmp b/instruments/other/(SMS) Variable.dmp index 7ed3209f5ce5f3d2b5bd4fd152b26b8870ee7b00..16a5c0f76e0c3289edb3a860f03eebb5ddbaa915 100644 GIT binary patch literal 13 Scmd;PW?9 diff --git a/instruments/other/(SMS) Whistle.dmp b/instruments/other/(SMS) Whistle.dmp index 0fd9127c031a0eb3668ed18eaf435e5c45d32936..1ed679bb83c880eb1f94f6bcbdfd2993af8ecc12 100644 GIT binary patch literal 22 Xcmd;PW? Date: Sun, 4 Sep 2022 13:56:18 -0700 Subject: [PATCH 31/93] Adding additional PSG instruments --- instruments/other/2A03 Noise Hi-Hat Closed.fui | Bin 0 -> 1937 bytes instruments/other/2A03 Noise Hi-Hat Open.fui | Bin 0 -> 2019 bytes instruments/other/2A03 Noise Kick.fui | Bin 0 -> 1928 bytes instruments/other/2A03 Noise Snare.fui | Bin 0 -> 2013 bytes instruments/other/2A03 Triangle Kick+Bass.fui | Bin 0 -> 1926 bytes instruments/other/2A03 Triangle Kick.fui | Bin 0 -> 1951 bytes instruments/other/2A03 Triangle Snare+Bass.fui | Bin 0 -> 1931 bytes instruments/other/2A03 Triangle Snare.fui | Bin 0 -> 1944 bytes instruments/other/AY kick.fui | Bin 0 -> 1799 bytes instruments/other/AY snare.fui | Bin 0 -> 2056 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 instruments/other/2A03 Noise Hi-Hat Closed.fui create mode 100644 instruments/other/2A03 Noise Hi-Hat Open.fui create mode 100644 instruments/other/2A03 Noise Kick.fui create mode 100644 instruments/other/2A03 Noise Snare.fui create mode 100644 instruments/other/2A03 Triangle Kick+Bass.fui create mode 100644 instruments/other/2A03 Triangle Kick.fui create mode 100644 instruments/other/2A03 Triangle Snare+Bass.fui create mode 100644 instruments/other/2A03 Triangle Snare.fui create mode 100644 instruments/other/AY kick.fui create mode 100644 instruments/other/AY snare.fui diff --git a/instruments/other/2A03 Noise Hi-Hat Closed.fui b/instruments/other/2A03 Noise Hi-Hat Closed.fui new file mode 100644 index 0000000000000000000000000000000000000000..a24dff830d17e67e06962ae403631f671aca32af GIT binary patch literal 1937 zcmeHHU24KW5dOv_Mys&k1+vdViboI%MPEeF3rNjFK(rWr_G;eD&L&y1E=wXUC|C!+ z%>UmKCjLC`x2vy2rrYeWe~-TbNQS2AY_Uv3K-p)zG)v?pjVG&v{MhWWWDWR0O9e-r zz!O6t(3uvGe4^f{#{WjX7+6V$8g;2F@=k`a5(Qdg5bK>1%nXVQIUmt84VHXio!5fm zE~s$9XfxDAs;#0PFz%NqEY;VpLprGnxmmJnHR(m+-!*KiZTtU2FMH=_*PDLKzwX~u zU;AHuuJ4h47rxlRJUA0BOptY_tw_1oLskQtQtcrUd{`A~$;%flLkK4kh9C#Iia5fY I13fD42lAWZrT_o{ literal 0 HcmV?d00001 diff --git a/instruments/other/2A03 Noise Hi-Hat Open.fui b/instruments/other/2A03 Noise Hi-Hat Open.fui new file mode 100644 index 0000000000000000000000000000000000000000..26889b0ad7e2b9d8bb07187c9a9ccac4b9cedf44 GIT binary patch literal 2019 zcmeHI%}T>S5dNk~Ow+0hJ$lKWg@}*PlhjKU+7~D#hg@Q5>)8kM;+xf(Bumz1NkqYd z?ZDUh+4(0-vxnDhxmxGCD0lnqUA6(B8HOI`i{*O^h<*N2>~cLTve{~{pEh|3gup;t z(_s{-TOcxV4o^d(@qNeph(a}Sf-E&nC{a}<%h-wvgDHshVFP9srIw5vRs!4L$OqPW zEC}~O+*N~X=7OsRm(0hE5hGzpJG?wxFl03X$u0F=$aDrWiV)|Ta?T31rTuk#BNNoR zd|PsJ_33rupB3!O?Y^HvEqCVsuJ?VIzn#A?zj;5``Fi8bUid#CdX7E_2PWvQb6JP{ l)gs#lZK?ARDL&SMP710)8yCWxier$6d`UcR-iD6l_X*Jl;^Y7T literal 0 HcmV?d00001 diff --git a/instruments/other/2A03 Noise Kick.fui b/instruments/other/2A03 Noise Kick.fui new file mode 100644 index 0000000000000000000000000000000000000000..7cf815e1a9eb2931b503248ae3ed8b909770a790 GIT binary patch literal 1928 zcmeHHOA5j;5Pj1RwTcA1K)V(y9>J9$f(Uv5MY<@0O4YSj^JY4!24e`VC@81{ubI!A znM^2?^!NE{zDVP2wcX{NWCI{(C>e|<(^&vW|7e|U(|DLImVgI1k`vW@SF{0NB^YPq z5jDCc@j7{;Wh7~46jCUPPMY(k3OGFw+qEnB&?M6I;)<^BF!U4KJQn0>gQN>a$dEkQ zRtnp_2BXO+9NNdMLn=w*JgS9idzhNr4SAx2tXFG6|vB)7y1cE$s*`1l4S;EY&pSyN>-&Cu1{nTB~9syLu%FTRn_nHH`KYv)Qn`+T6y9P*skvf&* zEHM{AW+S|{Ohu+wk@Jb9=FAIn%(9Te^fx)1d(~i6Luik8aB-w`bez*j+zn6P(2k+* zhMbZUa!jUVLJFcK@Tq!^xayNAe5xQ?i=y2r+J>US_?kIu=C1n<=i}EXHpub$tm@|) zvbTA^cHvNN|NJl9<@fyC?ViNoN)m7j7IY}TcN9b70ssI2 literal 0 HcmV?d00001 diff --git a/instruments/other/2A03 Triangle Kick+Bass.fui b/instruments/other/2A03 Triangle Kick+Bass.fui new file mode 100644 index 0000000000000000000000000000000000000000..9beaea86258dc329a57a0fbfeba950520da00370 GIT binary patch literal 1926 zcmdOOD=o@POioqE%quP_($g(qU|>)HVi@rB3l526XJ7z{hZJQd=B4MPDtKomXDetc zI3*SrGcd3)Fff5s%X6?Y^J_6Mu)?&W(ef;ejNArzYxh5ia;z2#2_(rfUcJj zg$uBlW({CHz{v0) z2pANY5#l&l^a@Dq*5fUR`1u(aKo~Dz2745$gpr}1pPik75evX(2o_~n9W+qt0KZt{ AWB>pF literal 0 HcmV?d00001 diff --git a/instruments/other/2A03 Triangle Kick.fui b/instruments/other/2A03 Triangle Kick.fui new file mode 100644 index 0000000000000000000000000000000000000000..f226301058a8ca894539476d70fc0ae1f89aef2b GIT binary patch literal 1951 zcmdOOD=o@POioqE%quP_($g(qU|>)HVi@rB3l1q~XJ7z{hZJQd=B4MPDtKomXEQLc zFfcHIl*x0jGV^OOFtEb(pwaRyjEvj{c;vYlS@?y}G$C>2IT<bF!@n?P|GM-xB$y<)&SN6 zj12#QfI)#7A&!GZuYkmEJ>G(dA6VFeFkZk6_9#>dBSSquJ39j-7J$tVEXuGtXrR;q E0Al9ing9R* literal 0 HcmV?d00001 diff --git a/instruments/other/2A03 Triangle Snare+Bass.fui b/instruments/other/2A03 Triangle Snare+Bass.fui new file mode 100644 index 0000000000000000000000000000000000000000..e91831c8a49de1ffd4332efc1a9feee9edc53a99 GIT binary patch literal 1931 zcmdOOD=o@POioqE%quP_($g(qU|>)HVi@rB3l2$UXJ7z{hZJQd=B4MPDg@^x7Nsg^ zD>x+<7c(%hFfcHIl*@ClGV^OOFtEb(qS5j!jEvj{c;vYlS@?y}G$C>2IT<Et@=o1G$Al3n5psyX&ffyu?4$#eE z1i6t+fNmBcKExXGEF@$$y8KA63*FDdolhv4QtE#~=ELMi?LjS%VBrERt62kB4=^(P z2Lc8KW`sBn7QF%zyY+YrB7S}b1`x&zn86-}Dq&=(=VxbUV8jBj8G=O_RtF7~Isl16 B)HVi@rB3l1q@XJ7z{hZJQd=B4MPDg@^x7Ns&U zurM$%fz-)!url*&F)*;gG@;S*ER2lY26*JT7+Ls*&@>@&W5Xad{8%tZ9Oy#_Js{QrVqj=EXaF%t92uaS z$p~^cnE>4^LVSodCZdhdZB8GNsi2gv^J@kJ^J;Ho?LLSa7ojupVG! z_zwgO3d{&`94vYTBzEiZ7DW8OLKcMa0%ovBp-LDT>iOB(85pquY=&S_hSfm>r49hQ C(Bjzu literal 0 HcmV?d00001 diff --git a/instruments/other/AY kick.fui b/instruments/other/AY kick.fui new file mode 100644 index 0000000000000000000000000000000000000000..111f1da7eef5c11230b2bdd186367113a5042685 GIT binary patch literal 1799 zcmdOOD=o@POioqE%quP_($mdiU|>)HVi@rB3l6!@#=wxpz{ZeNl$o5Jl#`#FZER+# z;25cp4dgR0urM$%ft1U0url*&F)*;g^rF%7ER2lY26*JT7+Ls*&@>@&(x{!h(<>K0gxX1+qbA zasn~POi&Oo12IS)2xtQ_NDT-J1F;AYivzJF5X%BFy8S>Q6tLni#68G@$nF8L89@M9 z0SRn$vk37aMw4e7A+yotM}l4Geje_8LP?WS{}VDFCO>KqY6!r>1y~2L2CyDr1Q&V? g3ZSA4KR`8xbRK@w3CSWCM96HgK7L?94Z?T<00z7EGE?*P;W0bzFNsJ2W239_&1*=sq&7{nd9s+^-6xCF zvkP^3tH$Xhn-Td$EzXZSfhUiM0`sxsV_%5gNzHy&_;T0z$O_{~L{e9>URFy>2?HD1+k}dd6umO6Y3nIX}=$x*>EkT Date: Mon, 5 Sep 2022 05:48:20 -0500 Subject: [PATCH 32/93] GUI: wave generator, part 5 completely untested FM generation --- src/gui/waveEdit.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 9fea53c0..2767911a 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -32,6 +32,25 @@ const char* waveGenBaseShapes[4]={ "Pulse" }; +const float multFactors[16]={ + M_PI, + 2*M_PI, + 4*M_PI, + 6*M_PI, + 8*M_PI, + 10*M_PI, + 12*M_PI, + 14*M_PI, + 16*M_PI, + 18*M_PI, + 20*M_PI, + 22*M_PI, + 24*M_PI, + 26*M_PI, + 28*M_PI, + 30*M_PI, +}; + void FurnaceGUI::doGenerateWave() { float finalResult[256]; if (curWave<0 || curWave>=(int)e->song.wave.size()) return; @@ -42,7 +61,18 @@ void FurnaceGUI::doGenerateWave() { if (wave->len<2) return; if (waveGenFM) { + for (int i=0; ilen; i++) { + float pos=(float)i/(float)wave->len; + float s0=sin(pos*multFactors[waveGenMult[0]])*waveGenTL[0]; + float s1=sin((pos+(waveGenFMCon1[0]?s0:0.0f))*multFactors[waveGenMult[1]])*waveGenTL[1]; + float s2=sin((pos+(waveGenFMCon1[1]?s0:0.0f)+(waveGenFMCon2[0]?s1:0.0f))*multFactors[waveGenMult[2]])*waveGenTL[2]; + float s3=sin((pos+(waveGenFMCon1[2]?s0:0.0f)+(waveGenFMCon2[1]?s1:0.0f)+(waveGenFMCon3[0]?s2:0.0f))*multFactors[waveGenMult[3]])*waveGenTL[3]; + if (waveGenFMCon1[3]) finalResult[i]+=s0; + if (waveGenFMCon2[2]) finalResult[i]+=s1; + if (waveGenFMCon3[1]) finalResult[i]+=s2; + finalResult[i]+=s3; + } } else { switch (waveGenBaseShape) { case 0: // sine From a59ed84322577e8c25e5751c2a93f4f17f196c4e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 6 Sep 2022 14:28:57 -0500 Subject: [PATCH 33/93] GUI: fix wave gen FM con checkboxes doing nothing --- src/gui/waveEdit.cpp | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 2767911a..9842d808 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -423,13 +423,21 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); ImGui::Text("1"); ImGui::TableNextColumn(); - ImGui::Checkbox("##Con12",&waveGenFMCon1[0]); + if (ImGui::Checkbox("##Con12",&waveGenFMCon1[0])) { + doGenerateWave(); + } ImGui::TableNextColumn(); - ImGui::Checkbox("##Con13",&waveGenFMCon1[1]); + if (ImGui::Checkbox("##Con13",&waveGenFMCon1[1])) { + doGenerateWave(); + } ImGui::TableNextColumn(); - ImGui::Checkbox("##Con14",&waveGenFMCon1[2]); + if (ImGui::Checkbox("##Con14",&waveGenFMCon1[2])) { + doGenerateWave(); + } ImGui::TableNextColumn(); - ImGui::Checkbox("##Con1O",&waveGenFMCon1[3]); + if (ImGui::Checkbox("##Con1O",&waveGenFMCon1[3])) { + doGenerateWave(); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -437,11 +445,17 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); // blank ImGui::TableNextColumn(); - ImGui::Checkbox("##Con23",&waveGenFMCon2[0]); + if (ImGui::Checkbox("##Con23",&waveGenFMCon2[0])) { + doGenerateWave(); + } ImGui::TableNextColumn(); - ImGui::Checkbox("##Con24",&waveGenFMCon2[1]); + if (ImGui::Checkbox("##Con24",&waveGenFMCon2[1])) { + doGenerateWave(); + } ImGui::TableNextColumn(); - ImGui::Checkbox("##Con2O",&waveGenFMCon2[2]); + if (ImGui::Checkbox("##Con2O",&waveGenFMCon2[2])) { + doGenerateWave(); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -451,9 +465,13 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); // blank ImGui::TableNextColumn(); - ImGui::Checkbox("##Con34",&waveGenFMCon3[0]); + if (ImGui::Checkbox("##Con34",&waveGenFMCon3[0])) { + doGenerateWave(); + } ImGui::TableNextColumn(); - ImGui::Checkbox("##Con3O",&waveGenFMCon3[1]); + if (ImGui::Checkbox("##Con3O",&waveGenFMCon3[1])) { + doGenerateWave(); + } ImGui::EndTable(); } From 56ba47408cc5a6682524531c0f860df40b2cd4b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 01:37:16 -0500 Subject: [PATCH 34/93] GUI: initialize variables --- src/gui/gui.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6dc86420..1911ceb1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -4870,6 +4871,13 @@ FurnaceGUI::FurnaceGUI(): fileDialog(NULL), scrW(1280), scrH(800), + scrConfW(1280), + scrConfH(800), + scrX(SDL_WINDOWPOS_CENTERED), + scrY(SDL_WINDOWPOS_CENTERED), + scrConfX(SDL_WINDOWPOS_CENTERED), + scrConfY(SDL_WINDOWPOS_CENTERED), + scrMax(false), dpiScale(1), aboutScroll(0), aboutSin(0), From 21baf2e27255b99b8e68fc3b8ced41663600766d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 01:45:05 -0500 Subject: [PATCH 35/93] GUI: typo fixing --- src/gui/settings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ed0e66f9..53ce0cde 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -515,11 +515,11 @@ void FurnaceGUI::drawSettings() { } bool saveWindowPosB=settings.saveWindowPos; - if (ImGui::Checkbox("Remember window location",&saveWindowPosB)) { + if (ImGui::Checkbox("Remember window position",&saveWindowPosB)) { settings.saveWindowPos=saveWindowPosB; } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("remembers where window was last located on start-up. When disabled, window will start in a defaul location."); + ImGui::SetTooltip("remembers the window's last position on startup."); } bool blankInsB=settings.blankIns; From 4ad324bfedd4e08a65f613debe91f95e80f233c1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 01:49:36 -0500 Subject: [PATCH 36/93] screw you clangd extension --- src/gui/gui.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1911ceb1..fb197e31 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include +// I hate you clangd extension! +// how about you DON'T insert random headers before this freaking important +// define!!!!!! #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" From 7de165fd88057588ad807bb2f9cd78f7591c9888 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 03:01:22 -0500 Subject: [PATCH 37/93] N163: fix channel count change glitch, part 1 --- src/engine/platform/n163.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 7da2e0a1..bc985b42 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -642,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) { for (int i=0; i<8; i++) { oscBuf[i]->rate=rate/(initChanMax+1); } + + // needed to make sure changing channel count won't trigger glitches + reset(); } int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { From 773b63b8102f174292dae1f6963f0e8535e6a862 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 13:16:57 -0500 Subject: [PATCH 38/93] GUI: don't save layout periodically --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fb197e31..9bd20333 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2382,7 +2382,7 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) { if (mobileUI) { ImGui::GetIO().IniFilename=NULL; } else { - ImGui::GetIO().IniFilename=finalLayoutPath; + ImGui::GetIO().IniFilename=NULL; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); } } From ae6e956f06d328ec06e3f4885e305a814d95fb4f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 17:04:38 -0500 Subject: [PATCH 39/93] GUI: some mobile view work --- src/gui/editControls.cpp | 4 ++++ src/gui/gui.cpp | 32 ++++++++++++++++++-------------- src/gui/gui.h | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 912bd1c0..a2014a30 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -23,12 +23,16 @@ void FurnaceGUI::drawMobileControls() { if (ImGui::Begin("Mobile Controls",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { + ImGui::SetWindowPos(ImVec2(0.0f,0.0f)); + ImGui::SetWindowSize(portrait?ImVec2(scrW*dpiScale,0.1*scrW*dpiScale):ImVec2(0.1*scrH*dpiScale,scrH*dpiScale)); float availX=ImGui::GetContentRegionAvail().x; ImVec2 buttonSize=ImVec2(availX,availX); if (ImGui::Button(ICON_FA_CHEVRON_RIGHT "##MobileMenu",buttonSize)) { } + ImGui::Text("I put here"); + ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9bd20333..c74f077f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2595,24 +2595,24 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { #define OOB_PIXELS_SAFETY 25 bool FurnaceGUI::detectOutOfBoundsWindow() { - int count = SDL_GetNumVideoDisplays(); - if(count < 1) { - logW("bounds check: error %s", SDL_GetError()); + int count=SDL_GetNumVideoDisplays(); + if (count<1) { + logW("bounds check: error %s",SDL_GetError()); return false; } SDL_Rect rect; - for(int i = 0;i < count;i ++) { - if(SDL_GetDisplayUsableBounds(i, &rect) != 0) { - logW("bounds check: error %s", SDL_GetError()); + for (int i=0; i= scrX; - bool ybound = (rect.y + OOB_PIXELS_SAFETY) <= (scrY + scrH) && (rect.y + rect.h - OOB_PIXELS_SAFETY) >= scrY; - logD("bounds check: display %d is at %dx%dx%dx%d: %s%s", i, rect.x + OOB_PIXELS_SAFETY, rect.y + OOB_PIXELS_SAFETY, rect.x + rect.w - OOB_PIXELS_SAFETY, rect.y + rect.h - OOB_PIXELS_SAFETY, xbound ? "x" : "", ybound ? "y" : ""); + bool xbound=((rect.x+OOB_PIXELS_SAFETY)<=(scrX+scrW)) && ((rect.x+rect.w-OOB_PIXELS_SAFETY)>=scrX); + bool ybound=((rect.y+OOB_PIXELS_SAFETY)<=(scrY+scrH)) && ((rect.y+rect.h-OOB_PIXELS_SAFETY)>=scrY); + logD("bounds check: display %d is at %dx%dx%dx%d: %s%s",i,rect.x+OOB_PIXELS_SAFETY,rect.y+OOB_PIXELS_SAFETY,rect.x+rect.w-OOB_PIXELS_SAFETY,rect.y+rect.h-OOB_PIXELS_SAFETY,xbound?"x":"",ybound?"y":""); - if(xbound && ybound) { + if (xbound && ybound) { return true; } } @@ -2746,6 +2746,7 @@ bool FurnaceGUI::loop() { scrW=ev.window.data1/dpiScale; scrH=ev.window.data2/dpiScale; #endif + portrait=(scrWgetConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED); scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); scrMax=e->getConfBool("lastWindowMax",false); + portrait=(scrWdisplaySize.w/dpiScale) scrW=(displaySize.w/dpiScale)-32; if (scrH>displaySize.h/dpiScale) scrH=(displaySize.h/dpiScale)-32; + portrait=(scrW sysSearchResults; std::vector newSongSearchResults; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints, portrait; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; bool displayPendingIns, pendingInsSingle, displayPendingRawSample; From 06845210925da94ae62096d942b140cc0492675e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 17:04:52 -0500 Subject: [PATCH 40/93] update Android ver num --- android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cb4b3c59..f332e6f5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 93 - versionName "0.6pre1" + versionCode 112 + versionName "dev112" externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static" From 2c18fe1051f7625109ab10d34c2703990d3e158f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 19:15:19 -0500 Subject: [PATCH 41/93] GUI: lots of mobile UI progress --- src/gui/editControls.cpp | 130 +++++++++++++++++++++++++++++++++++---- src/gui/gui.cpp | 10 ++- src/gui/gui.h | 4 +- src/gui/pattern.cpp | 13 +++- src/log.cpp | 4 ++ 5 files changed, 144 insertions(+), 17 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index a2014a30..5307163e 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -22,27 +22,54 @@ #include void FurnaceGUI::drawMobileControls() { + float timeScale=1.0f/(60.0f*ImGui::GetIO().DeltaTime); + if (mobileMenuOpen) { + if (mobileMenuPos<0.999f) { + WAKE_UP; + mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale; + } else { + mobileMenuPos=1.0f; + } + } else { + if (mobileMenuPos>0.001f) { + WAKE_UP; + mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale; + } else { + mobileMenuPos=0.0f; + } + } + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)-(0.16*scrW*dpiScale)):ImVec2(0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.16*scrW*dpiScale):ImVec2(0.16*scrH*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mobile Controls",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { - ImGui::SetWindowPos(ImVec2(0.0f,0.0f)); - ImGui::SetWindowSize(portrait?ImVec2(scrW*dpiScale,0.1*scrW*dpiScale):ImVec2(0.1*scrH*dpiScale,scrH*dpiScale)); - float availX=ImGui::GetContentRegionAvail().x; - ImVec2 buttonSize=ImVec2(availX,availX); + float avail=portrait?ImGui::GetContentRegionAvail().y:ImGui::GetContentRegionAvail().x; + ImVec2 buttonSize=ImVec2(avail,avail); - if (ImGui::Button(ICON_FA_CHEVRON_RIGHT "##MobileMenu",buttonSize)) { + const char* mobButtonName=ICON_FA_CHEVRON_RIGHT "##MobileMenu"; + if (portrait) mobButtonName=ICON_FA_CHEVRON_UP "##MobileMenu"; + if (mobileMenuOpen) { + if (portrait) { + mobButtonName=ICON_FA_CHEVRON_DOWN "##MobileMenu"; + } else { + mobButtonName=ICON_FA_CHEVRON_LEFT "##MobileMenu"; + } + } + if (ImGui::Button(mobButtonName,buttonSize)) { + mobileMenuOpen=!mobileMenuOpen; } - ImGui::Text("I put here"); - - ImGui::Separator(); + if (!portrait) ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } ImGui::PopStyleColor(); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne",buttonSize)) { e->stepOne(cursor.y); pendingStepUpdate=true; @@ -50,12 +77,14 @@ void FurnaceGUI::drawMobileControls() { bool repeatPattern=e->getRepeatPattern(); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } ImGui::PopStyleColor(); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } @@ -63,17 +92,94 @@ void FurnaceGUI::drawMobileControls() { bool metro=e->getMetronome(); ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } ImGui::PopStyleColor(); - - if (ImGui::Button("Get me out of here")) { - toggleMobileUI(false); - } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); + + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { + ImGui::Button("Pattern"); + ImGui::SameLine(); + ImGui::Button("Ins"); + ImGui::SameLine(); + ImGui::Button("Wave"); + ImGui::SameLine(); + ImGui::Button("Sample"); + + ImGui::Text("Data list goes here..."); + + if (ImGui::Button("New")) { + mobileMenuOpen=false; + //doAction(GUI_ACTION_NEW); + if (modified) { + showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); + } else { + displayNew=true; + } + } + ImGui::SameLine(); + if (ImGui::Button("Open")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Save")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE); + } + ImGui::SameLine(); + if (ImGui::Button("Save as...")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE_AS); + } + + ImGui::Button("1.1+ .dmf"); + ImGui::SameLine(); + ImGui::Button("Legacy .dmf"); + ImGui::SameLine(); + ImGui::Button("Export Audio"); + ImGui::SameLine(); + ImGui::Button("Export VGM"); + + ImGui::Button("CmdStream"); + ImGui::SameLine(); + ImGui::Button("Panic"); + ImGui::SameLine(); + if (ImGui::Button("Settings")) { + mobileMenuOpen=false; + } + ImGui::SameLine(); + if (ImGui::Button("About")) { + mobileMenuOpen=false; + mobileMenuPos=0.0f; + aboutOpen=true; + } + + ImGui::Separator(); + + if (ImGui::Button("Osc")) { + oscOpen=!oscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("ChanOsc")) { + chanOscOpen=!chanOscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("RegView")) { + regViewOpen=!regViewOpen; + } + ImGui::SameLine(); + if (ImGui::Button("Stats")) { + statsOpen=!statsOpen; + } + } + ImGui::End(); } void FurnaceGUI::drawEditControls() { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c74f077f..a353308e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2581,7 +2581,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { if (point.id==0) { ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,false); - ImGui::GetIO().AddMousePosEvent(-FLT_MAX,-FLT_MAX); + //ImGui::GetIO().AddMousePosEvent(-FLT_MAX,-FLT_MAX); } break; } @@ -2747,6 +2747,7 @@ bool FurnaceGUI::loop() { scrH=ev.window.data2/dpiScale; #endif portrait=(scrWgetConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); scrMax=e->getConfBool("lastWindowMax",false); portrait=(scrWdisplaySize.w/dpiScale) scrW=(displaySize.w/dpiScale)-32; if (scrH>displaySize.h/dpiScale) scrH=(displaySize.h/dpiScale)-32; portrait=(scrW sysSearchResults; std::vector newSongSearchResults; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints, portrait; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; + bool portrait, mobileMenuOpen; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; bool displayPendingIns, pendingInsSingle, displayPendingRawSample; @@ -996,6 +997,7 @@ class FurnaceGUI { int drawHalt; int macroPointSize; int waveEditStyle; + float mobileMenuPos; const int* curSysSection; String pendingRawSample; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 254c53be..5081d6ef 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -372,10 +372,17 @@ void FurnaceGUI::drawPattern() { sel2.xFine^=sel1.xFine; } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale)); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Pattern",&patternOpen,globalWinFlags|(settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0))) { - //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); - patWindowPos=ImGui::GetWindowPos(); - patWindowSize=ImGui::GetWindowSize(); + if (!mobileUI) { + patWindowPos=ImGui::GetWindowPos(); + patWindowSize=ImGui::GetWindowSize(); + } //char id[32]; ImGui::PushFont(patFont); int ord=oldOrder; diff --git a/src/log.cpp b/src/log.cpp index 89602b42..ef2750a2 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -19,7 +19,11 @@ #include "ta-log.h" +#ifdef IS_MOBILE +int logLevel=LOGLEVEL_TRACE; +#else int logLevel=LOGLEVEL_INFO; +#endif std::atomic logPosition; From 0fd72c53efe13c0cc3fff3222e82e0169f3ff69a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 23:20:33 -0500 Subject: [PATCH 42/93] GUI: and more mobile UI progress --- src/gui/gui.cpp | 3 +++ src/gui/gui.h | 9 +++++++++ src/gui/pattern.cpp | 2 +- src/gui/piano.cpp | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a353308e..d9e8ffa0 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3349,6 +3349,8 @@ bool FurnaceGUI::loop() { if (mobileUI) { globalWinFlags=ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoBringToFrontOnFocus; //globalWinFlags=ImGuiWindowFlags_NoTitleBar; + // scene handling goes here! + pianoOpen=true; drawMobileControls(); drawPattern(); drawPiano(); @@ -4882,6 +4884,7 @@ FurnaceGUI::FurnaceGUI(): curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), postWarnAction(GUI_WARN_GENERIC), + mobScene(GUI_SCENE_PATTERN), fileDialog(NULL), scrW(1280), scrH(800), diff --git a/src/gui/gui.h b/src/gui/gui.h index 280ec76b..797f9fd7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -260,6 +260,14 @@ enum FurnaceGUIWindows { GUI_WINDOW_SPOILER }; +enum FurnaceGUIMobileScenes { + GUI_SCENE_PATTERN, + GUI_SCENE_ORDERS, + GUI_SCENE_INSTRUMENT, + GUI_SCENE_WAVETABLE, + GUI_SCENE_SAMPLE +}; + enum FurnaceGUIFileDialogs { GUI_FILE_OPEN, GUI_FILE_SAVE, @@ -1009,6 +1017,7 @@ class FurnaceGUI { FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; FurnaceGUIWarnings postWarnAction; + FurnaceGUIMobileScenes mobScene; FurnaceGUIFileDialog* fileDialog; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 5081d6ef..002cabd9 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -374,7 +374,7 @@ void FurnaceGUI::drawPattern() { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); if (mobileUI) { patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); - patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); ImGui::SetNextWindowPos(patWindowPos); ImGui::SetNextWindowSize(patWindowSize); } diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 0e0063bb..27d8bbed 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -52,6 +52,10 @@ void FurnaceGUI::drawPiano() { nextWindow=GUI_WINDOW_NOTHING; } if (!pianoOpen) return; + if (mobileUI) { + ImGui::SetNextWindowPos(ImVec2(patWindowPos.x,patWindowPos.y+patWindowSize.y)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.5*scrW*dpiScale):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),0.3*scrH*dpiScale)); + } if (ImGui::Begin("Piano",&pianoOpen,((pianoOptions)?0:ImGuiWindowFlags_NoTitleBar)|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { bool oldPianoKeyPressed[180]; memcpy(oldPianoKeyPressed,pianoKeyPressed,180*sizeof(bool)); From ffcef2ac735b7b4279f99b34b6a9ac7d33a90f9a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 8 Sep 2022 23:48:39 -0500 Subject: [PATCH 43/93] fix .opni TL loading --- src/engine/fileOpsIns.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 62c3a1d8..c8dbb57f 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -805,7 +805,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S op.mult = dtMul & 0xF; op.dt = ((dtMul >> 4) & 0x7); - op.tl = totalLevel & 0x3F; + op.tl = totalLevel & 0x7F; op.rs = ((arRateScale >> 6) & 0x3); op.ar = arRateScale & 0x1F; op.dr = drAmpEnable & 0x1F; @@ -1643,7 +1643,7 @@ void DivEngine::loadWOPN(SafeReader& reader, std::vector& ret, S total += (op.mult = dtMul & 0xF); total += (op.dt = ((dtMul >> 4) & 0x7)); - total += (op.tl = totalLevel & 0x3F); + total += (op.tl = totalLevel & 0x7F); total += (op.rs = ((arRateScale >> 6) & 0x3)); total += (op.ar = arRateScale & 0x1F); total += (op.dr = drAmpEnable & 0x1F); From 0c8cde9f85706bd46d5af43a6e16668486e75750 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 00:02:43 -0500 Subject: [PATCH 44/93] GUI: mark modified upon order val change --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d9e8ffa0..6ad37fff 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1154,6 +1154,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { e->lockSave([this,num]() { e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); + MARK_MODIFIED; if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; if (!curNibble) { From 9a3c81d90ac04895a3d56f858f70b5083947f1ff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 03:23:18 -0500 Subject: [PATCH 45/93] GUI: wave generator, part 6 FM now with feedback! --- src/gui/gui.cpp | 12 +++++++++--- src/gui/gui.h | 2 +- src/gui/waveEdit.cpp | 29 ++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6ad37fff..55569a62 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5235,9 +5235,15 @@ FurnaceGUI::FurnaceGUI(): memset(waveGenAmp,0,sizeof(float)*16); memset(waveGenPhase,0,sizeof(float)*16); - memset(waveGenTL,0,sizeof(float)*4); - memset(waveGenMult,0,sizeof(int)*4); - memset(waveGenFB,0,sizeof(float)*4); + waveGenTL[0]=0.0f; + waveGenTL[1]=0.0f; + waveGenTL[2]=0.0f; + waveGenTL[3]=1.0f; + waveGenMult[0]=1; + waveGenMult[1]=1; + waveGenMult[2]=1; + waveGenMult[3]=1; + memset(waveGenFB,0,sizeof(int)*4); memset(waveGenFMCon1,0,sizeof(bool)*4); memset(waveGenFMCon2,0,sizeof(bool)*3); memset(waveGenFMCon3,0,sizeof(bool)*2); diff --git a/src/gui/gui.h b/src/gui/gui.h index 797f9fd7..7a67ac37 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1566,7 +1566,7 @@ class FurnaceGUI { float waveGenPhase[16]; float waveGenTL[4]; int waveGenMult[4]; - float waveGenFB[4]; + int waveGenFB[4]; bool waveGenFMCon1[4]; bool waveGenFMCon2[3]; bool waveGenFMCon3[2]; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 9842d808..80f3a341 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -61,12 +61,31 @@ void FurnaceGUI::doGenerateWave() { if (wave->len<2) return; if (waveGenFM) { + float s0fb0=0; + float s0fb1=0; + float s1fb0=0; + float s1fb1=0; + float s2fb0=0; + float s2fb1=0; + float s3fb0=0; + float s3fb1=0; for (int i=0; ilen; i++) { float pos=(float)i/(float)wave->len; - float s0=sin(pos*multFactors[waveGenMult[0]])*waveGenTL[0]; - float s1=sin((pos+(waveGenFMCon1[0]?s0:0.0f))*multFactors[waveGenMult[1]])*waveGenTL[1]; - float s2=sin((pos+(waveGenFMCon1[1]?s0:0.0f)+(waveGenFMCon2[0]?s1:0.0f))*multFactors[waveGenMult[2]])*waveGenTL[2]; - float s3=sin((pos+(waveGenFMCon1[2]?s0:0.0f)+(waveGenFMCon2[1]?s1:0.0f)+(waveGenFMCon3[0]?s2:0.0f))*multFactors[waveGenMult[3]])*waveGenTL[3]; + float s0=sin((pos+(waveGenFB[0]?((s0fb0+s0fb1)*pow(2.0f,waveGenFB[0]-8)):0.0f))*multFactors[waveGenMult[0]])*waveGenTL[0]; + s0fb0=s0fb1; + s0fb1=s0; + + float s1=sin((pos+(waveGenFB[1]?((s1fb0+s1fb1)*pow(2.0f,waveGenFB[1]-8)):0.0f)+(waveGenFMCon1[0]?s0:0.0f))*multFactors[waveGenMult[1]])*waveGenTL[1]; + s1fb0=s1fb1; + s1fb1=s1; + + float s2=sin((pos+(waveGenFB[2]?((s2fb0+s2fb1)*pow(2.0f,waveGenFB[2]-8)):0.0f)+(waveGenFMCon1[1]?s0:0.0f)+(waveGenFMCon2[0]?s1:0.0f))*multFactors[waveGenMult[2]])*waveGenTL[2]; + s2fb0=s2fb1; + s2fb1=s2; + + float s3=sin((pos+(waveGenFB[3]?((s3fb0+s3fb1)*pow(2.0f,waveGenFB[3]-8)):0.0f)+(waveGenFMCon1[2]?s0:0.0f)+(waveGenFMCon2[1]?s1:0.0f)+(waveGenFMCon3[0]?s2:0.0f))*multFactors[waveGenMult[3]])*waveGenTL[3]; + s3fb0=s3fb1; + s3fb1=s3; if (waveGenFMCon1[3]) finalResult[i]+=s0; if (waveGenFMCon2[2]) finalResult[i]+=s1; @@ -394,7 +413,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::PushID(i); - if (CWSliderFloat("##WGFB",&waveGenFB[i],0.0f,7.0f)) { + if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) { doGenerateWave(); } ImGui::PopID(); From 7e065e4cfea0ddfde34e2a49657cfdbc56080634 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 15:31:29 -0500 Subject: [PATCH 46/93] GUI: more mobile UI things --- src/gui/editControls.cpp | 40 ++++++++++++++++++++++++++++------ src/gui/gui.cpp | 46 ++++++++++++++++++++++++++++++++++++++-- src/gui/insEdit.cpp | 9 +++++++- src/gui/sampleEdit.cpp | 6 ++++++ src/gui/waveEdit.cpp | 9 +++++++- 5 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 5307163e..91db32f5 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -104,13 +104,39 @@ void FurnaceGUI::drawMobileControls() { ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f)); ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { - ImGui::Button("Pattern"); - ImGui::SameLine(); - ImGui::Button("Ins"); - ImGui::SameLine(); - ImGui::Button("Wave"); - ImGui::SameLine(); - ImGui::Button("Sample"); + if (ImGui::BeginTable("SceneSel",5)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,1.0f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImVec2 buttonSize=ImGui::GetContentRegionAvail(); + buttonSize.y=30.0f*dpiScale; + + if (ImGui::Button("Pattern",buttonSize)) { + mobScene=GUI_SCENE_PATTERN; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Orders",buttonSize)) { + mobScene=GUI_SCENE_ORDERS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Ins",buttonSize)) { + mobScene=GUI_SCENE_INSTRUMENT; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Wave",buttonSize)) { + mobScene=GUI_SCENE_WAVETABLE; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Sample",buttonSize)) { + mobScene=GUI_SCENE_SAMPLE; + } + ImGui::EndTable(); + } ImGui::Text("Data list goes here..."); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 55569a62..4aff3657 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3353,8 +3353,32 @@ bool FurnaceGUI::loop() { // scene handling goes here! pianoOpen=true; drawMobileControls(); - drawPattern(); - drawPiano(); + switch (mobScene) { + case GUI_SCENE_PATTERN: + patternOpen=true; + drawPattern(); + drawPiano(); + break; + case GUI_SCENE_ORDERS: + ordersOpen=true; + drawOrders(); + break; + case GUI_SCENE_INSTRUMENT: + insEditOpen=true; + drawInsEdit(); + drawPiano(); + break; + case GUI_SCENE_WAVETABLE: + waveEditOpen=true; + drawWaveEdit(); + drawPiano(); + break; + case GUI_SCENE_SAMPLE: + sampleEditOpen=true; + drawSampleEdit(); + drawPiano(); + break; + } } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); @@ -3397,6 +3421,13 @@ bool FurnaceGUI::loop() { if (firstFrame) { firstFrame=false; +#ifdef IS_MOBILE + SDL_GetWindowSize(sdlWin,&scrW,&scrH); + scrW/=dpiScale; + scrH/=dpiScale; + portrait=(scrWgetConfInt("lastWindowWidth",1280); scrH=scrConfH=e->getConfInt("lastWindowHeight",800); scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED); scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); scrMax=e->getConfBool("lastWindowMax",false); +#endif portrait=(scrW((displaySize.w/dpiScale)-48) && scrH>((displaySize.h/dpiScale)-64)) { diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 662448df..84b70d77 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1560,7 +1560,14 @@ void FurnaceGUI::drawInsEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!insEditOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + } if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curIns<0 || curIns>=(int)e->song.ins.size()) { ImGui::Text("no instrument selected"); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 0ce1500e..18579e71 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -35,6 +35,12 @@ void FurnaceGUI::drawSampleEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!sampleEditOpen) return; + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::Text("no sample selected"); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 80f3a341..1132b9ea 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -164,7 +164,14 @@ void FurnaceGUI::drawWaveEdit() { } if (!waveEditOpen) return; float wavePreview[257]; - ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + } if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); From 84b0ffbac324899126a8240c2f1b44ba1883a7a4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 15:31:38 -0500 Subject: [PATCH 47/93] VRC6: possibly bring solution to #671 --- src/engine/platform/vrc6.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 2500ce19..6e5e0d73 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -195,7 +195,7 @@ void DivPlatformVRC6::tick(bool sysTick) { if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOff) { chWrite(i,2,0); - } else { + } else if (chan[i].active) { chWrite(i,1,chan[i].freq&0xff); chWrite(i,2,0x80|((chan[i].freq>>8)&0xf)); } From d021005f26d518482418d63983bbc9d84bf4722a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 16:41:00 -0500 Subject: [PATCH 48/93] GUI: fix arp macro hover --- src/gui/editControls.cpp | 4 ++++ src/gui/gui.cpp | 2 +- src/gui/gui.h | 8 +++++--- src/gui/insEdit.cpp | 26 +++++++++++++++----------- src/gui/plot_nolerp.cpp | 10 +++++----- src/gui/plot_nolerp.h | 2 +- src/gui/waveEdit.cpp | 4 ++-- 7 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 91db32f5..f7e40c39 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -138,6 +138,10 @@ void FurnaceGUI::drawMobileControls() { ImGui::EndTable(); } + if (ImGui::Button("Create Ins")) { + doAction(GUI_ACTION_INS_LIST_ADD); + } + ImGui::Text("Data list goes here..."); if (ImGui::Button("New")) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4aff3657..9a49dd48 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4669,7 +4669,6 @@ bool FurnaceGUI::init() { SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale); } } -#endif if (SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(sdlWin),&displaySize)==0) { if (scrW>((displaySize.w/dpiScale)-48) && scrH>((displaySize.h/dpiScale)-64)) { @@ -4684,6 +4683,7 @@ bool FurnaceGUI::init() { SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale); } } +#endif } #endif diff --git a/src/gui/gui.h b/src/gui/gui.h index 7a67ac37..c4a3e18c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -887,9 +887,10 @@ struct FurnaceGUIMacroDesc { ImVec4 color; unsigned int bitOffset; bool isBitfield, blockMode, bit30; - String (*hoverFunc)(int,float); + String (*hoverFunc)(int,float,void*); + void* hoverFuncUser; - FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0, bool bit30Special=false): + FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float,void*)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0, bool bit30Special=false, void* hfu=NULL): macro(m), height(macroHeight), displayName(name), @@ -900,7 +901,8 @@ struct FurnaceGUIMacroDesc { isBitfield(bitfield), blockMode(block), bit30(bit30Special), - hoverFunc(hf) { + hoverFunc(hf), + hoverFuncUser(hfu) { // MSVC -> hell this->min=macroMin; this->max=macroMax; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 84b70d77..b8427ee4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -316,27 +316,31 @@ const char* macroRelativeMode="Relative"; const char* macroQSoundMode="QSound"; const char* macroDummyMode="Bug"; -String macroHoverNote(int id, float val) { - if (val<-60 || val>=120) return "???"; - return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); +String macroHoverNote(int id, float val, void* u) { + int* macroVal=(int*)u; + if ((macroVal[id]&0xc0000000)==0x40000000 || (macroVal[id]&0xc0000000)==0x80000000) { + if (val<-60 || val>=120) return "???"; + return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); + } + return fmt::sprintf("%d: %d",id,(int)val); } -String macroHover(int id, float val) { +String macroHover(int id, float val, void* u) { return fmt::sprintf("%d: %d",id,val); } -String macroHoverLoop(int id, float val) { +String macroHoverLoop(int id, float val, void* u) { if (val>1) return "Release"; if (val>0) return "Loop"; return ""; } -String macroHoverBit30(int id, float val) { +String macroHoverBit30(int id, float val, void* u) { if (val>0) return "Fixed"; return "Relative"; } -String macroHoverES5506FilterMode(int id, float val) { +String macroHoverES5506FilterMode(int id, float val, void* u) { String mode="???"; switch (((int)val)&3) { case 0: @@ -357,7 +361,7 @@ String macroHoverES5506FilterMode(int id, float val) { return fmt::sprintf("%d: %s",id,mode); } -String macroLFOWaves(int id, float val) { +String macroLFOWaves(int id, float val, void* u) { switch (((int)val)&3) { case 0: return "Saw"; @@ -1355,7 +1359,7 @@ void FurnaceGUI::drawMacros(std::vector& macros) { if (i.isBitfield) { PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight); } else { - PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight); + PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight); } if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) { macroDragStart=ImGui::GetItemRectMin(); @@ -3613,7 +3617,7 @@ void FurnaceGUI::drawInsEdit() { modTable[i]=ins->fds.modTable[i]; } ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale); - PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=modTableSize; @@ -4232,7 +4236,7 @@ void FurnaceGUI::drawInsEdit() { if (volMax>0) { macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); } - macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,NULL,false,NULL,0,true)); + macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,0,true,ins->std.arpMacro.val)); if (dutyMax>0) { if (ins->type==DIV_INS_MIKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); diff --git a/src/gui/plot_nolerp.cpp b/src/gui/plot_nolerp.cpp index d802cbc5..b75bda4f 100644 --- a/src/gui/plot_nolerp.cpp +++ b/src/gui/plot_nolerp.cpp @@ -293,7 +293,7 @@ void PlotBitfield(const char* label, const int* values, int values_count, int va PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor); } -int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) +int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) { ImGuiContext& g = *GImGui; ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -359,7 +359,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett const float v0 = values_getter(data, (v_idx) % values_count); const float v1 = values_getter(data, (v_idx + 1) % values_count); if (hoverFunc) { - std::string hoverText=hoverFunc(v_idx+values_display_offset,v0); + std::string hoverText=hoverFunc(v_idx+values_display_offset,v0,hoverFuncUser); if (!hoverText.empty()) { ImGui::SetTooltip("%s",hoverText.c_str()); } @@ -459,8 +459,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett return idx_hovered; } -void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) +void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) { FurnacePlotArrayGetterData data(values, stride); - PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, blockMode, guideFunc, values_highlight, highlightColor); -} \ No newline at end of file + PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, hoverFuncUser, blockMode, guideFunc, values_highlight, highlightColor); +} diff --git a/src/gui/plot_nolerp.h b/src/gui/plot_nolerp.h index b353e6f8..48332b33 100644 --- a/src/gui/plot_nolerp.h +++ b/src/gui/plot_nolerp.h @@ -22,4 +22,4 @@ void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); -void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float) = NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); \ No newline at end of file +void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float,void*) = NULL, void* hoverFuncUser=NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 1132b9ea..f26f4583 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -271,7 +271,7 @@ void FurnaceGUI::drawWaveEdit() { if (waveEditStyle) { PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); } else { - PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); } if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { waveDragStart=ImGui::GetItemRectMin(); @@ -412,7 +412,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::PushID(i); - if (CWSliderInt("##WGMULT",&waveGenMult[i],0,15)) { + if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) { doGenerateWave(); } ImGui::PopID(); From 0ac92209d034e7209085cf52114b75e68cb3dd43 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 18:53:20 -0500 Subject: [PATCH 49/93] GUI: even more mobile work --- CONTRIBUTING.md | 2 +- android/app/src/main/AndroidManifest.xml | 14 +- src/gui/dataList.cpp | 60 +++- src/gui/editControls.cpp | 18 +- src/gui/gui.cpp | 171 ++++++----- src/gui/gui.h | 10 +- src/gui/insEdit.cpp | 2 +- src/gui/pattern.cpp | 2 +- src/gui/piano.cpp | 347 ++++++++++++----------- src/gui/sampleEdit.cpp | 2 +- src/gui/waveEdit.cpp | 2 +- 11 files changed, 353 insertions(+), 277 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6931ce8..853020f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted. the coding style is described here: -- indentation: two spaces +- indentation: two spaces. **strictly** spaces. do NOT use tabs. - modified 1TBS style: - no spaces in function calls - spaces between arguments in function declarations diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5f7a06ef..e28b7fd1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ @@ -83,13 +83,17 @@ - diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 0ef1f183..0519fca3 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -29,14 +29,20 @@ const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; -void FurnaceGUI::drawInsList() { +void FurnaceGUI::drawInsList(bool asChild) { if (nextWindow==GUI_WINDOW_INS_LIST) { insListOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } - if (!insListOpen) return; - if (ImGui::Begin("Instruments",&insListOpen,globalWinFlags)) { + if (!insListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Instruments"); + } else { + began=ImGui::Begin("Instruments",&insListOpen,globalWinFlags); + } + if (began) { if (settings.unifiedDataView) settings.horizontalDataView=0; if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD); @@ -409,11 +415,15 @@ void FurnaceGUI::drawInsList() { ImGui::EndTable(); } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; + ImGui::End(); + } } -void FurnaceGUI::drawWaveList() { +void FurnaceGUI::drawWaveList(bool asChild) { if (nextWindow==GUI_WINDOW_WAVE_LIST) { waveListOpen=true; if (settings.unifiedDataView) { @@ -424,8 +434,14 @@ void FurnaceGUI::drawWaveList() { nextWindow=GUI_WINDOW_NOTHING; } if (settings.unifiedDataView) return; - if (!waveListOpen) return; - if (ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags)) { + if (!waveListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Wavetables"); + } else { + began=ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags); + } + if (began) { if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); } @@ -476,11 +492,15 @@ void FurnaceGUI::drawWaveList() { ImGui::EndTable(); } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; + ImGui::End(); + } } -void FurnaceGUI::drawSampleList() { +void FurnaceGUI::drawSampleList(bool asChild) { if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { sampleListOpen=true; if (settings.unifiedDataView) { @@ -491,8 +511,14 @@ void FurnaceGUI::drawSampleList() { nextWindow=GUI_WINDOW_NOTHING; } if (settings.unifiedDataView) return; - if (!sampleListOpen) return; - if (ImGui::Begin("Samples",&sampleListOpen,globalWinFlags)) { + if (!sampleListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Samples"); + } else { + began=ImGui::Begin("Samples",&sampleListOpen,globalWinFlags); + } + if (began) { if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } @@ -548,8 +574,12 @@ void FurnaceGUI::drawSampleList() { } ImGui::Unindent(); } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; + ImGui::End(); + } } void FurnaceGUI::actualWaveList() { diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index f7e40c39..ab3cc688 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -138,12 +138,22 @@ void FurnaceGUI::drawMobileControls() { ImGui::EndTable(); } - if (ImGui::Button("Create Ins")) { - doAction(GUI_ACTION_INS_LIST_ADD); + if (settings.unifiedDataView) { + drawInsList(true); + } else { + switch (mobScene) { + case GUI_SCENE_WAVETABLE: + drawWaveList(true); + break; + case GUI_SCENE_SAMPLE: + drawSampleList(true); + break; + default: + drawInsList(true); + break; + } } - ImGui::Text("Data list goes here..."); - if (ImGui::Button("New")) { mobileMenuOpen=false; //doAction(GUI_ACTION_NEW); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9a49dd48..4c1fc5c4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2544,12 +2544,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { TouchPoint* point=NULL; FIND_POINT(point,ev.tfinger.fingerId); if (point!=NULL) { + float prevX=point->x; + float prevY=point->y; point->x=ev.tfinger.x*scrW*dpiScale; point->y=ev.tfinger.y*scrH*dpiScale; point->z=ev.tfinger.pressure; if (point->id==0) { ImGui::GetIO().AddMousePosEvent(point->x,point->y); + pointMotion(point->x,point->y,point->x-prevX,point->y-prevY); } } break; @@ -2570,6 +2573,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { if (newPoint.id==0) { ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y); ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true); + pointDown(newPoint.x,newPoint.y,0); } break; } @@ -2577,13 +2581,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { for (size_t i=0; irenderSamplesP(); + } else { + if (sampleSelStart>sampleSelEnd) { + sampleSelStart^=sampleSelEnd; + sampleSelEnd^=sampleSelStart; + sampleSelStart^=sampleSelEnd; + } + } + } + sampleDragActive=false; + if (selecting) { + if (!selectingFull) cursor=selEnd; + finishSelection(); + demandScrollX=true; + if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && + cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { + if (!settings.cursorMoveNoScroll) { + updateScroll(cursor.y); + } + } + } +} + +void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) { + if (selecting) { + // detect whether we have to scroll + if (ypatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { + addScroll(1); + } + } + if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { + int distance=fabs((double)xrel); + if (distance<1) distance=1; + float start=x-xrel; + float end=x; + float startY=y-yrel; + float endY=y; + for (int i=0; i<=distance; i++) { + float fraction=(float)i/(float)distance; + float x=start+(end-start)*fraction; + float y=startY+(endY-startY)*fraction; + processDrags(x,y); + } + } +} + // how many pixels should be visible at least at x/y dir #define OOB_PIXELS_SAFETY 25 @@ -2640,7 +2724,7 @@ bool FurnaceGUI::loop() { if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } eventTimeBegin=SDL_GetPerformanceCounter(); - bool updateWindow = false; + bool updateWindow=false; while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); @@ -2658,80 +2742,14 @@ bool FurnaceGUI::loop() { motionXrel*=dpiScale; motionYrel*=dpiScale; #endif - if (selecting) { - // detect whether we have to scroll - if (motionYpatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { - addScroll(1); - } - } - if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { - int distance=fabs((double)motionXrel); - if (distance<1) distance=1; - float start=motionX-motionXrel; - float end=motionX; - float startY=motionY-motionYrel; - float endY=motionY; - for (int i=0; i<=distance; i++) { - float fraction=(float)i/(float)distance; - float x=start+(end-start)*fraction; - float y=startY+(endY-startY)*fraction; - processDrags(x,y); - } - } + pointMotion(motionX,motionY,motionXrel,motionYrel); break; } case SDL_MOUSEBUTTONUP: - if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) { - MARK_MODIFIED; - } - if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) { - displayMacroMenu=true; - } - macroDragActive=false; - macroDragBitMode=false; - macroDragInitialValue=false; - macroDragInitialValueSet=false; - macroDragLastX=-1; - macroDragLastY=-1; - macroLoopDragActive=false; - waveDragActive=false; - if (sampleDragActive) { - logD("stopping sample drag"); - if (sampleDragMode) { - e->renderSamplesP(); - } else { - if (sampleSelStart>sampleSelEnd) { - sampleSelStart^=sampleSelEnd; - sampleSelEnd^=sampleSelStart; - sampleSelStart^=sampleSelEnd; - } - } - } - sampleDragActive=false; - if (selecting) { - if (!selectingFull) cursor=selEnd; - finishSelection(); - demandScrollX=true; - if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && - cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { - if (!settings.cursorMoveNoScroll) { - updateScroll(cursor.y); - } - } - } + pointUp(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEBUTTONDOWN: - aboutOpen=false; - if (bindSetActive) { - bindSetActive=false; - bindSetPending=false; - actionKeys[bindSetTarget]=bindSetPrevValue; - bindSetTarget=0; - bindSetPrevValue=0; - } + pointDown(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEWHEEL: wheelX+=ev.wheel.x; @@ -3356,25 +3374,30 @@ bool FurnaceGUI::loop() { switch (mobScene) { case GUI_SCENE_PATTERN: patternOpen=true; + curWindow=GUI_WINDOW_PATTERN; drawPattern(); drawPiano(); break; case GUI_SCENE_ORDERS: ordersOpen=true; + curWindow=GUI_WINDOW_ORDERS; drawOrders(); break; case GUI_SCENE_INSTRUMENT: insEditOpen=true; + curWindow=GUI_WINDOW_INS_EDIT; drawInsEdit(); drawPiano(); break; case GUI_SCENE_WAVETABLE: waveEditOpen=true; + curWindow=GUI_WINDOW_WAVE_EDIT; drawWaveEdit(); drawPiano(); break; case GUI_SCENE_SAMPLE: sampleEditOpen=true; + curWindow=GUI_WINDOW_SAMPLE_EDIT; drawSampleEdit(); drawPiano(); break; @@ -4628,7 +4651,7 @@ bool FurnaceGUI::init() { portrait=(scrW0)?1:0),ImGuiTableFlags_BordersInnerV)) { + if (ImGui::BeginTable("PianoLayout",((pianoOptions && (!mobileUI || !portrait))?2:1),ImGuiTableFlags_BordersInnerV)) { int& off=(e->isPlaying() || pianoSharePosition)?pianoOffset:pianoOffsetEdit; int& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit; bool view=(pianoView==2)?(!e->isPlaying()):pianoView; - if (pianoOptions) { + if (pianoOptions && (!mobileUI || !portrait)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); } - if (pianoInputPadMode==1 && cursor.xFine>0) { - ImGui::TableSetupColumn("c0s",ImGuiTableColumnFlags_WidthStretch,2.0f); - } ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f); ImGui::TableNextRow(); if (pianoOptions) { ImGui::TableNextColumn(); - float optionSizeY=ImGui::GetContentRegionAvail().y*0.5-ImGui::GetStyle().ItemSpacing.y; - ImVec2 optionSize=ImVec2(1.2f*optionSizeY,optionSizeY); + float optionSizeY=ImGui::GetContentRegionAvail().y*((mobileUI && portrait)?0.3:0.5)-ImGui::GetStyle().ItemSpacing.y; + ImVec2 optionSize=ImVec2((mobileUI && portrait)?((ImGui::GetContentRegionAvail().x-ImGui::GetStyle().ItemSpacing.x*5.0f)/6.0f):(1.2f*optionSizeY),optionSizeY); if (pianoOptionsSet) { if (ImGui::Button("OFF##PianoNOff",optionSize)) { if (edit) noteInput(0,100); @@ -126,6 +123,10 @@ void FurnaceGUI::drawPiano() { ImGui::EndPopup(); } + if (mobileUI && portrait) { + ImGui::SameLine(); + } + if (pianoOptionsSet) { if (ImGui::Button("REL##PianoNMRel",optionSize)) { if (edit) noteInput(0,102); @@ -152,6 +153,10 @@ void FurnaceGUI::drawPiano() { } } + if (mobileUI && portrait) { + ImGui::TableNextRow(); + } + ImGui::TableNextColumn(); if (pianoInputPadMode==1 && cursor.xFine>0) { ImVec2 buttonSize=ImGui::GetContentRegionAvail(); @@ -199,192 +204,192 @@ void FurnaceGUI::drawPiano() { ImGui::EndTable(); } - ImGui::TableNextColumn(); - } - ImGuiWindow* window=ImGui::GetCurrentWindow(); - ImVec2 size=ImGui::GetContentRegionAvail(); - ImDrawList* dl=ImGui::GetWindowDrawList(); + } else { + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 size=ImGui::GetContentRegionAvail(); + ImDrawList* dl=ImGui::GetWindowDrawList(); - ImVec2 minArea=window->DC.CursorPos; - ImVec2 maxArea=ImVec2( - minArea.x+size.x, - minArea.y+size.y - ); - ImRect rect=ImRect(minArea,maxArea); + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); - // render piano - //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); - if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { - ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay")); - if (view) { - int notes=oct*12; - // evaluate input - for (TouchPoint& i: activePoints) { - if (rect.Contains(ImVec2(i.x,i.y))) { - int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off; - if (note<0) continue; - if (note>=180) continue; - pianoKeyPressed[note]=true; - } - } - - for (int i=0; i=180) continue; - float pkh=pianoKeyHit[note]; - ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; - if (pianoKeyPressed[note]) { - color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; - } else { - ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; - color.x+=(colorHit.x-color.x)*pkh; - color.y+=(colorHit.y-color.y)*pkh; - color.z+=(colorHit.z-color.z)*pkh; - color.w+=(colorHit.w-color.w)*pkh; - } - ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f)); - ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f)); - p1.x-=dpiScale; - dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); - if ((i%12)==0) { - String label=fmt::sprintf("%d",(note-60)/12); - ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); - ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); - pText.x-=labelSize.x*0.5f; - pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; - dl->AddText(pText,0xff404040,label.c_str()); - } - } - } else { - int bottomNotes=7*oct; - // evaluate input - for (TouchPoint& i: activePoints) { - if (rect.Contains(ImVec2(i.x,i.y))) { - // top - int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct; - ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f)); - ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f)); - bool foundTopKey=false; - - for (int j=0; j<5; j++) { - int note=topKeyNotes[j]+12*(o+off); + // render piano + //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { + ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay")); + if (view) { + int notes=oct*12; + // evaluate input + for (TouchPoint& i: activePoints) { + if (rect.Contains(ImVec2(i.x,i.y))) { + int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off; if (note<0) continue; if (note>=180) continue; - ImRect keyRect=ImRect( - ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)), - ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)) - ); - if (keyRect.Contains(ImVec2(i.x,i.y))) { - pianoKeyPressed[note]=true; - foundTopKey=true; - break; - } + pianoKeyPressed[note]=true; } - if (foundTopKey) continue; - - // bottom - int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes; - int note=bottomKeyNotes[n%7]+12*((n/7)+off); - if (note<0) continue; - if (note>=180) continue; - pianoKeyPressed[note]=true; - } - } - - for (int i=0; i=180) continue; - - float pkh=pianoKeyHit[note]; - ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; - if (pianoKeyPressed[note]) { - color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; - } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; - color.x+=(colorHit.x-color.x)*pkh; - color.y+=(colorHit.y-color.y)*pkh; - color.z+=(colorHit.z-color.z)*pkh; - color.w+=(colorHit.w-color.w)*pkh; } - ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); - ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); - p1.x-=dpiScale; - - dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); - if ((i%7)==0) { - String label=fmt::sprintf("%d",(note-60)/12); - ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); - ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); - pText.x-=labelSize.x*0.5f; - pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; - dl->AddText(pText,0xff404040,label.c_str()); - } - } - - for (int i=0; i=180) continue; float pkh=pianoKeyHit[note]; - ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; + ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; if (pianoKeyPressed[note]) { - color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; + color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; color.x+=(colorHit.x-color.x)*pkh; color.y+=(colorHit.y-color.y)*pkh; color.z+=(colorHit.z-color.z)*pkh; color.w+=(colorHit.w-color.w)*pkh; } - ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); - ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); - dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND])); - p0.x+=dpiScale; + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f)); p1.x-=dpiScale; - p1.y-=dpiScale; dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + if ((i%12)==0) { + String label=fmt::sprintf("%d",(note-60)/12); + ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); + ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); + pText.x-=labelSize.x*0.5f; + pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; + dl->AddText(pText,0xff404040,label.c_str()); + } + } + } else { + int bottomNotes=7*oct; + // evaluate input + for (TouchPoint& i: activePoints) { + if (rect.Contains(ImVec2(i.x,i.y))) { + // top + int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct; + ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f)); + ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f)); + bool foundTopKey=false; + + for (int j=0; j<5; j++) { + int note=topKeyNotes[j]+12*(o+off); + if (note<0) continue; + if (note>=180) continue; + ImRect keyRect=ImRect( + ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)), + ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)) + ); + if (keyRect.Contains(ImVec2(i.x,i.y))) { + pianoKeyPressed[note]=true; + foundTopKey=true; + break; + } + } + if (foundTopKey) continue; + + // bottom + int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes; + int note=bottomKeyNotes[n%7]+12*((n/7)+off); + if (note<0) continue; + if (note>=180) continue; + pianoKeyPressed[note]=true; + } + } + + for (int i=0; i=180) continue; + + float pkh=pianoKeyHit[note]; + ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; + if (pianoKeyPressed[note]) { + color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; + } else { + ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; + color.x+=(colorHit.x-color.x)*pkh; + color.y+=(colorHit.y-color.y)*pkh; + color.z+=(colorHit.z-color.z)*pkh; + color.w+=(colorHit.w-color.w)*pkh; + } + + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); + p1.x-=dpiScale; + + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + if ((i%7)==0) { + String label=fmt::sprintf("%d",(note-60)/12); + ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); + ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); + pText.x-=labelSize.x*0.5f; + pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; + dl->AddText(pText,0xff404040,label.c_str()); + } + } + + for (int i=0; i=180) continue; + float pkh=pianoKeyHit[note]; + ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; + if (pianoKeyPressed[note]) { + color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; + } else { + ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + color.x+=(colorHit.x-color.x)*pkh; + color.y+=(colorHit.y-color.y)*pkh; + color.z+=(colorHit.z-color.z)*pkh; + color.w+=(colorHit.w-color.w)*pkh; + } + ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); + ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); + dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND])); + p0.x+=dpiScale; + p1.x-=dpiScale; + p1.y-=dpiScale; + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + } + } + } + + const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + for (int i=0; i<180; i++) { + pianoKeyHit[i]-=reduction; + if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; + } + } + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + pianoOptions=!pianoOptions; + } + + // first check released keys + for (int i=0; i<180; i++) { + int note=i-60; + if (!pianoKeyPressed[i]) { + if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { + e->synchronized([this,note]() { + e->autoNoteOff(-1,note); + }); } } } - - const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + // then pressed ones for (int i=0; i<180; i++) { - pianoKeyHit[i]-=reduction; - if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; - } - } - - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - pianoOptions=!pianoOptions; - } - - // first check released keys - for (int i=0; i<180; i++) { - int note=i-60; - if (!pianoKeyPressed[i]) { - if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { - e->synchronized([this,note]() { - e->autoNoteOff(-1,note); - }); - } - } - } - // then pressed ones - for (int i=0; i<180; i++) { - int note=i-60; - if (pianoKeyPressed[i]) { - if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { - e->synchronized([this,note]() { - e->autoNoteOn(-1,curIns,note); - }); - if (edit) noteInput(note,0); + int note=i-60; + if (pianoKeyPressed[i]) { + if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); + if (edit) noteInput(note,0); + } } } } diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 18579e71..76f654c1 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -37,7 +37,7 @@ void FurnaceGUI::drawSampleEdit() { if (!sampleEditOpen) return; if (mobileUI) { patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); - patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); ImGui::SetNextWindowPos(patWindowPos); ImGui::SetNextWindowSize(patWindowSize); } diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index f26f4583..c46e2ec0 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -166,7 +166,7 @@ void FurnaceGUI::drawWaveEdit() { float wavePreview[257]; if (mobileUI) { patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); - patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); ImGui::SetNextWindowPos(patWindowPos); ImGui::SetNextWindowSize(patWindowSize); } else { From f2e519d71e80e8057ce6569d4874aef079c672a2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 19:23:53 -0500 Subject: [PATCH 50/93] GUI: add an option to exit mobile UI --- src/gui/editControls.cpp | 172 ++++++++++++++++++++++++--------------- src/gui/gui.cpp | 17 ++-- src/gui/gui.h | 6 +- 3 files changed, 121 insertions(+), 74 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index ab3cc688..9d6c0d00 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -135,89 +135,127 @@ void FurnaceGUI::drawMobileControls() { if (ImGui::Button("Sample",buttonSize)) { mobScene=GUI_SCENE_SAMPLE; } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button("Song",buttonSize)) { + mobScene=GUI_SCENE_SONG; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Channels",buttonSize)) { + mobScene=GUI_SCENE_CHANNELS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Chips",buttonSize)) { + mobScene=GUI_SCENE_CHIPS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Other",buttonSize)) { + mobScene=GUI_SCENE_OTHER; + } ImGui::EndTable(); } + ImGui::Separator(); + if (settings.unifiedDataView) { drawInsList(true); } else { switch (mobScene) { + case GUI_SCENE_PATTERN: + case GUI_SCENE_ORDERS: + case GUI_SCENE_INSTRUMENT: + drawInsList(true); + break; case GUI_SCENE_WAVETABLE: drawWaveList(true); break; case GUI_SCENE_SAMPLE: drawSampleList(true); break; - default: - drawInsList(true); + case GUI_SCENE_SONG: { + if (ImGui::Button("New")) { + mobileMenuOpen=false; + //doAction(GUI_ACTION_NEW); + if (modified) { + showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); + } else { + displayNew=true; + } + } + ImGui::SameLine(); + if (ImGui::Button("Open")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Save")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE); + } + ImGui::SameLine(); + if (ImGui::Button("Save as...")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE_AS); + } + + ImGui::Button("1.1+ .dmf"); + ImGui::SameLine(); + ImGui::Button("Legacy .dmf"); + ImGui::SameLine(); + ImGui::Button("Export Audio"); + ImGui::SameLine(); + ImGui::Button("Export VGM"); + + ImGui::Button("CmdStream"); + + ImGui::Separator(); + + ImGui::Text("Song info here..."); break; + } + case GUI_SCENE_CHANNELS: + ImGui::Text("Channels here..."); + break; + case GUI_SCENE_CHIPS: + ImGui::Text("Chips here..."); + break; + case GUI_SCENE_OTHER: { + if (ImGui::Button("Osc")) { + oscOpen=!oscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("ChanOsc")) { + chanOscOpen=!chanOscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("RegView")) { + regViewOpen=!regViewOpen; + } + ImGui::SameLine(); + if (ImGui::Button("Stats")) { + statsOpen=!statsOpen; + } + + ImGui::Separator(); + + ImGui::Button("Panic"); + ImGui::SameLine(); + if (ImGui::Button("Settings")) { + mobileMenuOpen=false; + } + ImGui::SameLine(); + if (ImGui::Button("About")) { + mobileMenuOpen=false; + mobileMenuPos=0.0f; + aboutOpen=true; + } + if (ImGui::Button("Switch to Desktop Mode")) { + toggleMobileUI(!mobileUI); + } + break; + } } } - - if (ImGui::Button("New")) { - mobileMenuOpen=false; - //doAction(GUI_ACTION_NEW); - if (modified) { - showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); - } else { - displayNew=true; - } - } - ImGui::SameLine(); - if (ImGui::Button("Open")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button("Save")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_SAVE); - } - ImGui::SameLine(); - if (ImGui::Button("Save as...")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_SAVE_AS); - } - - ImGui::Button("1.1+ .dmf"); - ImGui::SameLine(); - ImGui::Button("Legacy .dmf"); - ImGui::SameLine(); - ImGui::Button("Export Audio"); - ImGui::SameLine(); - ImGui::Button("Export VGM"); - - ImGui::Button("CmdStream"); - ImGui::SameLine(); - ImGui::Button("Panic"); - ImGui::SameLine(); - if (ImGui::Button("Settings")) { - mobileMenuOpen=false; - } - ImGui::SameLine(); - if (ImGui::Button("About")) { - mobileMenuOpen=false; - mobileMenuPos=0.0f; - aboutOpen=true; - } - - ImGui::Separator(); - - if (ImGui::Button("Osc")) { - oscOpen=!oscOpen; - } - ImGui::SameLine(); - if (ImGui::Button("ChanOsc")) { - chanOscOpen=!chanOscOpen; - } - ImGui::SameLine(); - if (ImGui::Button("RegView")) { - regViewOpen=!regViewOpen; - } - ImGui::SameLine(); - if (ImGui::Button("Stats")) { - statsOpen=!statsOpen; - } } ImGui::End(); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4c1fc5c4..65e982c7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3245,6 +3245,11 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } +#ifdef IS_MOBILE + if (ImGui::MenuItem("switch to mobile view")) { + toggleMobileUI(!mobileUI); + } +#endif if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; @@ -3372,12 +3377,6 @@ bool FurnaceGUI::loop() { pianoOpen=true; drawMobileControls(); switch (mobScene) { - case GUI_SCENE_PATTERN: - patternOpen=true; - curWindow=GUI_WINDOW_PATTERN; - drawPattern(); - drawPiano(); - break; case GUI_SCENE_ORDERS: ordersOpen=true; curWindow=GUI_WINDOW_ORDERS; @@ -3401,6 +3400,12 @@ bool FurnaceGUI::loop() { drawSampleEdit(); drawPiano(); break; + default: + patternOpen=true; + curWindow=GUI_WINDOW_PATTERN; + drawPattern(); + drawPiano(); + break; } } else { globalWinFlags=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 380141e3..180013df 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -265,7 +265,11 @@ enum FurnaceGUIMobileScenes { GUI_SCENE_ORDERS, GUI_SCENE_INSTRUMENT, GUI_SCENE_WAVETABLE, - GUI_SCENE_SAMPLE + GUI_SCENE_SAMPLE, + GUI_SCENE_SONG, + GUI_SCENE_CHANNELS, + GUI_SCENE_CHIPS, + GUI_SCENE_OTHER, }; enum FurnaceGUIFileDialogs { From ce4c0b4291159ccd44640c8e188fc8e978b1d55a Mon Sep 17 00:00:00 2001 From: Waverider <33787286+liaminventions@users.noreply.github.com> Date: Fri, 9 Sep 2022 20:28:06 -0400 Subject: [PATCH 51/93] New Demo Music 010 Editor: https://youtu.be/l-7PsOxffYE her11 VERA edit: https://youtu.be/SDpDUQ2A6k0 chaostune: https://youtu.be/coJDJDpAG7M --- demos/Cafe - 010 Editor 2.0crk.fur | Bin 0 -> 13323 bytes demos/ChaosTune.fur | Bin 0 -> 3048 bytes demos/her11_veraedit.fur | Bin 0 -> 8604 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/Cafe - 010 Editor 2.0crk.fur create mode 100644 demos/ChaosTune.fur create mode 100644 demos/her11_veraedit.fur diff --git a/demos/Cafe - 010 Editor 2.0crk.fur b/demos/Cafe - 010 Editor 2.0crk.fur new file mode 100644 index 0000000000000000000000000000000000000000..339e7238f3795d7ed3c013bed685eaf0d67b7ffe GIT binary patch literal 13323 zcmZ{KbyQr>vMvb{T!XuNu;A`)!Ge1rKybIgU4wf91eahT=->?Q!5J*L3_idxJbvfC zbKkjdz4yoNwRd+{?XRoqt6p7u`{thpVR@1d`)#&XGh)VsVD{Ja#KeWs)PRJzvDmAM zx$R38z8}t1kXbF5*M9$_T zLRGD!PI-J)sW>xTDI_kf$u-zwpZp%?TBX5{`q;g^Jcu3j@f{HYT1b8a?c{jyRS-Dv z>gi;B@;2Is$*`SPpHJeWlac>;MF8)OL!%MJlbun;F2=u$g9A~1 zod`qJEWTJgw_=|@{fS2n^1uK!7cd->FdX7&2P>{4nBu&K-IpW4UWI@SF*bupHg%3) z!P^NonSlt9!QqPs><~q7MA&jYVxT2gPZ1T8y(s3-%TgAKtjTDuG!f2Tv|M9K4KpL;*`T$PGc4_-Z+?~FQr zXQzA^;4vN_c5sXj+cjVydDcUN?=3PY!P}9ZVR`r*U@qw~X*8KBWyqBx!_q}D641{i zj2@h^X@0Hh`ap<=(#_R-Z@>txFK74-&qV?}wcqv=02AelpT(C7e6yl4K`GFT`Bg^G#(D#-*D?zkO!s^#D zza6_ULKA^$oA-c}Z!RifI;?1L7S7(}XXUVl0DO}%B?wS5bF+@2KM0HWy{IiTumrP! z-3I!A$a^g?;tJyGztbOqxJYpJlfNQ`!#sF9oAjd&_HC6SY@p?eHm-r%D9z*9b&3dCc|lm>0H%O}eqc+HZdLb?eDIZOf(T>(-oKgwX6H-TEc-_q$X9xgZ(Mb-7E}N{}UTAkt+Dx-{8-~^=JN5uYOYJ zZbgcN(N08tp(O405e84=cKKLiX8_c1y%FPfKRXh)gzLDn(jSBNoPviK+s?AoxxhG= zpZX+)TBsLuIcuygBO`8O<>|Qdn!yGacURE(&2x6>F$d&P7$zPD6o3V3SeGFKCZLXe zMZ^FqSX)S3^xsMncxj)7JY#h?(NRFi7s^U1H;JKfKQD}!7IA7{|sdkUI-)s4XY zsLw~qk-ztT>G8Z=vPq84NH^Y|iz@K;z9;pZ!U)MyJ#4n049yOYyN~p_TKA))z#-RE z8q$xAKl7TXA8Y=nQbVKfr-Jj-WfjK?;6QJ%;4yE7;g1hPA-ID7hDXS z9bb;c^^7vTFEB*(Ju~%F?okMA%zT5V8tjhfMwRGBQH=!bm%kXFSI#yz$D2<#6gPR6 zjQ85%ocT4pD^B1jPWWaUp1J<(rjU(OwF8Pw$u6SGJ_>U@HJolO68=@5)Q9P68D;W^ zC!?v$5)m!}X(a*_4S~a<#&ufpgTOr$TDPARy&(#7{iH@x>j>G{OxstYzoiF4S2Lb<+h4u8+9>_JS35 zf1&s*PO{=cdRIe$IVHrKI=l$HSU$J*>rLl*;AIb|XN=G%u>VEtu^`h?Wq!8OHf3zk52(KNyW-sR=0It&Bk2Br&l&CN5|z z0JfUa4kA5Riyz?UeWoLyRL*~-oAx8gc}`tY+_Sc!f^tv$k;cZdXhX%Og>!{uTYi6L zWo)kB|L^cIG;%vz_0Jc5s1+YBL=z*c8#^eo2aE|z{7~e>4+X{TATkABd>Q1Z4*ndQ z(R*q0nHc!ZTHfxnVv(Z!^((R<3EMtmu)bW;!w<${O}~G;xw&LY1TQE=q39c$d7v>~ z&{0-qjKtrO*>}Ox!*P?dNfPF7XWrOfqvb#bTM1{%UvwYn6#je3rw2`Da@5~u>?g1fDV%uhByZnD(DxL!GD;5oU{;t6PZcmFt z9Lr_M5p1*_qWynh_pRrROB-E&F9ScEO8u5-^$W=9)Jr!;X?l~?bD*6l$BAF5WJ5O- z(}}&^BxN2T$7Hpur^w#pJBW8-<&Nj6j6ZC7oRyiKpTYd!@odxrZvAccIQ{*pc79y# zd5GpwT{=i$sN11_+9?6@!JbH=TUu#DXBGWDO0fPhQjXqH{VOMwoz(~i0SSa5M+EZT zm{d7wuaOBN^(~3`Wz(nG#?@aNIzMdx?TNv$i`PvZ4`K>Grp{2a7gV429K98bNn_o2 zp@gyN3~|Acy~ZpTfid>_z~+IIda?hVHw=wtPM-H9a~~)9+H|vMIln!%7zr`Zg4jwA z)5M<|^57C)!B&cWlsVIf=N`2{o|jpDyPBmgXlMqdTU)CruM!;macs`P0)={bv1h%i>1h zQ=)+xFx%IMt>LVp{Nd+d>bX^(1bx@M55n`y8&5UYC)8Vmeg96xb3G#%cdxkQY5?Jv z)so%!8wJMIxq=a`RHCjmQ*?Ol>!P>LA6q_jdt97lq_P`qK;dIgTzNXvXg7C7LC3X= zZwOofu8fCIQqEY>w`cxGpw?t#JR25ZvtvMMP{G|!va_aX8?+?89@fjAX>&DqXD)fa z_oKSOI5t26yfQ4aT67=|IW0asK3wW8_xBO|^^O8lAm{`-ygOgJYEYM8MnVRgS=Nzc zk-=@69jouYT*2885qdhV1QsnH$#mBoppgpY9W0_E?ITgXG)AbH&2AB2U z-lyyF#8W_F=pMP9W>Mgct!iaA?5J|l{CO)+(*F?#8dTjes59yEI{mb*z?-8K%*}tZ z6@7?vzVURhF+QFkwu7>~9u0Xl{Cf*dt?_Duz_pg6J*Rhb zii0zRJ(Phwit=)~?ja?ck4Zi8@ABBo+Q*M0QZt%{4LF+$NqSly zoPNKOm4sgy6Hh)vhf9dxnf-=MLOZhDkm_mr7oFESFXoIvr<&!#553VoWfBjNcY%RZ zwEgJOtB0nc*1z%JFyh){AsNe_;kF_`gPa-Ssj#P7lmGXXX6DO z)N2JQ4EU`s>k7hyT2cme`1NGwh~tY3&ttjE&U$7krM`jiOpBk1Ww^?el$4fg1*x3b zbg7dX6mM?8larH4*o0LR=Qlmq6`xS8zjWMDTB$peCvBb?Mv-t%;^j*O9qm@m*+qS& zeDh$oviwy;rrZ?lNwb|tC9a-QAe5RM2*G^H6JPV0_t38vbMy9W$C6dh`DQukT zk_E|=Lrg}EBWps$A}|4SpD?K-J-YXA>xcZ6)>L1kTd1rkt7x>3^YWj^59U0T%(HDe zV^k8~;LE3)*hRwPBrbF98Dt{M0Dq2C95%O7FDoIDPzQIbM%(?#%nOQsM7Q(Ipf~NE z4#SfnY~Y}R-ppUWh?0k?HGG8uOLa5)u^&Ds`=_d^g8? ze#?~l{vM-d4Cnt7w&QPMhjS{;C5;dpJCk~$#$36wymy1N;7pYW?V_}yxofeVEwlh- z#SYK*)~y$)1d5eK1YlFrF)c*FUwRW06NW-)d|BU$2e@;Sk!$lapqT-TK}A%Og4|z0 zrA(9D;v!!9f_J?K*>ws}vLjARh#Q$i_4$PwetmD=hekKD8gf+-N*gx)trGyeapY6< zQT&CwAl|7-9@*d3(Tbc_LCriYo?2MQ`6U<#X1Dt=^D1Ne@cV_Yy9icFZciLR1qlAj z)lM<~bsVr(J|{`uI&Ht1Cq^B!fHHyY%NG?BzSk$iq$hd!*X6_4xlJ=_nFrR-Z3ow$ zZ=a-|E4S>$;&=;h&&50|>@cjNmFMSmcHC{KM=ubnxw=w+(TJQ{u*{~-HOh*cnup&5sus1?lbnNb@7s#AFuVnE%EUXTye9kYLwo#>iKoHb5}vzWOUD+T8z8%0Mw5 z_ydu1;t`d8%(qd*34Zg!w zY582vZnlFgB0YV0xn?051-WQByFvPBrSrH0I`ZrwC{o^334H1(&AKt(!!c|o-oR|9k8J~+$X z(k9V|t}zzxltzp@l?`W0W1dP(P_)+elx$ea9$MWzSc0^LdhDY_ zGnZ+rD@G5yMRfc^Dv#DEBq1U1k79nq6tfSJVH5B^!&Hzn|A}l>@!4*Oubar+TLdpT z2^SWQ2C><(E+M#pvalABs0IX$Uhf_p*r;5vA>D&)csT=%Wu!IHp9)(``iqhVdB2!; zw0a?hZ*i{D(P}bfXirn^G|2rSzP968NgCqgwVCNnNKlw_|K#q9kV8dDN<~GgKk+cI zDJLc(AjHirBo;>;^^-kTjfqFV^#moD`|=6=m`^kUsonU#HB^o*WJF zE>f{;I)o1RiXC5+GEWvrhzsTDi&dGCNZWb)p5diK|O5^rL*`BBi~l? zNtEwbj5-ak^ZR!yTq`*k6sAMo#B`S=<&(WMOq$UOW;gU(=vkQvi;%OpTjAJ@?PqVUjaS_LI*d#;gkgx_g^Tt1d4_i z8=CCw$VDQMQZ~yFpfagSit&r!hA+%V%tw4=M6Zj@N=t+IX4}zQ+E$?9orZW3LoD6RN|k z`C?1CShaGUh8fFrz$gFL=gE_agH{a(OVd3)_Eoixc1vk04CUnn4I?AIE2`t_(wvyl zVVw5QTS?yp3u1Mta|H2RXX*!RAHy;_PaF`Jwc$@vpD;&7s(uM?$whRD|3(pi1!y^lXR z9B5`_=g(+Qbb0aeNT`bv2wG&$Wfbt|29`jeB^$a7Y5QwH!$@rG$VWQ7Pn5UG%y|kGu`^rR?h|Pgs?6O zcejM-5j0(W$m8w+6jl40qrxd5(5%NyF;@4YNDjSY_s$Pu#Z;g`&{yZ%WhNsOYkhcg zAIJdNN*O9|vVTOdQ3=&mwzCCUlsBM#Bf&@Ni|4)A4w6^VQvpux-#o;4D(i{F>)^?$ zGep*E2Oq>W8al-%F(;Mao;h8WO{+4eB0DXFdf+k^$hPlQ7l%5-8IZ*H2Sc)z_GzqEz=L*>l_GMUv2OmL*d6)TI;wWTk4DF`a* zjm%hQqb;iaQwcvv8NEPcg$bMMC=#4jkfgDV_4R~y#!;jM*MqiTqT&_CIS5jQu9wJy ziNd$bt<+9EK&~)2hmVdNbO1vzE|Ck0p(~gy78B!@rjjF+?NU+S;fyRsAdT^iLz3~7 z@C76veNE6%V@M~`h4{WLz)wP|QX(U8voI1R4L^F1o*?(fhK;`zYHxJD%w(5+9{G|3t3RKXzj>P{2HNP zx#6z2w3STZN);ugxENLg+NxIR(kwQ-vg29q3GE8_w>jC=_)KT7BGc#)^BG4gP}pFp z%>E>`(;=Xx)cjoerygTU-31P0RmV@l1ybiBm>?t(Rm?F}mJ(~(>Ra5BUH}9Ko1WB= zTHAQsA`>;Nf=xu7npE`lvHWwC>2-?0(TYsw5_3CIv655}Vs8Rg%(ZspPfAi#_X2bo zTA3fj?l>aa>MLOq3o%`CZMxc_4m1(!TwC>#`jx@*w%D}_lw5QxM8y`Rm5U`+Wzj;^ zRyK_);h68;2nOnOO}{y%P<&%B@>KdzQi1j+PAQgK*)yU-U*)_~P5j#tk%YGT5NAIS{A z^5<9~Z%OIF-)g@tejmXj&+k5vAfdbPx!L!XBwG&Y#7EUWZj901D#%2FDZ}G5E zTX2*5%7E+JNtMW|w;JPm@H;^7L;9B%oM4rx>*@NVh9a{MUTS}%uQgjVibjce?42}i zHO95P;({0U{lp%WDzDw?W)zaXf$s4m2X%u*laROzXg`sk_bJ8uc8u%+%Q7ID#&Iyy+Jks(G4(tU2m zesDi3zpAx=Q1?fj|M>ndjsL55z5P-PlD9Xnh55hodws9PN^1ZH z#J00K1ic@(2k_2Q>77n|I{aqq^;Aks9uG@O*Da z_u1*g5fTZ?+|Q*AbsM4Odc$>IvD-7G$lb&aKZbBm12v{Rnps{IyqONfqBJ6vqIM0R z$WN_+S503uVw`Whj#8wS2C=>bciHGXvhQI~ zXYny0iBnjSW-9ExyWdn_3$(->S$uv5q%za+wOG%=3tP`j(}Q>BWGN-Z@=uMNm~eFO zE!Vyl3H8ZLe$iHqr|78P8`E62@{}|xdsNS~>)j5zNUj-{E$f4Ql*}fU{?esTt84lx z@k-D4Y>iamLBiFOrt?o9yL0}IYmgg7QhMkQzZDkNRTQ>@x9eb_?O?h!Ms#@|n@&6r%?~<$c0cKY?-09M zAMeb?eSdTS&l7KS&Rj8nO*Xr9x!Mf1wTzsK!B_s|+^B0Hzuo`iII(U}Rv7Tu)|!9p z0u)-wQoA*A1dDmpGkXRvE-6k|u-Wf5t{@tjnP7OHQo$qCTT&=HUN7>6^u+w-l~Syo!_yB8YG2#>_Ll8 za$`+SJ(vUpnG3tgwDY%en92zrJvu(`R2w4#F5mxNS5K{!;*+LO_GIkHSkUS6Y0ond zWbU7=yW$#x_sRiBv5No@7`BC$#y@DMma86#am+Q84>k(ZmBd)_SACaB4} zzVAbM2;ga0A^mK&9-(f~;UZE1GW3``s=QrrZf$H^?wVO&n=ke3K-(WaZ!<8*=2vf_ z`#zA`>hs;AclpGu%ip17H4#&db;?vmE{;6sH<5#bIC$}S?QcNp7?+u>&4l2^?0o#< zTf3`;;qw}e8gF)TyOr|Xb&XchHeo#NTo<;~oHC}?;s|GFPf^;q1|cRW|n1dpeLj^*X~`G>Qe=?MF^b>PoqG~%YLsqbfk*!!a=pE_my z(1BEYVL4d9875d+@s_;V?${_finLD(KC&l~Ru7^89)Vco(F9~EQgDo2uTO`wy=4e! z!YGzeJVp{RP9k?76Df@fKTZV<(PW~9SA@Q*TjqZw#=RY; zRStKIybbQcXopiBZ`;2w6;s=<&3a63W4dI84l}j<@Z+G21fH0vII;*sGm?R|dk1+` zp6KbzcIkr-(Mrn*JJJZl!aic)sN7b(xot6Iu$I@aSz-x41QaipCx|Hn_+cX}Plwd2 zQ?UW-jT8563bc+Ci!^;PlQFBXt|~nK(+5sRo-ne1+3WuSP6z&9YU_Emq1y&IEP-g% zYK^}}8Ju%e`u}BfKo^$-H_D8bb~yJKMtHWUc`YjK94N%=t20 zKyFsU8w-2=7b~1wV_c6;Cgv3UW2W}b3nt&Rs%QDqW%Yp*v-o}cjWh1eq@?vfpe%83 zTwZ*_l}q4(PQtu5z?mk*W5BlVZ_a3q+4pjrsK;c9`~DvsTa{3Xuw2CTHRfe89y_~? z5hu~JHHcM)@-8up3rU2VRUJN_jMMYGMCUH*pN8+#j!^anOc>d z%DuoVuB=$F3SjJS{mPDA?363&0~@>clDe>>R8fecd*Z=v7oWi=^W5)z*CWj2LeLuu zSi4CMl@q$hqU_pD^zY+S3U~{}lm3~1iX-(Vq<*hX&|}fSeKGI%vBX(>RtYd{$3@s@ zeWb<56`FXz1uuSUPcR;2nl8Uw>Li5p6#Q+71 z?b4`jZ8Rg?M7Srzz~k?yx^YeE+~18273!3K6l$IZ!*;COSiT4$WgQ_YML_J6DKYq2 z_8P2(r?hJeWsTXJZ>)jtx3A{=tcTrq`>&?199wWoXRp~3!7APQJBwO-)&|Qmz*643 z)RYkINeQg6#x2~;YHVqMMinAa_aKzb0Pcs83@60EdA7w!erAkEg?JF*ZN(GeZEMHJ z^iH*j{J!ir$w}rm-y97bxEC|nqt_yTX=bO~Z6#!alGv~Cj+w7k}2$SsH0q?3+K5qq}bo_u61=V;~H7wFZ(jQ zqNTFley(CgkvB}2_uCqclKo~!dO#8Jjyp3IbeXPsbv*5_8gI3( zKD%EK`xF4sha?BLi;ONbE1~5dU^*=DN`mLNu>gkwNyhCW^b5^UXt@MThYDUfF|`TI ztL!Gn?u9<&L@#IWvN_m&ys(W%mZ&ZW)EB)lAzCbuKV@L=5(AT$@8y9VT*ld2PiB8@ zETT(#k0oA>0~ZY64AGLDt69A|#V-xmQZ16z>rlJ9UBs6BK_avTG_u1hWt*$_&N;iT zp$vA)!itun7CSN~SM_=^azvY~7Dt_`e6vqh!+~|}w8zeWlFM2fd!0g(QgSDAv38qp zR4ka-z}wX}(vi!}wgbj$n^aVycF#_9dJoSE1JvC{UhqLNUzGD-CiQen5ZWM4|)$@jlkLL21BoM3L-1!(+5sCB1qwp zA{dqm=8_oNYyf>bT9Rm~DPG;RotT&4bbm_6il&+5eYqzeeqJ7)jDO|^*9<2Oq04;1 zN@0m_WZ|+{c11n$A-x9vL4^t)OrP_<1s-TaJ23Z7<3;1O9x#m9Jcumex6&WG8iwmS z$Mn7_BvDrj>Jx^$)CI8(7RYnEa2X%i*-p}>CxIP`)ZM_g*#Xp3`8(D^NVgrB#|xeJ zKBPTwzb~-o)~pzJrB=R#uwzerfOVSRa@@1kK~~d6=~|-ZAjEtUs+R|7Q-fvJldz`& z9g>R}C0=$iJ5sV9gJIDwxfg1+CA?&SOW;_~Gba(PIK~Y6zlft91D_Sb=q(c81as>h z1`)s{VhK%hz=93=?0k2KN-~pi7rYE zwBUH0E_$2_x}FNUpVFHI?4k6yqF%caoN|LLN|1YRh%lo$L^?Aar!Y8rTK17Q##{E^ zj32etrpD#bzhqRd*%vSChZH)*Noy1RMdp~}Pg0}-SR2B-$rfv-DveF+OEjM=u`NmM zRpZ_h;%9>5VP8QnrvS~z87`ce0EdSKT686#klYnE6Bm!)Lk2D75Qtq3E`_Hyld+NU zZD-Mkig1qh_PA@Xr^y`M>bQXE#{0@+%cVKA=xKX8kRADvpY~Q&X7Ja|Qu=e;Ye?gS zo8VZJ`Rm5dxF=ZwqjomVD3y&(H=m-mxwTpNpbB|$C%il~9VnJVQzp<;%-ba<-Y3u5*`-4o&YjTE7=u-WnR`d3t=FXtAX$7UTK)R#$NQJi zNQ?2K{bI*A$%f(qA}>GR5n_l9Ub+zf^Y~Ck0ymZ4WQ_uJ`0mv`Q|yE+a^Ekg1rw&Z ze7ZbYS0G5cdC`S2XU!waKDIHccV3)*SZ1HoVljDXKR>&9+@0F(XYI(Wc2|juW1B z{dKJD@(>-_LZw||2F;tCdBVH=1fG9n>6VbTrSp8J_5H<F|b;4{{C+-@hr24_0K4U zP!{Ah`HH>!CTB+QBsb8Ex3V6))9Q8oJX+@%(uN*0q!F#tgc*_`EfgU=j}TbP`r8!R z4t6dkkGN=?CbLl4mG3t4>!-Q4H82d2r#?^3B9xQb#8@kfw;T`F!gKA4Otrg7!>2XJUyjU7fkCX$?QlCx89Wqi zM93#Z`^@@B`n;=ryPJI}yqg5+gR#0knkqD~#cQw@Gvq5;XR{OpKM-Zff3?~y>Yvew z&9XJ-gDdih4Mt@`k6S8QN*we0?D=B|`t|+++e+tnD^@q*DV6I4wOOK2x{}*8cdR4Xa)rww%(wD z0*=7e+)mdhw*B>tJ!>WunAkj`a< z+`ogo_iKLo0PMHgFJW@gXl;cH%W-%qOCY}R!UcXCzY>=9@l`@=yC|A$`x!BR882(R$2+2m|NG1;|(mJobG>@#iffhr0*@Y)Z6UAC5m zIi5)#G$LhrKP@(1<&y&fW8y^OWcOWhk^U4VYJBnXJ#kpM%Q{QKK7y_SyfGH`!s8-Y z)NouB?RM$;ATz<3M}B1Y(1zpnj61C|2-Uq&HStR_Op%_>p03CpI8pYpKI=FyHk;?# z@E@YF8KQBBmevLe9UYFbFJ;M3XDN*bx5*)RLSb3jHMX0}Sq2d#zPGl-$E3cue@Vz+ zT3a$BT<({$Rtzq`3D+e?`b+f*4~6zg!wQS27+o8-;+U5dFiY^$83gG}RXzJtj`P1adjqWJn*7$LhPdaDVjUhW?4 zsX78aqN*1B=XOX)ZU^<=N>6DtKL3qzjHFQ(KvPT-opm7t_VU}nvG%ira`4Ro?&)$p zsmTnsHh9f6U+ShklBh!3KxiN`)0~skQ~bzWeDtMwwL*@gs0^F=HD)0o;dr01iF-5g zL?^>#5<#T1_u;-eQyL?%B{KP_h+hJ+&OW+=`mCZxwt1h1%`PNkul{4;Aekp^V0sn| z^DP^}%>)JQ0~?G3@LRkK`)U3;d4wkR#N;Z=7EC3z(pR!aBE1GU~Y^hOU#=tV>5DAcP>l||ZTpr6x`E4SbL zU`6+)fj4vKTaNrh3VL@!lRJ)8*#h6G)6lOCOp0Q3#nI&ios)7ZFNM7My3TfB6|?!3 zOXd9u=86%y49td~<_=%-o13rWm>}{6Q$6QQHaS!dW$4U6@){eRfjE&eed5qtIQXj; z0L>PLW>;Y;)5m|0-ka`q4L|*fdBx`gDw=}fXl7iS>vG_hYT#gg){G`a@@a+1zokRw z4mUX}4%mE_1!KxKx%9)&X$499Fc2{nEdF{1+Z>)dhC7Pwa2GXAaNrnZaq&oT1sSk6 z?e399qvj@B@}mVIr>5y==Nwv;r&HLNN9rc0klG7L0TH zM0-dC4P&qAI!e$7NzU%KI-(q;?dJqoh}M^0<&bSS+DckdVoEP$y?wmXkum!H{GR_& z!4Y`|J!CV#C}my#7je3+C_;M{d?m^?^T*UQ#hEBN`2E|dCXUmxcdF0t4+~rT7|A47 z9V#-Rm^KG&GSkYNbgryhH$ZM5qINDg!n}z)NGYmeNc^Yf^Tp})G3tx zQodKJ(L@|Edd=~#BamaYms>4gn_S@gU2k2aq#0_zuoyNNF8XpK;n5IlMHHC)ToKB@ zi(9R%eG;btZ7!-1a30MWKXk%d+LcF81T4EoEcMqEN7Nloei@&SWe`?GC2s#=Dk5RP zD2Yp|VU@(;mO7e+1K^&e2-4XzQx*pHMzujTuME>d#uA{w>+0t$PCrvR&{7cACqh#DV=HYFxo+Li zD-SUWT`5bdSzl!byDUuW;xp}5SJ~J6#+Rq+Xuk$3+byT#Wg!epAB}jv0nsN)T2B+g zR{}hN7k!x?)0FWPr+OyN`cVPzaPKjPxMiP&jMw@COc;iz@6f2V$}o+P z5*89``!j1Eh>KSJXL34afq5fQaagL6;PK}`LInBQb>BPl>@)TqluIGPoAaR|%3+#m z!u+k5e0#%0-x=fiY&zQEr<}@lSmK`jF6jc!RQZ|}Fzd~_AB__9S|_7?8;*F2-!FB! ztljcf0ijy>qez&)@Pp02bm&91-0vE_U$lf4GS9mUZkjAtNgw@}=*XUIRFC2P`Ut1h zji*Xkt>zqC1}w#?sE{y4G}x2&upTl<@u&1;jHx$4`YBPt2b!3pC7R+4eu*aC1Zklx z#|9ItLdC&ZHn#?h+O%=qd236@se7a+Ghzo1CE^9iE~NVGY&0pc9E5GcY+sJ2 zthu$(0;+B{caZfO%q@PW)(unckraX9X3(p{tXOR7BO6)&(2U2Wu>+n2u)pAXA$aVT z!<(#`3!y4@W<2k=l~k}Q?I)igS13MS1RI|;#CDFd!*Su#=b(fs6$hl1_&%Q;>CV0_ zw3{PA_HxEuPJjjTOmMnYR=YRV0_x(xuC7cjUbb#^mqrLd3D~KGAr$X+Z#wMRP^!ZB zNaNY-?w|6f7^++9g?mQdK@v(MIYo#OmMlw8rz7g>!SisjidSNj6~}a_-%S7Hd{D1y zByDe@uIrLk@bTeIos`6U8)}dLYZxX&AAazN$yn8hQe^G#UfQ=`K_U~eFezIls-f0= zRgyd}(9AT8#Euo#uL@2B_(?QkM$;uAcxGWEhV_9=~(K7p*I)qwfb0q2E1<=rK1WZ{Ds`r7|+4El%T17pgI zbc8};v1}jj%hgFNkMjSUNXZ3MtC?ucADd-)u@vxa(t!ZQCbQzj4!r%K{gHWH?O^JI zx~~3;?=Lj=VjXIZoS)xbrq<5>kJE;9HXYf zlP$yr*7Pn$x>d>#zLX`lvEg%n7#)TL9Grx04OThyJiyJ+GnZE4BJ6weIh(2^pGqC)-)QGOEG65^)NU1DD4e)G%iEm)_q6qIkkk_qm zN}I#+lJn-r&ZTZt1LOD3Z)>htmko~G`rqyR@9W+)#j z8E_exCjR{LcEv<+B3wExt%70wG3fEcE8$<0zIyFA7H`@rqZVFv&W;6#DmDyYGK8&n zg04mqhHZLTkw&{Ws|@BhZJ8f;<%6eK*!-gKm&_FLD6ngaHY>~W;(Immc3xm>xvsc1 zmz+<4!drgWoR*9mahHu(nd3_G57*=0TR3tWK;&p^VbD|#d*U*%p#8X9K({i?B!=a>l@%oOLSEM|fL@6!^1r}IFH#xbAQe{@}Ey7-(v@;AmAK-4t# z@uoN#sT1Ia3a|K>zIOFq}AJ*pLC}0rO1SBhUoIBNAGB zthyKqKz1`@#n4))$d+*4s(GOoZ>0!`^Yaz+%x1%# sl9A&C*r;1yEP7+A7jTVP5=M^ literal 0 HcmV?d00001 diff --git a/demos/her11_veraedit.fur b/demos/her11_veraedit.fur new file mode 100644 index 0000000000000000000000000000000000000000..1cfabac199e79e68e92f72f3e175116dfc6c582e GIT binary patch literal 8604 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 Date: Fri, 9 Sep 2022 20:28:27 -0700 Subject: [PATCH 52/93] Demo song request (Equinox Intro) --- demos/GEN Equinox Intro.fur | Bin 0 -> 51233 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/GEN Equinox Intro.fur diff --git a/demos/GEN Equinox Intro.fur b/demos/GEN Equinox Intro.fur new file mode 100644 index 0000000000000000000000000000000000000000..04a5bb051a29944822656c90aabe18dc44b29caf GIT binary patch literal 51233 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 literal 0 HcmV?d00001 From fcefbb490916f5b5dda36a545d685fa3e36c7f07 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 23:21:45 -0500 Subject: [PATCH 53/93] TIA: update to latest Stella core --- CMakeLists.txt | 3 +- src/engine/platform/sound/tia/Audio.cpp | 137 +++++++ src/engine/platform/sound/tia/Audio.h | 71 ++++ .../platform/sound/tia/AudioChannel.cpp | 140 +++++++ src/engine/platform/sound/tia/AudioChannel.h | 61 +++ src/engine/platform/sound/tia/TIASnd.cpp | 377 ------------------ src/engine/platform/sound/tia/TIASnd.h | 186 --------- src/engine/platform/sound/tia/TIATables.h | 219 ---------- src/engine/platform/tia.cpp | 14 +- src/engine/platform/tia.h | 4 +- 10 files changed, 421 insertions(+), 791 deletions(-) create mode 100644 src/engine/platform/sound/tia/Audio.cpp create mode 100644 src/engine/platform/sound/tia/Audio.h create mode 100644 src/engine/platform/sound/tia/AudioChannel.cpp create mode 100644 src/engine/platform/sound/tia/AudioChannel.h delete mode 100644 src/engine/platform/sound/tia/TIASnd.cpp delete mode 100644 src/engine/platform/sound/tia/TIASnd.h delete mode 100644 src/engine/platform/sound/tia/TIATables.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ff7db42e..6e213dcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,7 +386,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp src/engine/platform/sound/c64_fp/WaveformGenerator.cpp src/engine/platform/sound/c64_fp/resample/SincResampler.cpp -src/engine/platform/sound/tia/TIASnd.cpp +src/engine/platform/sound/tia/AudioChannel.cpp +src/engine/platform/sound/tia/Audio.cpp src/engine/platform/sound/ymfm/ymfm_adpcm.cpp src/engine/platform/sound/ymfm/ymfm_opm.cpp diff --git a/src/engine/platform/sound/tia/Audio.cpp b/src/engine/platform/sound/tia/Audio.cpp new file mode 100644 index 00000000..a90aee5a --- /dev/null +++ b/src/engine/platform/sound/tia/Audio.cpp @@ -0,0 +1,137 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#define _USE_MATH_DEFINES +#include "Audio.h" + +#include + +namespace { + constexpr double R_MAX = 30.; + constexpr double R = 1.; + + short mixingTableEntry(unsigned char v, unsigned char vMax) + { + return static_cast( + floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))) + ); + } +} + +namespace TIA { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Audio::Audio() +{ + for (unsigned char i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e); + for (unsigned char i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); + + reset(false); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::reset(bool st) +{ + myCounter = 0; + mySampleIndex = 0; + stereo = st; + + myCurrentSample[0]=0; + myCurrentSample[1]=0; + + myChannel0.reset(); + myChannel1.reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::tick() +{ + switch (myCounter) { + case 9: + case 81: + myChannel0.phase0(); + myChannel1.phase0(); + + break; + + case 37: + case 149: + phase1(); + break; + } + + if (++myCounter == 228) myCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::write(unsigned char addr, unsigned char val) { + switch (addr&0x3f) { + case 0x15: + myChannel0.audc(val); + break; + case 0x16: + myChannel1.audc(val); + break; + case 0x17: + myChannel0.audf(val); + break; + case 0x18: + myChannel1.audf(val); + break; + case 0x19: + myChannel0.audv(val); + break; + case 0x1a: + myChannel1.audv(val); + break; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::phase1() +{ + unsigned char sample0 = myChannel0.phase1(); + unsigned char sample1 = myChannel1.phase1(); + + addSample(sample0, sample1); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::addSample(unsigned char sample0, unsigned char sample1) +{ + if(stereo) { + myCurrentSample[0] = myMixingTableIndividual[sample0]; + myCurrentSample[1] = myMixingTableIndividual[sample1]; + } + else { + myCurrentSample[0] = myMixingTableSum[sample0 + sample1]; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel0() +{ + return myChannel0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel1() +{ + return myChannel1; +} + +} \ No newline at end of file diff --git a/src/engine/platform/sound/tia/Audio.h b/src/engine/platform/sound/tia/Audio.h new file mode 100644 index 00000000..ff388807 --- /dev/null +++ b/src/engine/platform/sound/tia/Audio.h @@ -0,0 +1,71 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_HXX +#define TIA_AUDIO_HXX + +#include "AudioChannel.h" +#include + +namespace TIA { + class Audio + { + public: + Audio(); + + void reset(bool stereo); + + void tick(); + + void write(unsigned char addr, unsigned char val); + + AudioChannel& channel0(); + + AudioChannel& channel1(); + + short myCurrentSample[2]; + + private: + void phase1(); + void addSample(unsigned char sample0, unsigned char sample1); + + private: + unsigned char myCounter{0}; + + AudioChannel myChannel0; + AudioChannel myChannel1; + + bool stereo; + + std::array myMixingTableSum; + std::array myMixingTableIndividual; + + unsigned int mySampleIndex{0}; + #ifdef GUI_SUPPORT + bool myRewindMode{false}; + mutable ByteArray mySamples; + #endif + + private: + Audio(const Audio&) = delete; + Audio(Audio&&) = delete; + Audio& operator=(const Audio&) = delete; + Audio& operator=(Audio&&) = delete; + }; +} + +#endif // TIA_AUDIO_HXX diff --git a/src/engine/platform/sound/tia/AudioChannel.cpp b/src/engine/platform/sound/tia/AudioChannel.cpp new file mode 100644 index 00000000..c780c172 --- /dev/null +++ b/src/engine/platform/sound/tia/AudioChannel.cpp @@ -0,0 +1,140 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "AudioChannel.h" + +namespace TIA { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::reset() +{ + myAudc = myAudv = myAudf = 0; + myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false; + myDivCounter = myPulseCounter = myNoiseCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::phase0() +{ + if (myClockEnable) { + myNoiseCounterBit4 = myNoiseCounter & 0x01; + + switch (myAudc & 0x03) { + case 0x00: + case 0x01: + myPulseCounterHold = false; + break; + + case 0x02: + myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02; + break; + + case 0x03: + myPulseCounterHold = !myNoiseCounterBit4; + break; + } + + switch (myAudc & 0x03) { + case 0x00: + myNoiseFeedback = + ((myPulseCounter ^ myNoiseCounter) & 0x01) || + !(myNoiseCounter || (myPulseCounter != 0x0a)) || + !(myAudc & 0x0c); + + break; + + default: + myNoiseFeedback = + (((myNoiseCounter & 0x04) ? 1 : 0) ^ (myNoiseCounter & 0x01)) || + myNoiseCounter == 0; + + break; + } + } + + myClockEnable = myDivCounter == myAudf; + + if (myDivCounter == myAudf || myDivCounter == 0x1f) { + myDivCounter = 0; + } else { + ++myDivCounter; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +unsigned char AudioChannel::phase1() +{ + if (myClockEnable) { + bool pulseFeedback = false; + switch (myAudc >> 2) { + case 0x00: + pulseFeedback = + (((myPulseCounter & 0x02) ? 1 : 0) ^ (myPulseCounter & 0x01)) && + (myPulseCounter != 0x0a) && + (myAudc & 0x03); + + break; + + case 0x01: + pulseFeedback = !(myPulseCounter & 0x08); + break; + + case 0x02: + pulseFeedback = !myNoiseCounterBit4; + break; + + case 0x03: + pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e)); + break; + } + + myNoiseCounter >>= 1; + if (myNoiseFeedback) { + myNoiseCounter |= 0x10; + } + + if (!myPulseCounterHold) { + myPulseCounter = ~(myPulseCounter >> 1) & 0x07; + + if (pulseFeedback) { + myPulseCounter |= 0x08; + } + } + } + + return (myPulseCounter & 0x01) * myAudv; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audc(unsigned char value) +{ + myAudc = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audv(unsigned char value) +{ + myAudv = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audf(unsigned char value) +{ + myAudf = value & 0x1f; +} + +} \ No newline at end of file diff --git a/src/engine/platform/sound/tia/AudioChannel.h b/src/engine/platform/sound/tia/AudioChannel.h new file mode 100644 index 00000000..942e101a --- /dev/null +++ b/src/engine/platform/sound/tia/AudioChannel.h @@ -0,0 +1,61 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_CHANNEL_HXX +#define TIA_AUDIO_CHANNEL_HXX + +namespace TIA { + class AudioChannel + { + public: + AudioChannel() = default; + + void reset(); + + void phase0(); + + unsigned char phase1(); + + void audc(unsigned char value); + + void audf(unsigned char value); + + void audv(unsigned char value); + + private: + unsigned char myAudc{0}; + unsigned char myAudv{0}; + unsigned char myAudf{0}; + + bool myClockEnable{false}; + bool myNoiseFeedback{false}; + bool myNoiseCounterBit4{false}; + bool myPulseCounterHold{false}; + + unsigned char myDivCounter{0}; + unsigned char myPulseCounter{0}; + unsigned char myNoiseCounter{0}; + + private: + AudioChannel(const AudioChannel&) = delete; + AudioChannel(AudioChannel&&) = delete; + AudioChannel& operator=(const AudioChannel&) = delete; + AudioChannel& operator=(AudioChannel&&) = delete; + }; +} + +#endif // TIA_AUDIO_CHANNEL_HXX diff --git a/src/engine/platform/sound/tia/TIASnd.cpp b/src/engine/platform/sound/tia/TIASnd.cpp deleted file mode 100644 index e2d0568c..00000000 --- a/src/engine/platform/sound/tia/TIASnd.cpp +++ /dev/null @@ -1,377 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#include "TIATables.h" -#include "TIASnd.h" - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TIASound::TIASound(int outputFrequency) - : myChannelMode(Hardware2Stereo), - myOutputFrequency(outputFrequency), - myVolumePercentage(100) -{ - reset(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::reset() -{ - // Fill the polynomials - polyInit(Bit4, 4, 4, 3); - polyInit(Bit5, 5, 5, 3); - polyInit(Bit9, 9, 9, 5); - - // Initialize instance variables - for(int chan = 0; chan <= 1; ++chan) - { - myVolume[chan] = 0; - myDivNCnt[chan] = 0; - myDivNMax[chan] = 0; - myDiv3Cnt[chan] = 3; - myAUDC[chan] = 0; - myAUDF[chan] = 0; - myAUDV[chan] = 0; - myP4[chan] = 0; - myP5[chan] = 0; - myP9[chan] = 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::outputFrequency(int freq) -{ - myOutputFrequency = freq; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::string TIASound::channels(unsigned int hardware, bool stereo) -{ - if(hardware == 1) - myChannelMode = Hardware1; - else - myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono; - - switch(myChannelMode) - { - case Hardware1: return "Hardware1"; - case Hardware2Mono: return "Hardware2Mono"; - case Hardware2Stereo: return "Hardware2Stereo"; - default: return ""; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::set(unsigned short address, unsigned char value) -{ - int chan = ~address & 0x1; - switch(address) - { - case TIARegister::AUDC0: - case TIARegister::AUDC1: - myAUDC[chan] = value & 0x0f; - break; - - case TIARegister::AUDF0: - case TIARegister::AUDF1: - myAUDF[chan] = value & 0x1f; - break; - - case TIARegister::AUDV0: - case TIARegister::AUDV1: - myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT; - break; - - default: - return; - } - - unsigned short newVal = 0; - - // An AUDC value of 0 is a special case - if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5) - { - // Indicate the clock is zero so no processing will occur, - // and set the output to the selected volume - newVal = 0; - myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100; - } - else - { - // Otherwise calculate the 'divide by N' value - newVal = myAUDF[chan] + 1; - - // If bits 2 & 3 are set, then multiply the 'div by n' count by 3 - if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3) - newVal *= 3; - } - - // Only reset those channels that have changed - if(newVal != myDivNMax[chan]) - { - // Reset the divide by n counters - myDivNMax[chan] = newVal; - - // If the channel is now volume only or was volume only, - // reset the counter (otherwise let it complete the previous) - if ((myDivNCnt[chan] == 0) || (newVal == 0)) - myDivNCnt[chan] = newVal; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -unsigned char TIASound::get(unsigned short address) const -{ - switch(address) - { - case TIARegister::AUDC0: return myAUDC[0]; - case TIARegister::AUDC1: return myAUDC[1]; - case TIARegister::AUDF0: return myAUDF[0]; - case TIARegister::AUDF1: return myAUDF[1]; - case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT; - case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT; - default: return 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::volume(unsigned int percent) -{ - if(percent <= 100) - myVolumePercentage = percent; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf) -{ - // Make temporary local copy - unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1]; - unsigned char p5_0 = myP5[0], p5_1 = myP5[1]; - unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1]; - short v0 = myVolume[0], v1 = myVolume[1]; - - // Take external volume into account - short audv0 = (myAUDV[0] * myVolumePercentage) / 100, - audv1 = (myAUDV[1] * myVolumePercentage) / 100; - - // Loop until the sample buffer is full - while(samples > 0) - { - // Process channel 0 - if (div_n_cnt0 > 1) - { - div_n_cnt0--; - } - else if (div_n_cnt0 == 1) - { - int prev_bit5 = Bit5[p5_0]; - div_n_cnt0 = myDivNMax[0]; - - // The P5 counter has multiple uses, so we increment it here - p5_0++; - if (p5_0 == POLY5_SIZE) - p5_0 = 0; - - // Check clock modifier for clock tick - if ((audc0 & 0x02) == 0 || - ((audc0 & 0x01) == 0 && Div31[p5_0]) || - ((audc0 & 0x01) == 1 && Bit5[p5_0]) || - ((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5)) - { - if (audc0 & 0x04) // Pure modified clock selected - { - if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode - { - if ( Bit5[p5_0] != prev_bit5 ) - { - myDiv3Cnt[0]--; - if ( !myDiv3Cnt[0] ) - { - myDiv3Cnt[0] = 3; - v0 = v0 ? 0 : audv0; - } - } - } - else - { - // If the output was set turn it off, else turn it on - v0 = v0 ? 0 : audv0; - } - } - else if (audc0 & 0x08) // Check for p5/p9 - { - if (audc0 == POLY9) // Check for poly9 - { - // Increase the poly9 counter - myP9[0]++; - if (myP9[0] == POLY9_SIZE) - myP9[0] = 0; - - v0 = Bit9[myP9[0]] ? audv0 : 0; - } - else if ( audc0 & 0x02 ) - { - v0 = (v0 || audc0 & 0x01) ? 0 : audv0; - } - else // Must be poly5 - { - v0 = Bit5[p5_0] ? audv0 : 0; - } - } - else // Poly4 is the only remaining option - { - // Increase the poly4 counter - myP4[0]++; - if (myP4[0] == POLY4_SIZE) - myP4[0] = 0; - - v0 = Bit4[myP4[0]] ? audv0 : 0; - } - } - } - - // Process channel 1 - if (div_n_cnt1 > 1) - { - div_n_cnt1--; - } - else if (div_n_cnt1 == 1) - { - int prev_bit5 = Bit5[p5_1]; - - div_n_cnt1 = myDivNMax[1]; - - // The P5 counter has multiple uses, so we increment it here - p5_1++; - if (p5_1 == POLY5_SIZE) - p5_1 = 0; - - // Check clock modifier for clock tick - if ((audc1 & 0x02) == 0 || - ((audc1 & 0x01) == 0 && Div31[p5_1]) || - ((audc1 & 0x01) == 1 && Bit5[p5_1]) || - ((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5)) - { - if (audc1 & 0x04) // Pure modified clock selected - { - if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode - { - if ( Bit5[p5_1] != prev_bit5 ) - { - myDiv3Cnt[1]--; - if ( ! myDiv3Cnt[1] ) - { - myDiv3Cnt[1] = 3; - v1 = v1 ? 0 : audv1; - } - } - } - else - { - // If the output was set turn it off, else turn it on - v1 = v1 ? 0 : audv1; - } - } - else if (audc1 & 0x08) // Check for p5/p9 - { - if (audc1 == POLY9) // Check for poly9 - { - // Increase the poly9 counter - myP9[1]++; - if (myP9[1] == POLY9_SIZE) - myP9[1] = 0; - - v1 = Bit9[myP9[1]] ? audv1 : 0; - } - else if ( audc1 & 0x02 ) - { - v1 = (v1 || audc1 & 0x01) ? 0 : audv1; - } - else // Must be poly5 - { - v1 = Bit5[p5_1] ? audv1 : 0; - } - } - else // Poly4 is the only remaining option - { - // Increase the poly4 counter - myP4[1]++; - if (myP4[1] == POLY4_SIZE) - myP4[1] = 0; - - v1 = Bit4[myP4[1]] ? audv1 : 0; - } - } - } - - short byte = v0 + v1; - switch(myChannelMode) - { - case Hardware2Mono: // mono sampling with 2 hardware channels - *(buffer++) = byte; - *(buffer++) = byte; - samples--; - break; - - case Hardware2Stereo: // stereo sampling with 2 hardware channels - *(buffer++) = v0; - *(buffer++) = v1; - samples--; - break; - - case Hardware1: // mono/stereo sampling with only 1 hardware channel - *(buffer++) = (v0 + v1) >> 1; - samples--; - break; - } - - if (oscBuf!=NULL) { - oscBuf[0]->data[oscBuf[0]->needle++]=v0; - oscBuf[1]->data[oscBuf[1]->needle++]=v1; - } - } - - // Save for next round - myP5[0] = p5_0; - myP5[1] = p5_1; - myVolume[0] = v0; - myVolume[1] = v1; - myDivNCnt[0] = div_n_cnt0; - myDivNCnt[1] = div_n_cnt1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1) -{ - int mask = (1 << size) - 1, x = mask; - - for(int i = 0; i < mask; i++) - { - int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01; - int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01; - poly[i] = x & 1; - // calculate next bit - x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) ); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const unsigned char TIASound::Div31[POLY5_SIZE] = { - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; diff --git a/src/engine/platform/sound/tia/TIASnd.h b/src/engine/platform/sound/tia/TIASnd.h deleted file mode 100644 index 78459426..00000000 --- a/src/engine/platform/sound/tia/TIASnd.h +++ /dev/null @@ -1,186 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#ifndef TIASOUND_HXX -#define TIASOUND_HXX - -#include -#include "../../../dispatch.h" - -/** - This class implements a fairly accurate emulation of the TIA sound - hardware. This class uses code/ideas from z26 and MESS. - - Currently, the sound generation routines work at 31400Hz only. - Resampling can be done by passing in a different output frequency. - - @author Bradford W. Mott, Stephen Anthony, z26 and MESS teams - @version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $ -*/ -class TIASound -{ - public: - /** - Create a new TIA Sound object using the specified output frequency - */ - TIASound(int outputFrequency = 31400); - - public: - /** - Reset the sound emulation to its power-on state - */ - void reset(); - - /** - Set the frequency output samples should be generated at - */ - void outputFrequency(int freq); - - /** - Selects the number of audio channels per sample. There are two factors - to consider: hardware capability and desired mixing. - - @param hardware The number of channels supported by the sound system - @param stereo Whether to output the internal sound signals into 1 - or 2 channels - - @return Status of the channel configuration used - */ - std::string channels(unsigned int hardware, bool stereo); - - public: - /** - Sets the specified sound register to the given value - - @param address Register address - @param value Value to store in the register - */ - void set(unsigned short address, unsigned char value); - - /** - Gets the specified sound register's value - - @param address Register address - */ - unsigned char get(unsigned short address) const; - - /** - Create sound samples based on the current sound register settings - in the specified buffer. NOTE: If channels is set to stereo then - the buffer will need to be twice as long as the number of samples. - - @param buffer The location to store generated samples - @param samples The number of samples to generate - */ - void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL); - - /** - Set the volume of the samples created (0-100) - */ - void volume(unsigned int percent); - - private: - void polyInit(unsigned char* poly, int size, int f0, int f1); - - private: - // Definitions for AUDCx (15, 16) - enum AUDCxRegister - { - SET_TO_1 = 0x00, // 0000 - POLY4 = 0x01, // 0001 - DIV31_POLY4 = 0x02, // 0010 - POLY5_POLY4 = 0x03, // 0011 - PURE1 = 0x04, // 0100 - PURE2 = 0x05, // 0101 - DIV31_PURE = 0x06, // 0110 - POLY5_2 = 0x07, // 0111 - POLY9 = 0x08, // 1000 - POLY5 = 0x09, // 1001 - DIV31_POLY5 = 0x0a, // 1010 - POLY5_POLY5 = 0x0b, // 1011 - DIV3_PURE = 0x0c, // 1100 - DIV3_PURE2 = 0x0d, // 1101 - DIV93_PURE = 0x0e, // 1110 - POLY5_DIV3 = 0x0f // 1111 - }; - - enum { - POLY4_SIZE = 0x000f, - POLY5_SIZE = 0x001f, - POLY9_SIZE = 0x01ff, - DIV3_MASK = 0x0c, - AUDV_SHIFT = 10 // shift 2 positions for AUDV, - // then another 8 for 16-bit sound - }; - - enum ChannelMode { - Hardware2Mono, // mono sampling with 2 hardware channels - Hardware2Stereo, // stereo sampling with 2 hardware channels - Hardware1 // mono/stereo sampling with only 1 hardware channel - }; - - private: - // Structures to hold the 6 tia sound control bytes - unsigned char myAUDC[2]; // AUDCx (15, 16) - unsigned char myAUDF[2]; // AUDFx (17, 18) - short myAUDV[2]; // AUDVx (19, 1A) - - short myVolume[2]; // Last output volume for each channel - - unsigned char myP4[2]; // Position pointer for the 4-bit POLY array - unsigned char myP5[2]; // Position pointer for the 5-bit POLY array - unsigned short myP9[2]; // Position pointer for the 9-bit POLY array - - unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel - unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel - unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode - - ChannelMode myChannelMode; - int myOutputFrequency; - unsigned int myVolumePercentage; - - /* - Initialize the bit patterns for the polynomials (at runtime). - - The 4bit and 5bit patterns are the identical ones used in the tia chip. - Though the patterns could be packed with 8 bits per byte, using only a - single bit per byte keeps the math simple, which is important for - efficient processing. - */ - unsigned char Bit4[POLY4_SIZE]; - unsigned char Bit5[POLY5_SIZE]; - unsigned char Bit9[POLY9_SIZE]; - - /* - The 'Div by 31' counter is treated as another polynomial because of - the way it operates. It does not have a 50% duty cycle, but instead - has a 13:18 ratio (of course, 13+18 = 31). This could also be - implemented by using counters. - */ - static const unsigned char Div31[POLY5_SIZE]; - - private: - // Following constructors and assignment operators not supported - TIASound(const TIASound&) = delete; - TIASound(TIASound&&) = delete; - TIASound& operator=(const TIASound&) = delete; - TIASound& operator=(TIASound&&) = delete; -}; - -#endif diff --git a/src/engine/platform/sound/tia/TIATables.h b/src/engine/platform/sound/tia/TIATables.h deleted file mode 100644 index 782a24de..00000000 --- a/src/engine/platform/sound/tia/TIATables.h +++ /dev/null @@ -1,219 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#ifndef TIA_TABLES_HXX -#define TIA_TABLES_HXX - -enum TIABit { - P0Bit = 0x01, // Bit for Player 0 - M0Bit = 0x02, // Bit for Missle 0 - P1Bit = 0x04, // Bit for Player 1 - M1Bit = 0x08, // Bit for Missle 1 - BLBit = 0x10, // Bit for Ball - PFBit = 0x20, // Bit for Playfield - ScoreBit = 0x40, // Bit for Playfield score mode - PriorityBit = 0x80 // Bit for Playfield priority -}; - -enum TIAColor { - BKColor = 0, // Color index for Background - PFColor = 1, // Color index for Playfield - P0Color = 2, // Color index for Player 0 - P1Color = 3, // Color index for Player 1 - M0Color = 4, // Color index for Missle 0 - M1Color = 5, // Color index for Missle 1 - BLColor = 6, // Color index for Ball - HBLANKColor = 7 // Color index for HMove blank area -}; - -enum CollisionBit -{ - Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision - Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision - Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision - Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision - Cx_P0PF = 1 << 4, // Player0 - Playfield collision - Cx_P0BL = 1 << 5, // Player0 - Ball collision - Cx_P1PF = 1 << 6, // Player1 - Playfield collision - Cx_P1BL = 1 << 7, // Player1 - Ball collision - Cx_M0PF = 1 << 8, // Missle0 - Playfield collision - Cx_M0BL = 1 << 9, // Missle0 - Ball collision - Cx_M1PF = 1 << 10, // Missle1 - Playfield collision - Cx_M1BL = 1 << 11, // Missle1 - Ball collision - Cx_BLPF = 1 << 12, // Ball - Playfield collision - Cx_P0P1 = 1 << 13, // Player0 - Player1 collision - Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision -}; - -// TIA Write/Read register names -enum TIARegister { - VSYNC = 0x00, // Write: vertical sync set-clear (D1) - VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1) - WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe) - RSYNC = 0x03, // Write: reset hrz. sync counter (strobe) - NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0) - NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0) - COLUP0 = 0x06, // Write: color-lum player 0 (D7-1) - COLUP1 = 0x07, // Write: color-lum player 1 (D7-1) - COLUPF = 0x08, // Write: color-lum playfield (D7-1) - COLUBK = 0x09, // Write: color-lum background (D7-1) - CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0) - REFP0 = 0x0b, // Write: reflect player 0 (D3) - REFP1 = 0x0c, // Write: reflect player 1 (D3) - PF0 = 0x0d, // Write: playfield register byte 0 (D7-4) - PF1 = 0x0e, // Write: playfield register byte 1 (D7-0) - PF2 = 0x0f, // Write: playfield register byte 2 (D7-0) - RESP0 = 0x10, // Write: reset player 0 (strobe) - RESP1 = 0x11, // Write: reset player 1 (strobe) - RESM0 = 0x12, // Write: reset missle 0 (strobe) - RESM1 = 0x13, // Write: reset missle 1 (strobe) - RESBL = 0x14, // Write: reset ball (strobe) - AUDC0 = 0x15, // Write: audio control 0 (D3-0) - AUDC1 = 0x16, // Write: audio control 1 (D4-0) - AUDF0 = 0x17, // Write: audio frequency 0 (D4-0) - AUDF1 = 0x18, // Write: audio frequency 1 (D3-0) - AUDV0 = 0x19, // Write: audio volume 0 (D3-0) - AUDV1 = 0x1a, // Write: audio volume 1 (D3-0) - GRP0 = 0x1b, // Write: graphics player 0 (D7-0) - GRP1 = 0x1c, // Write: graphics player 1 (D7-0) - ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1) - ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1) - ENABL = 0x1f, // Write: graphics (enable) ball (D1) - HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4) - HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4) - HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4) - HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4) - HMBL = 0x24, // Write: horizontal motion ball (D7-4) - VDELP0 = 0x25, // Write: vertical delay player 0 (D0) - VDELP1 = 0x26, // Write: vertical delay player 1 (D0) - VDELBL = 0x27, // Write: vertical delay ball (D0) - RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1) - RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1) - HMOVE = 0x2a, // Write: apply horizontal motion (strobe) - HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe) - CXCLR = 0x2c, // Write: clear collision latches (strobe) - - CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0) - CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1) - CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL) - CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL) - CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL) - CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL) - CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused) - CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1) - INPT0 = 0x08, // Read pot port: D7 - INPT1 = 0x09, // Read pot port: D7 - INPT2 = 0x0a, // Read pot port: D7 - INPT3 = 0x0b, // Read pot port: D7 - INPT4 = 0x0c, // Read P1 joystick trigger: D7 - INPT5 = 0x0d // Read P2 joystick trigger: D7 -}; - -/** - The TIA class uses some static tables that aren't dependent on the actual - TIA state. For code organization, it's better to place that functionality - here. - - @author Stephen Anthony - @version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $ -*/ -class TIATables -{ - public: - /** - Compute all static tables used by the TIA - */ - static void computeAllTables(); - - // Player mask table - // [suppress mode][nusiz][pixel] - static unsigned char PxMask[2][8][320]; - - // Missle mask table (entries are true or false) - // [number][size][pixel] - // There are actually only 4 possible size combinations on a real system - // The fifth size is used for simulating the starfield effect in - // Cosmic Ark and Stay Frosty - static unsigned char MxMask[8][5][320]; - - // Ball mask table (entries are true or false) - // [size][pixel] - static unsigned char BLMask[4][320]; - - // Playfield mask table for reflected and non-reflected playfields - // [reflect, pixel] - static unsigned int PFMask[2][160]; - - // A mask table which can be used when an object is disabled - static unsigned char DisabledMask[640]; - - // Used to set the collision register to the correct value - static unsigned short CollisionMask[64]; - - // Indicates the update delay associated with poking at a TIA address - static const short PokeDelay[64]; - -#if 0 - // Used to convert value written in a motion register into - // its internal representation - static const int CompleteMotion[76][16]; -#endif - - // Indicates if HMOVE blanks should occur for the corresponding cycle - static const bool HMOVEBlankEnableCycles[76]; - - // Used to reflect a players graphics - static unsigned char GRPReflect[256]; - - // Indicates if player is being reset during delay, display or other times - // [nusiz][old pixel][new pixel] - static signed char PxPosResetWhen[8][160][160]; - - private: - // Compute the collision decode table - static void buildCollisionMaskTable(); - - // Compute the player mask table - static void buildPxMaskTable(); - - // Compute the missle mask table - static void buildMxMaskTable(); - - // Compute the ball mask table - static void buildBLMaskTable(); - - // Compute playfield mask table - static void buildPFMaskTable(); - - // Compute the player reflect table - static void buildGRPReflectTable(); - - // Compute the player position reset when table - static void buildPxPosResetWhenTable(); - - private: - // Following constructors and assignment operators not supported - TIATables() = delete; - TIATables(const TIATables&) = delete; - TIATables(TIATables&&) = delete; - TIATables& operator=(const TIATables&) = delete; - TIATables& operator=(TIATables&&) = delete; -}; - -#endif diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 82d03270..54ef306f 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -22,7 +22,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {tia.write(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} } const char* regCheatSheetTIA[]={ "AUDC0", "15", @@ -39,7 +39,10 @@ const char** DivPlatformTIA::getRegisterSheet() { } void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - tia.process(bufL+start,len,oscBuf); + for (size_t h=start; h& wlist) { void DivPlatformTIA::setFlags(unsigned int flags) { if (flags&1) { - rate=31250; + rate=COLOR_PAL*4.0/5.0; } else { - rate=31468; + rate=COLOR_NTSC; } chipClock=rate; for (int i=0; i<2; i++) { @@ -351,7 +354,6 @@ int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - tia.channels(1,false); setFlags(flags); reset(); return 2; diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index 16817536..fe45c18b 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include -#include "sound/tia/TIASnd.h" +#include "sound/tia/Audio.h" class DivPlatformTIA: public DivDispatch { protected: @@ -42,7 +42,7 @@ class DivPlatformTIA: public DivDispatch { Channel chan[2]; DivDispatchOscBuffer* oscBuf[2]; bool isMuted[2]; - TIASound tia; + TIA::Audio tia; unsigned char regPool[16]; friend void putDispatchChan(void*,int,int); From 917a03eb070b8f195a4fcfa9828a0936c575f8bc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 23:40:21 -0500 Subject: [PATCH 54/93] TIA: fixes fixes fixes --- src/engine/platform/sound/tia/Audio.cpp | 6 ++++++ src/engine/platform/sound/tia/Audio.h | 1 + src/engine/platform/tia.cpp | 24 ++++++++++++++++++++---- src/engine/platform/tia.h | 2 ++ src/gui/sysConf.cpp | 18 ++++++++++++++++++ 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/sound/tia/Audio.cpp b/src/engine/platform/sound/tia/Audio.cpp index a90aee5a..662bd7fd 100644 --- a/src/engine/platform/sound/tia/Audio.cpp +++ b/src/engine/platform/sound/tia/Audio.cpp @@ -53,6 +53,9 @@ void Audio::reset(bool st) myCurrentSample[0]=0; myCurrentSample[1]=0; + myChannelOut[0]=0; + myChannelOut[1]=0; + myChannel0.reset(); myChannel1.reset(); } @@ -120,6 +123,9 @@ void Audio::addSample(unsigned char sample0, unsigned char sample1) else { myCurrentSample[0] = myMixingTableSum[sample0 + sample1]; } + + myChannelOut[0] = myMixingTableIndividual[sample0]; + myChannelOut[1] = myMixingTableIndividual[sample1]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/engine/platform/sound/tia/Audio.h b/src/engine/platform/sound/tia/Audio.h index ff388807..4a782bb3 100644 --- a/src/engine/platform/sound/tia/Audio.h +++ b/src/engine/platform/sound/tia/Audio.h @@ -38,6 +38,7 @@ namespace TIA { AudioChannel& channel1(); short myCurrentSample[2]; + short myChannelOut[2]; private: void phase1(); diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 54ef306f..59c53491 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -41,7 +41,19 @@ const char** DivPlatformTIA::getRegisterSheet() { void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; h>1; + } else { + bufL[h]=tia.myCurrentSample[0]; + } + if (++chanOscCounter>=114) { + chanOscCounter=0; + oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]; + } } } @@ -303,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() { } void DivPlatformTIA::reset() { - tia.reset(false); + tia.reset(mixingType); memset(regPool,0,16); for (int i=0; i<2; i++) { chan[i]=DivPlatformTIA::Channel(); @@ -313,7 +325,7 @@ void DivPlatformTIA::reset() { } bool DivPlatformTIA::isStereo() { - return false; + return (mixingType==2); } bool DivPlatformTIA::keyOffAffectsArp(int ch) { @@ -341,15 +353,19 @@ void DivPlatformTIA::setFlags(unsigned int flags) { rate=COLOR_NTSC; } chipClock=rate; + mixingType=(flags>>1)&3; for (int i=0; i<2; i++) { - oscBuf[i]->rate=rate; + oscBuf[i]->rate=rate/114; } + tia.reset(mixingType); } int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; + mixingType=0; + chanOscCounter=0; for (int i=0; i<2; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index fe45c18b..c6ea280d 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -42,6 +42,8 @@ class DivPlatformTIA: public DivDispatch { Channel chan[2]; DivDispatchOscBuffer* oscBuf[2]; bool isMuted[2]; + unsigned char mixingType; + unsigned char chanOscCounter; TIA::Audio tia; unsigned char regPool[16]; friend void putDispatchChan(void*,int,int); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index a6bbcb92..ba3ded1a 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -402,6 +402,24 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_TIA: { + ImGui::Text("Mixing mode:"); + if (ImGui::RadioButton("Mono",(flags&6)==0)) { + copyOfFlags=(flags&(~6)); + } + if (ImGui::RadioButton("Mono (no distortion)",(flags&6)==2)) { + copyOfFlags=(flags&(~6))|2; + } + if (ImGui::RadioButton("Stereo",(flags&6)==4)) { + copyOfFlags=(flags&(~6))|4; + } + + sysPal=flags&1; + if (ImGui::Checkbox("PAL",&sysPal)) { + copyOfFlags=(flags&(~1))|(unsigned int)sysPal; + } + break; + } case DIV_SYSTEM_PCSPKR: { ImGui::Text("Speaker type:"); if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { From 2e49f9c8a0cd33808a5ec8b0f937b323c8e70253 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 23:42:48 -0500 Subject: [PATCH 55/93] TIA: volume --- src/engine/platform/tia.cpp | 4 ++++ src/engine/platform/tia.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 59c53491..796c9448 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -324,6 +324,10 @@ void DivPlatformTIA::reset() { } } +float DivPlatformTIA::getPostAmp() { + return 0.5f; +} + bool DivPlatformTIA::isStereo() { return (mixingType==2); } diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index c6ea280d..b838e068 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -63,6 +63,7 @@ class DivPlatformTIA: public DivDispatch { void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); + float getPostAmp(); bool isStereo(); bool keyOffAffectsArp(int ch); void notifyInsDeletion(void* ins); From 68587dab0dc005bf9173a46f5f17d99b525d763b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 23:44:08 -0500 Subject: [PATCH 56/93] update Nuked-OPN2 core --- extern/Nuked-OPN2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/Nuked-OPN2 b/extern/Nuked-OPN2 index 64704a44..b0e9de0f 160000 --- a/extern/Nuked-OPN2 +++ b/extern/Nuked-OPN2 @@ -1 +1 @@ -Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7 +Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250 From 183d78e2a137fd7268ef278db2a495ac1763c13d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 9 Sep 2022 23:47:22 -0500 Subject: [PATCH 57/93] update Nuked-OPM to latest version --- extern/opm/opm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extern/opm/opm.c b/extern/opm/opm.c index 79f6414c..aafce483 100644 --- a/extern/opm/opm.c +++ b/extern/opm/opm.c @@ -1,5 +1,5 @@ /* Nuked OPM - * Copyright (C) 2020 Nuke.YKT + * Copyright (C) 2022 Nuke.YKT * * This file is part of Nuked OPM. * @@ -21,7 +21,7 @@ * siliconpr0n.org(digshadow, John McMaster): * YM2151 and other FM chip decaps and die shots. * - * version: 0.9.2 beta + * version: 0.9.3 beta */ #include #include @@ -651,7 +651,7 @@ static inline void OPM_EnvelopePhase4(opm_t *chip) chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]); eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0; - slreach = (chip->eg_level[slot] >> 5) == chip->eg_sl[1]; + slreach = (chip->eg_level[slot] >> 4) == (chip->eg_sl[1] << 1); eg_zero = chip->eg_level[slot] == 0; chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon; From 023d065fcc05c7b25aeef896907a623f93fce74a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 00:09:38 -0500 Subject: [PATCH 58/93] fix .dmp loading --- src/engine/fileOpsIns.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index c8dbb57f..7c2071de 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -171,9 +171,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St mode=reader.readC(); logD("instrument mode is %d",mode); if (mode==0) { - if (version<11) { - ins->type=DIV_INS_STD; - } + if (ins->type==DIV_INS_FM) ins->type=DIV_INS_STD; } else { ins->type=DIV_INS_FM; } From 3c5d71ce4ceb396713a512778f0b20c67ab4dbb6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 00:12:53 -0500 Subject: [PATCH 59/93] fix .dmp loading for Neo Geo presets --- src/engine/fileOpsIns.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 7c2071de..842f29f7 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -150,6 +150,10 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->type=DIV_INS_FM; logD("instrument type is Arcade"); break; + case 9: // Neo Geo + ins->type=DIV_INS_FM; + logD("instrument type is Neo Geo"); + break; default: logD("instrument type is unknown"); lastError="unknown instrument type!"; @@ -171,7 +175,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St mode=reader.readC(); logD("instrument mode is %d",mode); if (mode==0) { - if (ins->type==DIV_INS_FM) ins->type=DIV_INS_STD; + if (ins->type==DIV_INS_FM) { + if (sys==9) { + ins->type=DIV_INS_AY; + } else { + ins->type=DIV_INS_STD; + } + } } else { ins->type=DIV_INS_FM; } From 9ac9cfc9038370895452fbadb3f323323570f1e1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 00:28:21 -0500 Subject: [PATCH 60/93] more .dmp loading fixes this time for OPLL --- src/engine/fileOpsIns.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 842f29f7..9668c8d9 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -156,7 +156,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St break; default: logD("instrument type is unknown"); - lastError="unknown instrument type!"; + lastError=fmt::sprintf("unknown instrument type %d!",sys); delete ins; return; break; @@ -183,7 +183,14 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } } } else { - ins->type=DIV_INS_FM; + if (sys==3 || sys==6) { + ins->type=DIV_INS_OPLL; + } else if (sys==1) { + ins->type=DIV_INS_OPL; + } else { + ins->type=DIV_INS_FM; + } + } } else { ins->type=DIV_INS_FM; @@ -240,12 +247,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->fm.op[j].dvb=reader.readC(); ins->fm.op[j].dam=reader.readC(); } else { - ins->fm.op[j].rs=reader.readC(); - ins->fm.op[j].dt=reader.readC(); - ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; - ins->fm.op[j].dt&=15; - ins->fm.op[j].d2r=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); + if (sys==3 || sys==6) { // OPLL/VRC7 + ins->fm.op[j].ksr=reader.readC()?1:0; + ins->fm.op[j].vib=reader.readC(); + if (j==0) { + ins->fm.opllPreset=ins->fm.op[j].vib>>4; + } + ins->fm.op[j].vib=ins->fm.op[j].vib?1:0; + ins->fm.op[j].ksl=reader.readC()?1:0; + ins->fm.op[j].ssgEnv=reader.readC(); + } else { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; + ins->fm.op[j].dt&=15; + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } } } } else { // STD From ac0decd01b2aca0e785c4dc979883a77b9b840b1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 00:31:17 -0500 Subject: [PATCH 61/93] .dmf FDS instrument detection --- src/engine/fileOpsIns.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 9668c8d9..83361130 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -190,7 +190,6 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } else { ins->type=DIV_INS_FM; } - } } else { ins->type=DIV_INS_FM; @@ -273,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St if (version>5) { for (int i=0; istd.volMacro.len; i++) { ins->std.volMacro.val[i]=reader.readI(); + if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS + ins->type=DIV_INS_FDS; + } } } else { for (int i=0; istd.volMacro.len; i++) { From 187653a70f13d23b1920e80fe5543a4a85aee60f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 01:39:42 -0500 Subject: [PATCH 62/93] dev113 - loop detection changes --- android/app/build.gradle | 4 +-- android/app/src/main/AndroidManifest.xml | 4 +-- papers/format.md | 4 ++- src/engine/engine.cpp | 1 + src/engine/engine.h | 8 +++-- src/engine/fileOps.cpp | 14 ++++++-- src/engine/playback.cpp | 42 ++++++++++++++++++++---- src/engine/song.h | 6 ++++ src/gui/compatFlags.cpp | 20 +++++++++++ 9 files changed, 87 insertions(+), 16 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f332e6f5..92a79e4d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 112 - versionName "dev112" + versionCode 113 + versionName "dev113" externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e28b7fd1..b77cf32e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/papers/format.md b/papers/format.md index def8cba1..8528a7d6 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 113: Furnace dev113 - 112: Furnace dev112 - 111: Furnace dev111 - 110: Furnace dev110 @@ -338,7 +339,8 @@ size | description 1 | broken initial position of porta after arp (>=101) or reserved 1 | SN periods under 8 are treated as 1 (>=108) or reserved 1 | cut/delay effect policy (>=110) or reserved - 5 | reserved + 1 | 0B/0D effect treatment (>=113) or reserved + 4 | reserved --- | **virtual tempo data** 2 | virtual tempo numerator of first song (>=96) or reserved 2 | virtual tempo denominator of first song (>=96) or reserved diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4a8b7f5b..a184b5b5 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1658,6 +1658,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { speedAB=false; playing=true; skipping=true; + memset(walked,0,8192); for (int i=0; isetSkipRegisterWrites(true); while (playing && curOrder conf; std::deque pendingNotes; + // bitfield + unsigned char walked[8192]; bool isMuted[DIV_MAX_CHANS]; std::mutex isBusy, saveLock; String configPath; @@ -1072,6 +1073,7 @@ class DivEngine { memset(reversePitchTable,0,4096*sizeof(int)); memset(pitchTable,0,4096*sizeof(int)); memset(sysDefs,0,256*sizeof(void*)); + memset(walked,0,8192); for (int i=0; i<256; i++) { sysFileMapFur[i]=DIV_SYSTEM_NULL; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 66ca9d46..10a4bcce 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.brokenPortaArp=false; ds.snNoLowPeriods=true; ds.delayBehavior=0; + ds.jumpTreatment=2; // 1.1 compat flags if (ds.version>24) { @@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<110) { ds.delayBehavior=1; } + if (ds.version<113) { + ds.jumpTreatment=1; + } ds.isDMF=false; reader.readS(); // reserved @@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<5; i++) { + if (ds.version>=113) { + ds.jumpTreatment=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<4; i++) { reader.readC(); } } @@ -3747,7 +3756,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.brokenPortaArp); w->writeC(song.snNoLowPeriods); w->writeC(song.delayBehavior); - for (int i=0; i<5; i++) { + w->writeC(song.jumpTreatment); + for (int i=0; i<4; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7fb9583d..898bb2b0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -31,7 +31,9 @@ void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; if (++curOrder>=curSubSong->ordersLen) { + logV("end of orders reached"); endOfSong=true; + memset(walked,0,8192); curOrder=0; } } @@ -348,15 +350,31 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal>0) speed2=effectVal; break; case 0x0b: // change order - if (changeOrd==-1) { + if (changeOrd==-1 || song.jumpTreatment==0) { changeOrd=effectVal; - changePos=0; + if (song.jumpTreatment==1 || song.jumpTreatment==2) { + changePos=0; + } } break; case 0x0d: // next order - if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { - changeOrd=-2; - changePos=effectVal; + if (song.jumpTreatment==2) { + if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + } else if (song.jumpTreatment==1) { + if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + } else { + if (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd) { + if (changeOrd<0) { + changeOrd=-2; + } + changePos=effectVal; + } } break; case 0xed: // delay @@ -911,18 +929,23 @@ void DivEngine::nextRow() { processRow(i,false); } + walked[((curOrder<<5)+(curRow>>3))&8191]|=1<<(curRow&7); + if (changeOrd!=-1) { if (repeatPattern) { curRow=0; changeOrd=-1; } else { curRow=changePos; + changePos=0; if (changeOrd==-2) changeOrd=curOrder+1; - if (changeOrd<=curOrder) endOfSong=true; + // old loop detection routine + //if (changeOrd<=curOrder) endOfSong=true; curOrder=changeOrd; if (curOrder>=curSubSong->ordersLen) { curOrder=0; endOfSong=true; + memset(walked,0,8192); } changeOrd=-1; } @@ -932,6 +955,13 @@ void DivEngine::nextRow() { if (haltOn==DIV_HALT_PATTERN) halted=true; } + // new loop detection routine + if (!endOfSong && walked[((curOrder<<5)+(curRow>>3))&8191]&(1<<(curRow&7))) { + logV("loop reached"); + endOfSong=true; + memset(walked,0,8192); + } + if (song.brokenSpeedSel) { if ((curSubSong->patLen&1) && curOrder&1) { ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); diff --git a/src/engine/song.h b/src/engine/song.h index ac49832e..493fc412 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -468,6 +468,11 @@ struct DivSong { // 1: broken (don't allow value higher than speed) // 2: lax (allow value higher than speed) unsigned char delayBehavior; + // 0B/0D treatment + // 0: normal (0B/0D accepted) + // 1: old Furnace (first one accepted) + // 2: DefleMask (0D takes priority over 0B) + unsigned char jumpTreatment; bool properNoiseLayout; bool waveDutyIsVol; bool resetMacroOnPorta; @@ -571,6 +576,7 @@ struct DivSong { pitchSlideSpeed(4), loopModality(2), delayBehavior(2), + jumpTreatment(0), properNoiseLayout(true), waveDutyIsVol(false), resetMacroOnPorta(false), diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 74d493e7..63571dca 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -213,6 +213,26 @@ void FurnaceGUI::drawCompatFlags() { ImGui::SetTooltip("no checks (like FamiTracker)"); } + ImGui::Text("Simultaneous jump (0B+0D) treatment:"); + if (ImGui::RadioButton("Normal",e->song.jumpTreatment==0)) { + e->song.jumpTreatment=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("accept 0B+0D to jump to a specific row of an order"); + } + if (ImGui::RadioButton("Old Furnace",e->song.jumpTreatment==1)) { + e->song.jumpTreatment=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only accept the first jump effect"); + } + if (ImGui::RadioButton("DefleMask",e->song.jumpTreatment==2)) { + e->song.jumpTreatment=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only accept 0Dxx"); + } + ImGui::Separator(); ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); From a99f1bd8a070045170352ad2cbec1f2898e9bd12 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 02:34:47 -0500 Subject: [PATCH 63/93] fix walkSong --- src/engine/engine.cpp | 53 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a184b5b5..f6fe659e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { int nextOrder=-1; int nextRow=0; int effectVal=0; + int lastSuspectedLoopEnd=-1; DivPattern* pat[DIV_MAX_CHANS]; + unsigned char wsWalked[8192]; + memset(wsWalked,0,8192); for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } + if (i>lastSuspectedLoopEnd) { + lastSuspectedLoopEnd=i; + } for (int j=nextRow; jpatLen; j++) { nextRow=0; + bool changingOrder=false; + bool jumpingOrder=false; + if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) { + loopOrder=i; + loopRow=j; + loopEnd=lastSuspectedLoopEnd; + return; + } for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { - nextOrder=i+1; - nextRow=effectVal; + if (song.jumpTreatment==2) { + if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { + nextOrder=i+1; + nextRow=effectVal; + jumpingOrder=true; + } + } else if (song.jumpTreatment==1) { + if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { + nextOrder=i+1; + nextRow=effectVal; + jumpingOrder=true; + } + } else { + if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { + if (!changingOrder) { + nextOrder=i+1; + } + jumpingOrder=true; + nextRow=effectVal; + } } } else if (pat[k]->data[j][4+(l<<1)]==0x0b) { - if (nextOrder==-1) { + if (nextOrder==-1 || song.jumpTreatment==0) { nextOrder=effectVal; - nextRow=0; + if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) { + nextRow=0; + } + changingOrder=true; } } } } + + wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7); + if (nextOrder!=-1) { - if (nextOrder<=i) { - loopOrder=nextOrder; - loopRow=nextRow; - loopEnd=i; - return; - } i=nextOrder-1; nextOrder=-1; break; From 99340234b8a3883b9eee985230ce54bcabf47583 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 16:01:22 -0500 Subject: [PATCH 64/93] GUI: wave generator, part 7 prepare for WaveTools (formerly Mangle) --- src/gui/gui.cpp | 6 ++++ src/gui/gui.h | 2 ++ src/gui/waveEdit.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 65e982c7..0b1d008b 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5241,6 +5241,12 @@ FurnaceGUI::FurnaceGUI(): waveGenDuty(0.5f), waveGenPower(1), waveGenInvertPoint(1.0f), + waveGenScaleX(32), + waveGenScaleY(31), + waveGenOffsetX(0), + waveGenOffsetY(0), + waveGenSmooth(1), + waveGenAmplify(1.0f), waveGenFM(false) { // value keys valueKeys[SDLK_0]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 180013df..79a39a1f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1573,6 +1573,8 @@ class FurnaceGUI { float waveGenTL[4]; int waveGenMult[4]; int waveGenFB[4]; + int waveGenScaleX, waveGenScaleY, waveGenOffsetX, waveGenOffsetY, waveGenSmooth; + float waveGenAmplify; bool waveGenFMCon1[4]; bool waveGenFMCon2[3]; bool waveGenFMCon3[2]; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index c46e2ec0..de106cb5 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -503,7 +503,91 @@ void FurnaceGUI::drawWaveEdit() { } ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Mangle")) { + if (ImGui::BeginTabItem("WaveTools")) { + if (ImGui::BeginTable("WGParamItems",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGScaleX",&waveGenScaleX,1,16)) { + if (waveGenScaleX<2) waveGenScaleX=2; + if (waveGenScaleX>256) waveGenScaleX=256; + } + ImGui::TableNextColumn(); + ImGui::Button("Scale X"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGScaleY",&waveGenScaleY,1,16)) { + if (waveGenScaleY<2) waveGenScaleY=2; + if (waveGenScaleY>256) waveGenScaleY=256; + } + ImGui::TableNextColumn(); + ImGui::Button("Scale Y"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGOffsetX",&waveGenOffsetX,1,16)) { + if (waveGenOffsetX<-wave->len+1) waveGenOffsetX=-wave->len+1; + if (waveGenOffsetX>wave->len-1) waveGenOffsetX=wave->len-1; + } + ImGui::TableNextColumn(); + ImGui::Button("Offset X"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGOffsetY",&waveGenOffsetY,1,16)) { + if (waveGenOffsetY<-wave->max) waveGenOffsetY=-wave->max; + if (waveGenOffsetY>wave->max) waveGenOffsetY=wave->max; + } + ImGui::TableNextColumn(); + ImGui::Button("Offset Y"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGSmooth",&waveGenSmooth,1,4)) { + if (waveGenSmooth>wave->len) waveGenSmooth=wave->len; + if (waveGenSmooth<1) waveGenSmooth=1; + } + ImGui::TableNextColumn(); + ImGui::Button("Smooth"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + float amp=waveGenAmplify*100.0f; + if (ImGui::InputFloat("##WGAmplify",&,1.0f,10.0f)) { + waveGenAmplify=amp/100.0f; + if (waveGenAmplify<0.0f) waveGenAmplify=0.0f; + if (waveGenAmplify>100.0f) waveGenAmplify=100.0f; + } + ImGui::TableNextColumn(); + ImGui::Button("Amplify"); + + ImGui::EndTable(); + } + + ImVec2 buttonSize=ImGui::GetContentRegionAvail(); + buttonSize.y=0.0f; + ImVec2 buttonSizeHalf=buttonSize; + buttonSizeHalf.x-=ImGui::GetStyle().ItemSpacing.x; + buttonSizeHalf.x*=0.5; + + ImGui::Button("Normalize",buttonSize); + ImGui::Button("Invert",buttonSize); + + ImGui::Button("/2",buttonSizeHalf); + ImGui::SameLine(); + ImGui::Button("×2",buttonSizeHalf); + + ImGui::Button("Convert Signed/Unsigned",buttonSize); + ImGui::Button("Randomize",buttonSize); ImGui::EndTabItem(); } ImGui::EndTabBar(); From 8e256d4dd5c26b6b5b20870e95e1f070a0c0cd1e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 16:33:40 -0500 Subject: [PATCH 65/93] allocate ID for MSM5232 --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index 8528a7d6..14512fe1 100644 --- a/papers/format.md +++ b/papers/format.md @@ -255,6 +255,7 @@ size | description | - 0xc3: OPN CSM - 10 channels | - 0xc4: PC-98 CSM - 20 channels | - 0xc5: YM2610B CSM - 20 channels + | - 0xc6: MSM5232 - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels From 7c9384a63f16c4328acde310b4d5dd98241e52d1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 17:01:58 -0500 Subject: [PATCH 66/93] update test suite with new assert_delta --- .gitignore | 1 + test/assert_delta.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ test/furnace-test.sh | 20 ++++++++++++++++-- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 test/assert_delta.c diff --git a/.gitignore b/.gitignore index 566a3c3f..25bd6d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ linuxbuild/ test/songs/ test/delta/ test/result/ +test/assert_delta android/.gradle/ android/app/build/ android/app/.cxx/ diff --git a/test/assert_delta.c b/test/assert_delta.c new file mode 100644 index 00000000..6dad6992 --- /dev/null +++ b/test/assert_delta.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +#define BUF_SIZE 8192 + +// usage: assert_delta file +// return values: +// - 0: pass (file is silence) +// - 1: fail (noise found) +// - 2: command line error +// - 3: file open error +int main(int argc, char** argv) { + if (argc<2) return 2; + + SF_INFO si; + memset(&si,0,sizeof(SF_INFO)); + SNDFILE* sf=sf_open(argv[1],SFM_READ,&si); + if (sf==NULL) { + fprintf(stderr,"open: %s\n",sf_strerror(NULL)); + return 3; + } + + if (si.channels<1) { + fprintf(stderr,"invalid channel count\n"); + return 3; + } + + float* buf=malloc(BUF_SIZE*si.channels*sizeof(float)); + + sf_count_t totalRead=0; + size_t seekPos=0; + while ((totalRead=sf_readf_float(sf,buf,BUF_SIZE))!=0) { + for (int i=0; i Date: Sat, 10 Sep 2022 15:21:39 -0700 Subject: [PATCH 67/93] Kinda simple demo song --- demos/lunacommdemo.fur | Bin 0 -> 12875 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/lunacommdemo.fur diff --git a/demos/lunacommdemo.fur b/demos/lunacommdemo.fur new file mode 100644 index 0000000000000000000000000000000000000000..43531b8bc141cb1aa260b96f7e0bb9224dade1fc GIT binary patch literal 12875 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 literal 0 HcmV?d00001 From fd98ecee4f549d4ea623883a1e7fd0dc2225d465 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 17:57:14 -0500 Subject: [PATCH 68/93] also on OPZ --- src/engine/platform/tx81z.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 0fe0265e..1c6470fc 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -906,6 +906,7 @@ void DivPlatformTX81Z::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); From 7b19c8afa273f3f2e8c25065e31d3e23be390bf3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 17:57:28 -0500 Subject: [PATCH 69/93] no verbose FFmpeg output in furnace-test.sh --- test/furnace-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/furnace-test.sh b/test/furnace-test.sh index 0b1568a2..bb0ec375 100755 --- a/test/furnace-test.sh +++ b/test/furnace-test.sh @@ -29,7 +29,7 @@ if [ -z $lastTest ]; then echo "skipping since this apparently is your first run." else mkdir -p "test/delta/$testDir" || exit 1 - ls "test/result/$testDir/" | parallel --verbose -j4 ffmpeg -i "test/result/$lastTest/{0}" -i "test/result/$testDir/{0}" -filter_complex stereotools=phasel=1:phaser=1,amix=inputs=2:duration=longest -c:a pcm_s16le -y "test/delta/$testDir/{0}" + ls "test/result/$testDir/" | parallel --verbose -j4 ffmpeg -loglevel fatal -i "test/result/$lastTest/{0}" -i "test/result/$testDir/{0}" -filter_complex stereotools=phasel=1:phaser=1,amix=inputs=2:duration=longest -c:a pcm_s16le -y "test/delta/$testDir/{0}" fi echo "--- STEP 3: check deltas" if [ -z $lastTest ]; then From eb2c01097f865fbe02af71551991ef4a97cdc860 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 18:53:27 -0500 Subject: [PATCH 70/93] GUI: add recent file list --- src/engine/config.cpp | 4 +++ src/engine/engine.h | 1 + src/gui/gui.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++ src/gui/gui.h | 4 +++ src/gui/settings.cpp | 16 +++++++++++- 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 862cd4c0..38cb2b04 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double value) { conf[key]=fmt::sprintf("%f",value); } +void DivEngine::setConf(String key, const char* value) { + conf[key]=String(value); +} + void DivEngine::setConf(String key, String value) { conf[key]=value; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 4b78828e..7c700e00 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -544,6 +544,7 @@ class DivEngine { void setConf(String key, int value); void setConf(String key, float value); void setConf(String key, double value); + void setConf(String key, const char* value); void setConf(String key, String value); // calculate base frequency/period diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0b1d008b..0d0635ec 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -536,6 +536,7 @@ void FurnaceGUI::setFileName(String name) { } #endif updateWindowTitle(); + pushRecentFile(curFileName); } void FurnaceGUI::updateWindowTitle() { @@ -1724,6 +1725,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } @@ -1801,9 +1803,26 @@ int FurnaceGUI::load(String path) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } +void FurnaceGUI::pushRecentFile(String path) { + if (path.empty()) return; + if (path==backupPath) return; + for (int i=0; i<(int)recentFile.size(); i++) { + if (recentFile[i]==path) { + recentFile.erase(recentFile.begin()+i); + i--; + } + } + recentFile.push_front(path); + + while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { + recentFile.pop_back(); + } +} + void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; @@ -3045,6 +3064,27 @@ bool FurnaceGUI::loop() { openFileDialog(GUI_FILE_OPEN); } } + if (ImGui::BeginMenu("open recent")) { + for (int i=0; i<(int)recentFile.size(); i++) { + String item=recentFile[i]; + if (ImGui::MenuItem(item.c_str())) { + if (modified) { + nextFile=item; + showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP); + } else { + recentFile.erase(recentFile.begin()+i); + i--; + if (load(item)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + } + } + } + if (recentFile.empty()) { + ImGui::Text("nothing here yet"); + } + ImGui::EndMenu(); + } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { @@ -4628,6 +4668,13 @@ bool FurnaceGUI::init() { syncSettings(); + for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); + if (!r.empty()) { + recentFile.push_back(r); + } + } + if (settings.dpiScale>=0.5f) { dpiScale=settings.dpiScale; } @@ -4900,6 +4947,16 @@ bool FurnaceGUI::finish() { e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscGrad",chanOscGrad.toString()); + // commit recent files + for (int i=0; i<30; i++) { + String key=fmt::sprintf("recentFile%d",i); + if (i>=settings.maxRecentFile || i>=(int)recentFile.size()) { + e->setConf(key,""); + } else { + e->setConf(key,recentFile[i]); + } + } + for (int i=0; i sysSearchResults; std::vector newSongSearchResults; + std::deque recentFile; bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; bool portrait, mobileMenuOpen; @@ -1171,6 +1172,7 @@ class FurnaceGUI { int channelStyle; int channelVolStyle; int channelFeedbackStyle; + int maxRecentFile; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1291,6 +1293,7 @@ class FurnaceGUI { channelStyle(0), channelVolStyle(0), channelFeedbackStyle(1), + maxRecentFile(10), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1728,6 +1731,7 @@ class FurnaceGUI { void openFileDialog(FurnaceGUIFileDialogs type); int save(String path, int dmfVersion); int load(String path); + void pushRecentFile(String path); void exportAudio(String path, DivAudioExportModes mode); bool parseSysEx(unsigned char* data, size_t len); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 53ce0cde..54da8e89 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1130,6 +1130,13 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + if (ImGui::InputInt("Number of recent files",&settings.maxRecentFile)) { + if (settings.maxRecentFile<0) settings.maxRecentFile=0; + if (settings.maxRecentFile>30) settings.maxRecentFile=30; + } + + ImGui::Separator(); + ImGui::Text("Pattern view labels:"); ImGui::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel); ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel); @@ -2273,6 +2280,7 @@ void FurnaceGUI::syncSettings() { settings.channelStyle=e->getConfInt("channelStyle",0); settings.channelVolStyle=e->getConfInt("channelVolStyle",0); settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1); + settings.maxRecentFile=e->getConfInt("maxRecentFile",10); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2371,6 +2379,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.channelStyle,0,5); clampSetting(settings.channelVolStyle,0,3); clampSetting(settings.channelFeedbackStyle,0,3); + clampSetting(settings.maxRecentFile,0,30); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2403,7 +2412,7 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { - bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + bool sampleROMsChanged=settings.yrw801Path!=e->getConfString("yrw801Path","") || settings.tg100Path!=e->getConfString("tg100Path","") || settings.mu5Path!=e->getConfString("mu5Path",""); @@ -2525,6 +2534,7 @@ void FurnaceGUI::commitSettings() { e->setConf("channelStyle",settings.channelStyle); e->setConf("channelVolStyle",settings.channelVolStyle); e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle); + e->setConf("maxRecentFile",settings.maxRecentFile); // colors for (int i=0; isaveConf(); + while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { + recentFile.pop_back(); + } + if (sampleROMsChanged) { if (e->loadSampleROMs()) { showError(e->getLastError()); From a46ef0c0be52cc4bb0fad78b8c02abb48f403f2f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 19:35:50 -0500 Subject: [PATCH 71/93] GUI: wave generator, part 8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit to-do: - normalize - /2 - ×2 - smooth --- src/gui/waveEdit.cpp | 85 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index de106cb5..37b8cc80 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -516,7 +516,17 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenScaleX>256) waveGenScaleX=256; } ImGui::TableNextColumn(); - ImGui::Button("Scale X"); + if (ImGui::Button("Scale X")) { + if (waveGenScaleX>0 && wave->len!=waveGenScaleX) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + for (int i=0; idata[i]=origData[i*wave->len/waveGenScaleX]; + } + wave->len=waveGenScaleX; + MARK_MODIFIED; + }); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -526,7 +536,15 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenScaleY>256) waveGenScaleY=256; } ImGui::TableNextColumn(); - ImGui::Button("Scale Y"); + if (ImGui::Button("Scale Y")) { + if (waveGenScaleY>0 && wave->max!=waveGenScaleY) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=(wave->data[i]*(waveGenScaleY+1))/(wave->max+1); + } + wave->max=waveGenScaleY; + MARK_MODIFIED; + }); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -536,7 +554,19 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenOffsetX>wave->len-1) waveGenOffsetX=wave->len-1; } ImGui::TableNextColumn(); - ImGui::Button("Offset X"); + if (ImGui::Button("Offset X")) { + if (waveGenOffsetX!=0 && wave->len>0) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + int realOff=-waveGenOffsetX; + while (realOff<0) realOff+=wave->len; + + for (int i=0; ilen; i++) { + wave->data[i]=origData[(i+realOff)%wave->len]; + } + MARK_MODIFIED; + }); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -546,7 +576,14 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenOffsetY>wave->max) waveGenOffsetY=wave->max; } ImGui::TableNextColumn(); - ImGui::Button("Offset Y"); + if (ImGui::Button("Offset Y")) { + if (waveGenOffsetY!=0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=CLAMP(wave->data[i]+waveGenOffsetY,0,wave->max); + } + MARK_MODIFIED; + }); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -568,7 +605,14 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenAmplify>100.0f) waveGenAmplify=100.0f; } ImGui::TableNextColumn(); - ImGui::Button("Amplify"); + if (ImGui::Button("Amplify")) { + if (waveGenAmplify!=1.0f) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=CLAMP(round((float)(wave->data[i]-((wave->max+1)/2))*waveGenAmplify),(int)(-((wave->max+1)/2)),(int)(wave->max/2))+((wave->max+1)/2); + } + MARK_MODIFIED; + }); + } ImGui::EndTable(); } @@ -580,14 +624,39 @@ void FurnaceGUI::drawWaveEdit() { buttonSizeHalf.x*=0.5; ImGui::Button("Normalize",buttonSize); - ImGui::Button("Invert",buttonSize); + if (ImGui::Button("Invert",buttonSize)) { + e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=wave->max-wave->data[i]; + } + MARK_MODIFIED; + }); + } ImGui::Button("/2",buttonSizeHalf); ImGui::SameLine(); ImGui::Button("×2",buttonSizeHalf); - ImGui::Button("Convert Signed/Unsigned",buttonSize); - ImGui::Button("Randomize",buttonSize); + if (ImGui::Button("Convert Signed/Unsigned",buttonSize)) { + if (wave->max>0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + if (wave->data[i]>(wave->max/2)) { + wave->data[i]-=(wave->max+1)/2; + } else { + wave->data[i]+=wave->max/2; + } + } + MARK_MODIFIED; + }); + } + if (ImGui::Button("Randomize",buttonSize)) { + if (wave->max>0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=rand()%wave->max; + } + MARK_MODIFIED; + }); + } ImGui::EndTabItem(); } ImGui::EndTabBar(); From 06fe2daa017757749a3dea6fa025595c4deda8ed Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 19:36:41 -0500 Subject: [PATCH 72/93] update to-do list --- TODO.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index cba0b7c6..859ad68c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ -# to-do for 0.6pre1.5-0.6pre2 +# to-do for 0.6pre1.5 -- volume commands should work on Game Boy - stereo separation control for AY -- "paste with instrument" \ No newline at end of file +- "paste with instrument" +- FM operator muting +- FM operator swap +- bug fixes From fc4aea3c916ca8cd40fdb66c72d038853b199a68 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 21:21:08 -0500 Subject: [PATCH 73/93] update demo songs (#661) --- demos/FEDMS.fur | Bin 0 -> 75333 bytes demos/hope_for_the_dream.fur | Bin 0 -> 3467 bytes demos/iji_tor.fur | Bin 0 -> 299701 bytes src/gui/about.cpp | 3 ++- 4 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 demos/FEDMS.fur create mode 100644 demos/hope_for_the_dream.fur create mode 100644 demos/iji_tor.fur diff --git a/demos/FEDMS.fur b/demos/FEDMS.fur new file mode 100644 index 0000000000000000000000000000000000000000..4c521dadaa23f90f934abdee9e692d25f2bac28f GIT binary patch literal 75333 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 B?h*h1 literal 0 HcmV?d00001 diff --git a/demos/iji_tor.fur b/demos/iji_tor.fur new file mode 100644 index 0000000000000000000000000000000000000000..84a6ac5b3575319834d026f47838a1e8147d5155 GIT binary patch literal 299701 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�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%0D&UXZQbOXjDYI-E9L9W@bkL>(w!Gk9zuK2op%i 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[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 Z6CU$`gVrO0zV~95pJHBGO~-z`;XhYgN-qEa literal 0 HcmV?d00001 diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 39cb1192..47ae9e98 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -87,8 +87,9 @@ const char* aboutLine[]={ "Miker", "nicco1690", "NikonTeen", - "psdominator", + "psxdominator", "Raijin", + "SnugglyBun", "SuperJet Spade", "TheDuccinator", "theloredev", From 7d89708bf747455fdc0717ebe40247f7c11c254a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 21:25:58 -0500 Subject: [PATCH 74/93] Clang can you stop complaining --- src/gui/waveEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 37b8cc80..2b7e638b 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -608,7 +608,7 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::Button("Amplify")) { if (waveGenAmplify!=1.0f) e->lockEngine([this,wave]() { for (int i=0; ilen; i++) { - wave->data[i]=CLAMP(round((float)(wave->data[i]-((wave->max+1)/2))*waveGenAmplify),(int)(-((wave->max+1)/2)),(int)(wave->max/2))+((wave->max+1)/2); + wave->data[i]=CLAMP(round((float)(wave->data[i]-(int)( /* Clang can you stop complaining */ (int)(wave->max+1)/(int)2))*waveGenAmplify),(int)(-((wave->max+1)/2)),(int)(wave->max/2))+(int)((wave->max+1)/2); } MARK_MODIFIED; }); From 60e37714dacbd0f7942b1d4b5aa241742ebd36c6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 21:28:54 -0500 Subject: [PATCH 75/93] Vim mistakes *** for italic --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d2308dd7..7efe2c2a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ the biggest multi-system chiptune tracker ever made! [downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) -*** +--- ## downloads check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). @@ -74,7 +74,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - loads .dmf modules from all versions (beta 1 to 1.1.3) - saves .dmf modules - both modern and legacy - Furnace doubles as a module downgrader - - loads .dmp instruments and .dmw wavetables as well + - loads/saves .dmp instruments and .dmw wavetables as well - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy through compatibility flags - VGM export @@ -103,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - built-in visualizer in pattern view - open-source under GPLv2 or later. -*** +--- # quick references - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). @@ -119,7 +119,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. -*** +--- # developer info [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) @@ -228,7 +228,7 @@ this will play a compatible file and enable the commands view. **note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** -*** +--- # frequently asked questions > woah! 50 sound chips?! I can't believe it! @@ -274,7 +274,7 @@ the DefleMask format has several limitations. save in Furnace song format instea right click on the channel name. -*** +--- # footnotes copyright (C) 2021-2022 tildearrow and contributors. From f0d3ad1c82d9b089101fd731e4704ed209e5ea07 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 21:33:35 -0500 Subject: [PATCH 76/93] GUI: don't care about clipboard data version --- src/gui/editing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 391a39f6..201c9be2 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -431,7 +431,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int startOff=-1; bool invalidData=false; if (data.size()<2) return; - if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; + if (data[0].find("org.tildearrow.furnace - Pattern Data")!=0) return; if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; if (startOff<0) return; From 2a051900a77dcb2f1dd54aeece23b424d47dc5fd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 21:46:10 -0500 Subject: [PATCH 77/93] update Bullet_Hell.fur --- demos/Bullet_Hell.fur | Bin 60233 -> 60291 bytes src/gui/about.cpp | 1 + 2 files changed, 1 insertion(+) diff --git a/demos/Bullet_Hell.fur b/demos/Bullet_Hell.fur index 48c50f674d7d8a4c9cfbb2f632a1d1de602a617e..dfd2a04251e144596f21d8dd6aeaa3d2cc0a297b 100644 GIT binary patch literal 60291 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

    lfopXzJz@$ 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 N5YtCrb8Rn|{{f|H`MUrB literal 60233 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

    @Jkl=4Ns}LO9Yo9Q>HcR&Vv2yWz%NN& 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 Date: Sat, 10 Sep 2022 22:12:03 -0500 Subject: [PATCH 78/93] GUI: wave generator, part 9 it's complete! --- src/gui/waveEdit.cpp | 74 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 2b7e638b..69594f5d 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -593,7 +593,23 @@ void FurnaceGUI::drawWaveEdit() { if (waveGenSmooth<1) waveGenSmooth=1; } ImGui::TableNextColumn(); - ImGui::Button("Smooth"); + if (ImGui::Button("Smooth")) { + if (waveGenSmooth>0) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + for (int i=0; ilen; i++) { + int dataSum=0; + for (int j=i; jlen; + dataSum+=origData[pos%wave->len]; + } + dataSum/=waveGenSmooth+1; + wave->data[i]=dataSum; + } + MARK_MODIFIED; + }); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -623,7 +639,39 @@ void FurnaceGUI::drawWaveEdit() { buttonSizeHalf.x-=ImGui::GetStyle().ItemSpacing.x; buttonSizeHalf.x*=0.5; - ImGui::Button("Normalize",buttonSize); + if (ImGui::Button("Normalize",buttonSize)) { + e->lockEngine([this,wave]() { + // find lowest point + int lowest=wave->max; + for (int i=0; ilen; i++) { + if (wave->data[i]data[i]; + } + + // find highest point + int highest=0; + for (int i=0; ilen; i++) { + if (wave->data[i]>highest) highest=wave->data[i]; + } + + // abort if lowest and highest points are equal + if (lowest==highest) return; + + // abort if lowest and highest points already span the entire height + if (lowest==wave->max && highest==0) return; + + // apply offset + for (int i=0; ilen; i++) { + wave->data[i]-=lowest; + } + highest-=lowest; + + // scale + for (int i=0; ilen; i++) { + wave->data[i]=(wave->data[i]*wave->max)/highest; + } + MARK_MODIFIED; + }); + } if (ImGui::Button("Invert",buttonSize)) { e->lockEngine([this,wave]() { for (int i=0; ilen; i++) { @@ -633,9 +681,25 @@ void FurnaceGUI::drawWaveEdit() { }); } - ImGui::Button("/2",buttonSizeHalf); + if (ImGui::Button("Half",buttonSizeHalf)) { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[i>>1]; + } + MARK_MODIFIED; + } ImGui::SameLine(); - ImGui::Button("×2",buttonSizeHalf); + if (ImGui::Button("Double",buttonSizeHalf)) { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[(i*2)%wave->len]; + } + MARK_MODIFIED; + } if (ImGui::Button("Convert Signed/Unsigned",buttonSize)) { if (wave->max>0) e->lockEngine([this,wave]() { @@ -643,7 +707,7 @@ void FurnaceGUI::drawWaveEdit() { if (wave->data[i]>(wave->max/2)) { wave->data[i]-=(wave->max+1)/2; } else { - wave->data[i]+=wave->max/2; + wave->data[i]+=(wave->max+1)/2; } } MARK_MODIFIED; From 09233b6de01faf39748192be53f8dbecff629214 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 22:35:21 -0500 Subject: [PATCH 79/93] GUI: add signed waveform view mode --- src/gui/gui.cpp | 13 ++++++++----- src/gui/gui.h | 4 ++-- src/gui/waveEdit.cpp | 32 ++++++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0d0635ec..66b562b6 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -228,7 +228,7 @@ void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macr } } -void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex) { +void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex) { int buf=0; bool negaBuf=false; bool hasVal=false; @@ -264,9 +264,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma case ' ': if (hasVal) { hasVal=false; - negaBuf=false; macro[macroLen]=negaBuf?-buf:buf; - if (macro[macroLen]<0) macro[macroLen]=0; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; macroLen++; buf=0; @@ -277,9 +277,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma } if (hasVal && macroLen<256) { hasVal=false; - negaBuf=false; macro[macroLen]=negaBuf?-buf:buf; - if (macro[macroLen]<0) macro[macroLen]=0; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; macroLen++; buf=0; @@ -4621,6 +4621,7 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + waveSigned=e->getConfBool("waveSigned",false); waveGenVisible=e->getConfBool("waveGenVisible",false); waveEditStyle=e->getConfInt("waveEditStyle",0); lockLayout=e->getConfBool("lockLayout",false); @@ -4906,6 +4907,7 @@ bool FurnaceGUI::finish() { e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); + e->setConf("waveSigned",waveSigned); e->setConf("waveGenVisible",waveGenVisible); e->setConf("waveEditStyle",waveEditStyle); e->setConf("lockLayout",lockLayout); @@ -5112,6 +5114,7 @@ FurnaceGUI::FurnaceGUI(): firstFrame(true), tempoView(true), waveHex(false), + waveSigned(false), waveGenVisible(false), lockLayout(false), editOptsVisible(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 0f52115d..2fb566d5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1327,7 +1327,7 @@ class FurnaceGUI { SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; @@ -1741,7 +1741,7 @@ class FurnaceGUI { void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false, bool bit30=false); void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30=false); - void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false); + void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex=false); String encodeKeyMap(std::map& map); void decodeKeyMap(std::map& map, String source); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 69594f5d..006ee430 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -255,6 +255,9 @@ void FurnaceGUI::drawWaveEdit() { for (int i=0; ilen; i++) { if (wave->data[i]>wave->max) wave->data[i]=wave->max; wavePreview[i]=wave->data[i]; + if (waveSigned && !waveHex) { + wavePreview[i]-=(int)((wave->max+1)/2); + } } if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; @@ -269,9 +272,9 @@ void FurnaceGUI::drawWaveEdit() { ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y; if (waveEditStyle) { - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion); } else { - PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); + PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); } if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { waveDragStart=ImGui::GetItemRectMin(); @@ -737,12 +740,33 @@ void FurnaceGUI::drawWaveEdit() { waveHex=true; } ImGui::SameLine(); + if (!waveHex) if (ImGui::Button(waveSigned?"±##WaveSign":"+##WaveSign",ImVec2(ImGui::GetFrameHeight(),ImGui::GetFrameHeight()))) { + waveSigned=!waveSigned; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/Unsigned"); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex); + int actualData[256]; + decodeMMLStrW(mmlStringW,actualData,wave->len,(waveSigned && !waveHex)?(-((wave->max+1)/2)):0,(waveSigned && !waveHex)?(wave->max/2):wave->max,waveHex); + if (waveSigned && !waveHex) { + for (int i=0; ilen; i++) { + actualData[i]+=(wave->max+1)/2; + } + } + memcpy(wave->data,actualData,wave->len*sizeof(int)); } if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex); + int actualData[256]; + memcpy(actualData,wave->data,256*sizeof(int)); + if (waveSigned && !waveHex) { + for (int i=0; ilen; i++) { + actualData[i]-=(wave->max+1)/2; + } + } + encodeMMLStr(mmlStringW,actualData,wave->len,-1,-1,waveHex); } } } From 2952baaa5409f3e2d3f9c0d32d9eaef64a062d5b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 22:40:25 -0500 Subject: [PATCH 80/93] update 6-sample --- papers/doc/6-sample/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 59613da2..4fbb6c91 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -64,7 +64,3 @@ In there, you can modify certain data pertaining to your sample, such as the: - and many more. The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text. - -# tips - -if you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file, From 2f0e97f6d95a24ca718174cd826f69c769655941 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 23:20:22 -0500 Subject: [PATCH 81/93] GUI: FM operator swapping --- src/gui/gui.cpp | 1 + src/gui/gui.h | 2 +- src/gui/insEdit.cpp | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 66b562b6..329c3e8e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5217,6 +5217,7 @@ FurnaceGUI::FurnaceGUI(): chanToMove(-1), sysToMove(-1), sysToDelete(-1), + opToMove(-1), transposeAmount(0), randomizeMin(0), randomizeMax(255), diff --git a/src/gui/gui.h b/src/gui/gui.h index 2fb566d5..f91c4bee 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1471,7 +1471,7 @@ class FurnaceGUI { int renderTimeBegin, renderTimeEnd, renderTimeDelta; int eventTimeBegin, eventTimeEnd, eventTimeDelta; - int chanToMove, sysToMove, sysToDelete; + int chanToMove, sysToMove, sysToDelete, opToMove; ImVec2 patWindowPos, patWindowSize; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index ec9ba32b..c2c7a260 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1557,6 +1557,35 @@ void FurnaceGUI::drawMacros(std::vector& macros) { #define CENTER_TEXT_20(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x)); +#define OP_DRAG_POINT \ + if (ImGui::Button(ICON_FA_ARROWS)) { \ + } \ + if (ImGui::BeginDragDropSource()) { \ + opToMove=i; \ + ImGui::SetDragDropPayload("FUR_OP",NULL,0,ImGuiCond_Once); \ + ImGui::Button(ICON_FA_ARROWS "##SysDrag"); \ + ImGui::EndDragDropSource(); \ + } else if (ImGui::IsItemHovered()) { \ + ImGui::SetTooltip("(drag to swap operators)"); \ + } \ + if (ImGui::BeginDragDropTarget()) { \ + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_OP"); \ + if (dragItem!=NULL) { \ + if (dragItem->IsDataType("FUR_OP")) { \ + if (opToMove!=i && opToMove>=0) { \ + e->lockEngine([this,ins,i]() { \ + DivInstrumentFM::Operator origOp=ins->fm.op[orderedOps[opToMove]]; \ + ins->fm.op[orderedOps[opToMove]]=ins->fm.op[orderedOps[i]]; \ + ins->fm.op[orderedOps[i]]=origOp; \ + }); \ + PARAMETER; \ + } \ + opToMove=-1; \ + } \ + } \ + ImGui::EndDragDropTarget(); \ + } + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -2073,6 +2102,9 @@ void FurnaceGUI::drawInsEdit() { } else { ImGui::Text("OP%d",i+1); } + + // drag point + OP_DRAG_POINT; int maxTl=127; if (ins->type==DIV_INS_OPLL) { @@ -2356,7 +2388,10 @@ void FurnaceGUI::drawInsEdit() { } else { snprintf(tempID,1024,"Operator %d",i+1); } - CENTER_TEXT(tempID); + float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x); + OP_DRAG_POINT; + ImGui::SameLine(); + ImGui::SetCursorPosX(nextCursorPosX); ImGui::TextUnformatted(tempID); float sliderHeight=200.0f*dpiScale; @@ -2789,6 +2824,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + OP_DRAG_POINT; + ImGui::SameLine(); if (ins->type==DIV_INS_OPL_DRUMS) { ImGui::Text("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { From 29f1be3b3605afa599cf099115a3a8f6d9e0bfac Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 23:32:04 -0500 Subject: [PATCH 82/93] fix audio backend not changing on switchMaster --- src/engine/engine.cpp | 11 ++++++++--- src/engine/engine.h | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f6fe659e..91764034 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3453,7 +3453,8 @@ void DivEngine::setConsoleMode(bool enable) { } bool DivEngine::switchMaster() { - deinitAudioBackend(); + logI("switching output..."); + deinitAudioBackend(true); quitDispatch(); initDispatch(); if (initAudioBackend()) { @@ -3599,6 +3600,7 @@ void DivEngine::quitDispatch() { bool DivEngine::initAudioBackend() { // load values + logI("initializing audio."); if (audioEngine==DIV_AUDIO_NULL) { if (getConfString("audioEngine","SDL")=="JACK") { audioEngine=DIV_AUDIO_JACK; @@ -3704,8 +3706,9 @@ bool DivEngine::initAudioBackend() { return true; } -bool DivEngine::deinitAudioBackend() { +bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) { if (output!=NULL) { + logI("closing audio output."); output->quit(); if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { @@ -3722,7 +3725,9 @@ bool DivEngine::deinitAudioBackend() { output->quitMidi(); delete output; output=NULL; - //audioEngine=DIV_AUDIO_NULL; + if (dueToSwitchMaster) { + audioEngine=DIV_AUDIO_NULL; + } } return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 7c700e00..869587a2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -452,7 +452,7 @@ class DivEngine { int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); bool initAudioBackend(); - bool deinitAudioBackend(); + bool deinitAudioBackend(bool dueToSwitchMaster=false); void registerSystems(); void initSongWithDesc(const int* description); From 03e226e52b1854ffb939b2da1b5b7dc312920693 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 10 Sep 2022 23:33:05 -0500 Subject: [PATCH 83/93] seamless switchMaster --- src/engine/engine.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 91764034..bb03eda1 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3455,8 +3455,6 @@ void DivEngine::setConsoleMode(bool enable) { bool DivEngine::switchMaster() { logI("switching output..."); deinitAudioBackend(true); - quitDispatch(); - initDispatch(); if (initAudioBackend()) { for (int i=0; i Date: Sat, 10 Sep 2022 23:50:53 -0500 Subject: [PATCH 84/93] GUI: fix toggles losing their colors on hover --- src/gui/editControls.cpp | 92 ++++++++++++++++++++-------------------- src/gui/gui.cpp | 29 +++++++++++++ src/gui/gui.h | 5 ++- src/gui/insEdit.cpp | 32 +++++++------- src/gui/sampleEdit.cpp | 16 +++---- 5 files changed, 102 insertions(+), 72 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 9d6c0d00..1d0e2a25 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -59,12 +59,12 @@ void FurnaceGUI::drawMobileControls() { if (!portrait) ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); @@ -76,27 +76,27 @@ void FurnaceGUI::drawMobileControls() { } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -306,11 +306,11 @@ void FurnaceGUI::drawEditControls() { ImGui::EndTable(); } - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); @@ -340,12 +340,12 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -356,11 +356,11 @@ void FurnaceGUI::drawEditControls() { stop(); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); @@ -369,26 +369,26 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); ImGui::Text("Octave"); @@ -425,12 +425,12 @@ void FurnaceGUI::drawEditControls() { unimportant(ImGui::Checkbox("Pattern",&followPattern)); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -438,11 +438,11 @@ void FurnaceGUI::drawEditControls() { case 2: // compact vertical if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { ImVec2 buttonSize=ImVec2(ImGui::GetContentRegionAvail().x,0.0f); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } @@ -452,24 +452,24 @@ void FurnaceGUI::drawEditControls() { } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::Text("Oct."); float avail=ImGui::GetContentRegionAvail().x; @@ -496,23 +496,23 @@ void FurnaceGUI::drawEditControls() { } ImGui::Text("Foll."); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders)); + pushToggleColors(followOrders); if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant followOrders=!followOrders; } - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern)); + popToggleColors(); + pushToggleColors(followPattern); if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant followPattern=!followPattern; } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -520,11 +520,11 @@ void FurnaceGUI::drawEditControls() { case 3: // split if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { if (e->isPlaying()) { - ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]); + pushToggleColors(true); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } - ImGui::PopStyleColor(); + popToggleColors(); } else { if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(oldRow); @@ -547,35 +547,35 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 329c3e8e..4785ee81 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2408,6 +2408,35 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) { } } +void FurnaceGUI::pushToggleColors(bool status) { + ImVec4 toggleColor=status?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]; + ImGui::PushStyleColor(ImGuiCol_Button,toggleColor); + if (settings.guiColorsBase) { + toggleColor.x*=0.8f; + toggleColor.y*=0.8f; + toggleColor.z*=0.8f; + } else { + toggleColor.x=CLAMP(toggleColor.x*1.3f,0.0f,1.0f); + toggleColor.y=CLAMP(toggleColor.y*1.3f,0.0f,1.0f); + toggleColor.z=CLAMP(toggleColor.z*1.3f,0.0f,1.0f); + } + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,toggleColor); + if (settings.guiColorsBase) { + toggleColor.x*=0.8f; + toggleColor.y*=0.8f; + toggleColor.z*=0.8f; + } else { + toggleColor.x=CLAMP(toggleColor.x*1.5f,0.0f,1.0f); + toggleColor.y=CLAMP(toggleColor.y*1.5f,0.0f,1.0f); + toggleColor.z=CLAMP(toggleColor.z*1.5f,0.0f,1.0f); + } + ImGui::PushStyleColor(ImGuiCol_ButtonActive,toggleColor); +} + +void FurnaceGUI::popToggleColors() { + ImGui::PopStyleColor(3); +} + int _processEvent(void* instance, SDL_Event* event) { return ((FurnaceGUI*)instance)->processEvent(event); } diff --git a/src/gui/gui.h b/src/gui/gui.h index f91c4bee..b71fe368 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -47,8 +47,6 @@ #define MARK_MODIFIED modified=true; #define WAKE_UP drawHalt=16; -#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) - #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() // TODO: @@ -1616,6 +1614,9 @@ class FurnaceGUI { void toggleMobileUI(bool enable, bool force=false); + void pushToggleColors(bool status); + void popToggleColors(); + void drawMobileControls(); void drawEditControls(); void drawSongInfo(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c2c7a260..563e0579 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3400,29 +3400,29 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { ImGui::Text("Waveform"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn)); + pushToggleColors(ins->c64.triOn); if (ImGui::Button("tri")) { PARAMETER ins->c64.triOn=!ins->c64.triOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn)); + pushToggleColors(ins->c64.sawOn); if (ImGui::Button("saw")) { PARAMETER ins->c64.sawOn=!ins->c64.sawOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn)); + pushToggleColors(ins->c64.pulseOn); if (ImGui::Button("pulse")) { PARAMETER ins->c64.pulseOn=!ins->c64.pulseOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn)); + pushToggleColors(ins->c64.noiseOn); if (ImGui::Button("noise")) { PARAMETER ins->c64.noiseOn=!ins->c64.noiseOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); @@ -3484,29 +3484,29 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Filter Mode"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp)); + pushToggleColors(ins->c64.lp); if (ImGui::Button("low")) { PARAMETER ins->c64.lp=!ins->c64.lp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp)); + pushToggleColors(ins->c64.bp); if (ImGui::Button("band")) { PARAMETER ins->c64.bp=!ins->c64.bp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp)); + pushToggleColors(ins->c64.hp); if (ImGui::Button("high")) { PARAMETER ins->c64.hp=!ins->c64.hp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off)); + pushToggleColors(ins->c64.ch3off); if (ImGui::Button("ch3off")) { PARAMETER ins->c64.ch3off=!ins->c64.ch3off; } - ImGui::PopStyleColor(); + popToggleColors(); P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 76f654c1..51411c43 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -152,20 +152,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -687,20 +687,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } From d4867c505039461cb98606eb813643cf78c27e62 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 11 Sep 2022 05:14:15 -0500 Subject: [PATCH 85/93] GUI: consistency in credits --- src/gui/about.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index fae8019c..b763ab97 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -104,7 +104,7 @@ const char* aboutLine[]={ "fd", "GENATARi", "host12prog", - "lunathir", + "Lunathir", "plane", "TheEssem", "", From 7b1713758bca595ddc86acda8b087bf844d1379a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Sep 2022 00:37:25 -0500 Subject: [PATCH 86/93] dev114 - operator muting for OPN/OPM --- papers/format.md | 4 ++- src/engine/engine.h | 4 +-- src/engine/instrument.cpp | 12 ++++++-- src/engine/platform/arcade.cpp | 7 ++++- src/engine/platform/arcade.h | 5 ++-- src/engine/platform/genesis.cpp | 7 ++++- src/engine/platform/genesis.h | 3 +- src/engine/platform/ym2203.cpp | 7 ++++- src/engine/platform/ym2203.h | 3 +- src/engine/platform/ym2608.cpp | 7 ++++- src/engine/platform/ym2608.h | 3 +- src/engine/platform/ym2610.cpp | 7 ++++- src/engine/platform/ym2610.h | 5 ++-- src/engine/platform/ym2610b.cpp | 7 ++++- src/engine/platform/ym2610b.h | 5 ++-- src/gui/insEdit.cpp | 52 ++++++++++++++++++++++++++------- 16 files changed, 108 insertions(+), 30 deletions(-) diff --git a/papers/format.md b/papers/format.md index 14512fe1..da06488f 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 114: Furnace dev114 - 113: Furnace dev113 - 112: Furnace dev112 - 111: Furnace dev111 @@ -497,7 +498,8 @@ size | description 1 | vib 1 | ws 1 | ksr - 12 | reserved + 1 | operator enabled (>=114) or reserved + 11 | reserved --- | **Game Boy instrument data** 1 | volume 1 | direction diff --git a/src/engine/engine.h b/src/engine/engine.h index 869587a2..3047a8af 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -46,8 +46,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev113" -#define DIV_ENGINE_VERSION 113 +#define DIV_VERSION "dev114" +#define DIV_ENGINE_VERSION 114 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 19cb1ba5..22cdddf4 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -71,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(op.ws); w->writeC(op.ksr); + w->writeC(op.enable); + // reserved - for (int k=0; k<12; k++) { + for (int k=0; k<11; k++) { w->writeC(0); } } @@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { op.ws=reader.readC(); op.ksr=reader.readC(); + if (version>=114) { + op.enable=reader.readC(); + } else { + reader.readC(); + } + // reserved - for (int k=0; k<12; k++) reader.readC(); + for (int k=0; k<11; k++) reader.readC(); } // GB diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 90de2387..7d44fe8a 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -348,7 +348,7 @@ void DivPlatformArcade::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].keyOn) { - immWrite(0x08,0x78|i); + immWrite(0x08,(chan[i].opMask<<3)|i); chan[i].keyOn=false; } } @@ -370,6 +370,11 @@ int DivPlatformArcade::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } chan[c.chan].macroInit(ins); diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index 93aa490d..683394e3 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -45,7 +45,7 @@ class DivPlatformArcade: public DivPlatformOPM { signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; int vol, outVol; - unsigned char chVolL, chVolR; + unsigned char chVolL, chVolR, opMask; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; @@ -71,7 +71,8 @@ class DivPlatformArcade: public DivPlatformOPM { vol(0), outVol(0), chVolL(127), - chVolR(127) {} + chVolR(127), + opMask(15) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index f499865e..5b4b535b 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -480,7 +480,7 @@ void DivPlatformGenesis::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].keyOn) { - if (i<6) immWrite(0x28,0xf0|konOffs[i]); + if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); chan[i].keyOn=false; } } @@ -591,6 +591,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } chan[c.chan].macroInit(ins); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 5e61fe67..34a4d4f2 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -47,7 +47,7 @@ class DivPlatformGenesis: public DivPlatformOPN { int ins; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; int vol, outVol; - unsigned char pan; + unsigned char pan, opMask; bool dacMode; int dacPeriod; @@ -85,6 +85,7 @@ class DivPlatformGenesis: public DivPlatformOPN { vol(0), outVol(0), pan(3), + opMask(15), dacMode(false), dacPeriod(0), dacRate(0), diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 178ef05b..46696b8a 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -386,7 +386,7 @@ void DivPlatformYM2203::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); chan[i].keyOn=false; } } @@ -409,6 +409,11 @@ int DivPlatformYM2203::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 0395c9d0..921c1d94 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -43,7 +43,7 @@ class DivPlatformYM2203: public DivPlatformOPN { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; @@ -66,6 +66,7 @@ class DivPlatformYM2203: public DivPlatformOPN { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 244e4a16..801bab32 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -582,7 +582,7 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); chan[i].keyOn=false; } } @@ -683,6 +683,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 7a471b8b..1689436e 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -48,7 +48,7 @@ class DivPlatformYM2608: public DivPlatformOPN { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; @@ -72,6 +72,7 @@ class DivPlatformYM2608: public DivPlatformOPN { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 064b49f2..63a6d72e 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -619,7 +619,7 @@ void DivPlatformYM2610::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); chan[i].keyOn=false; } } @@ -727,6 +727,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 5e22ed2a..0e363329 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -76,7 +76,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; - unsigned char pan; + unsigned char pan, opMask; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -107,7 +107,8 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { vol(0), outVol(15), sample(-1), - pan(3) {} + pan(3), + opMask(15) {} }; Channel chan[14]; DivDispatchOscBuffer* oscBuf[14]; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 8d283374..f01f39b4 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -601,7 +601,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { chan[i].freqChanged=false; } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); chan[i].keyOn=false; } } @@ -709,6 +709,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 703f8dd4..87500e9b 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -43,7 +43,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; - unsigned char pan; + unsigned char pan, opMask; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -74,7 +74,8 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { vol(0), outVol(15), sample(-1), - pan(3) {} + pan(3), + opMask(15) {} }; Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 563e0579..3ffc86a8 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1737,6 +1737,7 @@ void FurnaceGUI::drawInsEdit() { int opCount=4; if (ins->type==DIV_INS_OPLL) opCount=2; if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; + bool opsAreMutable=(ins->type==DIV_INS_FM); if (ImGui::BeginTabItem("FM")) { if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { @@ -2091,16 +2092,27 @@ void FurnaceGUI::drawInsEdit() { if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y; ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + String opNameLabel; if (ins->type==DIV_INS_OPL_DRUMS) { - ImGui::Text("%s",oplDrumNames[i]); + opNameLabel=fmt::sprintf("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { - ImGui::Text("Kick"); + opNameLabel="Kick"; } else { - ImGui::Text("Env"); + opNameLabel="Env"; } } else { - ImGui::Text("OP%d",i+1); + opNameLabel=fmt::sprintf("OP%d",i+1); + } + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(opNameLabel.c_str())) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(opNameLabel.c_str()); } // drag point @@ -2388,11 +2400,20 @@ void FurnaceGUI::drawInsEdit() { } else { snprintf(tempID,1024,"Operator %d",i+1); } - float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x); + float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x-(opsAreMutable?(ImGui::GetStyle().FramePadding.x*2.0f):0.0f)); OP_DRAG_POINT; ImGui::SameLine(); ImGui::SetCursorPosX(nextCursorPosX); - ImGui::TextUnformatted(tempID); + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(tempID)) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(tempID); + } float sliderHeight=200.0f*dpiScale; float waveWidth=140.0*dpiScale; @@ -2824,18 +2845,29 @@ void FurnaceGUI::drawInsEdit() { } ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + String opNameLabel; OP_DRAG_POINT; ImGui::SameLine(); if (ins->type==DIV_INS_OPL_DRUMS) { - ImGui::Text("%s",oplDrumNames[i]); + opNameLabel=fmt::sprintf("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { - ImGui::Text("Envelope 2 (kick only)"); + opNameLabel="Envelope 2 (kick only)"; } else { - ImGui::Text("Envelope"); + opNameLabel="Envelope"; } } else { - ImGui::Text("OP%d",i+1); + opNameLabel=fmt::sprintf("OP%d",i+1); + } + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(opNameLabel.c_str())) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(opNameLabel.c_str()); } ImGui::SameLine(); From d64e20e85958d12451b94a01173e01786a07fea0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Sep 2022 00:51:09 -0500 Subject: [PATCH 87/93] implement operator muting for ExtCh mode --- src/engine/platform/genesisext.cpp | 9 ++++++--- src/engine/platform/genesisext.h | 3 ++- src/engine/platform/ym2203ext.cpp | 9 ++++++--- src/engine/platform/ym2203ext.h | 4 ++-- src/engine/platform/ym2608ext.cpp | 9 ++++++--- src/engine/platform/ym2608ext.h | 4 ++-- src/engine/platform/ym2610bext.cpp | 9 ++++++--- src/engine/platform/ym2610bext.h | 4 ++-- src/engine/platform/ym2610ext.cpp | 9 ++++++--- src/engine/platform/ym2610ext.h | 4 ++-- 10 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index dfd7d514..3f03e622 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -69,6 +69,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); @@ -412,7 +413,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -459,10 +460,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index 07e0d5cc..d4dd93e7 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; OpChannel(): @@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { keyOff(false), portaPause(false), inPorta(false), + mask(true), vol(0), pan(3) {} }; diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 3ff24eb7..c7080d43 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 1a398d1a..d25ca45d 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 63503ccc..c6d7e03b 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index bc3d4f99..21c8a35c 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 7c8247ff..f55e6561 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index 732678fe..e60f9713 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -27,12 +27,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index b2bd06a8..6cee242f 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[1]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 119d6356..07d855c0 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; From 6e1f54b77736d1040f7001c3b46f1b174c52e99a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Sep 2022 01:50:27 -0500 Subject: [PATCH 88/93] YM2612: implement OpMask will be done for OPM and the rest of the OPN chips later --- src/engine/platform/genesis.cpp | 7 ++++++- src/engine/platform/genesis.h | 3 ++- src/gui/insEdit.cpp | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 5b4b535b..515f0c0a 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) { } chan[i].freqChanged=false; } - if (chan[i].keyOn) { + if (chan[i].keyOn || chan[i].opMaskChanged) { if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 34a4d4f2..8588b21d 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -45,7 +45,7 @@ class DivPlatformGenesis: public DivPlatformOPN { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note; int ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged; int vol, outVol; unsigned char pan, opMask; @@ -82,6 +82,7 @@ class DivPlatformGenesis: public DivPlatformOPN { furnaceDac(false), inPorta(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), pan(3), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 3ffc86a8..1c49bf99 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3101,7 +3101,14 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("PM Depth",&ins->std.ex2Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("LFO Speed",&ins->std.ex3Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("LFO Shape",&ins->std.waveMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); + } + if (ins->type==DIV_INS_FM) { macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,fmOperatorBits)); + } else if (ins->type==DIV_INS_OPZ) { + macroList.push_back(FurnaceGUIMacroDesc("AM Depth 2",&ins->std.ex5Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("PM Depth 2",&ins->std.ex6Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO2 Speed",&ins->std.ex7Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO2 Shape",&ins->std.ex8Macro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); } drawMacros(macroList); ImGui::EndTabItem(); From a08ae8cce7e0b8d41a79db90de61cb46020ef184 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Sep 2022 02:26:00 -0500 Subject: [PATCH 89/93] implement OpMask for the rest of FM chips whether supported --- src/engine/platform/arcade.cpp | 7 ++++++- src/engine/platform/arcade.h | 3 ++- src/engine/platform/ym2203.cpp | 7 ++++++- src/engine/platform/ym2203.h | 3 ++- src/engine/platform/ym2608.cpp | 7 ++++++- src/engine/platform/ym2608.h | 3 ++- src/engine/platform/ym2610.cpp | 7 ++++++- src/engine/platform/ym2610.h | 3 ++- src/engine/platform/ym2610b.cpp | 7 ++++++- src/engine/platform/ym2610b.h | 3 ++- 10 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 7d44fe8a..6e925c9a 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -347,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) { immWrite(i+0x30,chan[i].freq<<2); chan[i].freqChanged=false; } - if (chan[i].keyOn) { + if (chan[i].keyOn || chan[i].opMaskChanged) { immWrite(0x08,(chan[i].opMask<<3)|i); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index 683394e3..2a9c2b40 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -43,7 +43,7 @@ class DivPlatformArcade: public DivPlatformOPM { int freq, baseFreq, pitch, pitch2, note; int ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged; int vol, outVol; unsigned char chVolL, chVolR, opMask; void macroInit(DivInstrument* which) { @@ -68,6 +68,7 @@ class DivPlatformArcade: public DivPlatformOPM { portaPause(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), chVolL(127), diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 46696b8a..1c348800 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -273,6 +273,10 @@ void DivPlatformYM2203::tick(bool sysTick) { chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -385,8 +389,9 @@ void DivPlatformYM2203::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { + if (chan[i].keyOn || chan[i].opMaskChanged) { immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 921c1d94..75660365 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -45,7 +45,7 @@ class DivPlatformYM2203: public DivPlatformOPN { int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; DivMacroInt std; @@ -76,6 +76,7 @@ class DivPlatformYM2203: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1) {} diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 801bab32..ec90c1c6 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -441,6 +441,10 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -581,8 +585,9 @@ void DivPlatformYM2608::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { + if (chan[i].keyOn || chan[i].opMaskChanged) { immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 1689436e..ed850bf8 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -50,7 +50,7 @@ class DivPlatformYM2608: public DivPlatformOPN { int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; unsigned char pan; @@ -82,6 +82,7 @@ class DivPlatformYM2608: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 63a6d72e..6a8509d5 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -482,6 +482,10 @@ void DivPlatformYM2610::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -618,8 +622,9 @@ void DivPlatformYM2610::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { + if (chan[i].keyOn || chan[i].opMaskChanged) { immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 0e363329..4f1a1664 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -73,7 +73,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; unsigned char pan, opMask; @@ -104,6 +104,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index f01f39b4..31cd7b3f 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -465,6 +465,10 @@ void DivPlatformYM2610B::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -600,8 +604,9 @@ void DivPlatformYM2610B::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { + if (chan[i].keyOn || chan[i].opMaskChanged) { immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 87500e9b..1d5fc1f3 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -40,7 +40,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; unsigned char pan, opMask; @@ -71,6 +71,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), From d19c6fc236ffe0e789a990e8dbbced41a4e26289 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Sep 2022 03:29:28 -0500 Subject: [PATCH 90/93] GUI: add operator copy --- src/gui/insEdit.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1c49bf99..a4b99d14 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1564,20 +1564,32 @@ void FurnaceGUI::drawMacros(std::vector& macros) { opToMove=i; \ ImGui::SetDragDropPayload("FUR_OP",NULL,0,ImGuiCond_Once); \ ImGui::Button(ICON_FA_ARROWS "##SysDrag"); \ + ImGui::SameLine(); \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + ImGui::Text("(copying)"); \ + } else { \ + ImGui::Text("(swapping)"); \ + } \ ImGui::EndDragDropSource(); \ } else if (ImGui::IsItemHovered()) { \ - ImGui::SetTooltip("(drag to swap operators)"); \ + ImGui::SetTooltip("- drag to swap operator\n- shift-drag to copy operator"); \ } \ if (ImGui::BeginDragDropTarget()) { \ const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_OP"); \ if (dragItem!=NULL) { \ if (dragItem->IsDataType("FUR_OP")) { \ if (opToMove!=i && opToMove>=0) { \ - e->lockEngine([this,ins,i]() { \ - DivInstrumentFM::Operator origOp=ins->fm.op[orderedOps[opToMove]]; \ - ins->fm.op[orderedOps[opToMove]]=ins->fm.op[orderedOps[i]]; \ - ins->fm.op[orderedOps[i]]=origOp; \ - }); \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + e->lockEngine([this,ins,i]() { \ + ins->fm.op[orderedOps[i]]=ins->fm.op[orderedOps[opToMove]]; \ + }); \ + } else { \ + e->lockEngine([this,ins,i]() { \ + DivInstrumentFM::Operator origOp=ins->fm.op[orderedOps[opToMove]]; \ + ins->fm.op[orderedOps[opToMove]]=ins->fm.op[orderedOps[i]]; \ + ins->fm.op[orderedOps[i]]=origOp; \ + }); \ + } \ PARAMETER; \ } \ opToMove=-1; \ From 146255b08e03250486d165dab911cf24f61fd8f7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Sep 2022 03:29:36 -0500 Subject: [PATCH 91/93] OPZ: SysEx fixes and notes --- src/engine/platform/sound/ymfm/ymfm_opz.cpp | 4 ++++ src/gui/sysEx.cpp | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 94123bf6..37c6a5fc 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 // note from tildearrow: // - are you kidding? I have to write to this "load preset" register before keying on? + // another note from tildearrow: + // - see https://github.com/110-kenichi/ymfm/blob/main/src/ymfm_opz.cpp + // - is 0x08 the actual key on register just like OPM? + // - if so then what's bit 5? if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/) { channel = bitfield(index, 0, 3); diff --git a/src/gui/sysEx.cpp b/src/gui/sysEx.cpp index 5b2b6657..f8849b56 100644 --- a/src/gui/sysEx.cpp +++ b/src/gui/sysEx.cpp @@ -1,6 +1,17 @@ #include "gui.h" #include "../ta-log.h" +// table taken from https://nornand.hatenablog.com/entry/2020/11/21/201911 +// Yamaha why didn't you just use 0-127 as it should be? +const unsigned char tlTable[100]={ + 127, 122, 118, 114, 110, 107, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 85, 84, 82, 81, + // desde aquí la tabla consiste de valores que bajan de 1 en 1 + 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, + 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, + 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +}; + bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { SafeReader reader(data,len); @@ -137,7 +148,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { op.sl=15-reader.readC(); reader.readC(); // LS - ignore op.am=(reader.readC()&0x40)?1:0; - op.tl=3+((99-reader.readC())*124)/99; + op.tl=tlTable[reader.readC()%100]; unsigned char freq=reader.readC(); logV("OP%d freq: %d",i,freq); op.mult=freq>>2; From c99ac948389b92a8414a02c0986541aa9421a25c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 14 Sep 2022 00:19:24 -0500 Subject: [PATCH 92/93] YM2612: fix clicks when muting with CSM on --- src/engine/platform/genesisext.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 3f03e622..d22f71ac 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -25,6 +25,7 @@ #define CHIP_DIVIDER fmDivBase #define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) +#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3]) int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -73,7 +74,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -124,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -398,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) { rWrite(baseAddr+0x40,op.tl); immWrite(baseAddr+0x40,op.tl); } + + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } static int opChanOffsL[4]={ @@ -547,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i==2) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } else { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; From 480243b652d1dea8be853561a9c8b47321240095 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 14 Sep 2022 00:51:45 -0500 Subject: [PATCH 93/93] what? --- src/engine/platform/genesisext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index d22f71ac..a2df33c4 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -551,7 +551,7 @@ void DivPlatformGenesisExt::forceIns() { } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (i==2) { - rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } else { rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); }