From f56c77b032c1f0f7fd0e0cef25e8932a62fe5a9b Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 17 Sep 2024 13:28:00 +0200 Subject: [PATCH] DX7: make loading of .syx patches integrated with "load SYNTH" UI very WIP, I am learning how the "preset slot system" works (and doesn't work) in real time. Need to assign a "unsaved slot" to a loaded DX7 patch somehow, otherwise unsaved changes get lost if you navigate to another preset and then back Notably, the location is no longer (sdcard)/DX7 but the SYNTH folder and its subfolders. Approximately treat a 32-preset cartridge as a folder. --- src/deluge/gui/menu_item/dx/browse.cpp | 39 --- src/deluge/gui/menu_item/dx/browse.h | 33 --- src/deluge/gui/menu_item/dx/cartridge.cpp | 178 ------------ src/deluge/gui/ui/browser/dx_browser.cpp | 125 --------- src/deluge/gui/ui/browser/dx_browser.h | 33 --- src/deluge/gui/ui/load/load_dx_cartridge.cpp | 253 ++++++++++++++++++ .../load/load_dx_cartridge.h} | 39 +-- .../gui/ui/load/load_instrument_preset_ui.cpp | 70 ++++- src/deluge/gui/ui/menus.cpp | 4 +- src/deluge/gui/views/view.cpp | 11 + .../processing/sound/sound_instrument.cpp | 4 + .../processing/sound/sound_instrument.h | 7 + 12 files changed, 367 insertions(+), 429 deletions(-) delete mode 100644 src/deluge/gui/menu_item/dx/browse.cpp delete mode 100644 src/deluge/gui/menu_item/dx/browse.h delete mode 100644 src/deluge/gui/menu_item/dx/cartridge.cpp delete mode 100644 src/deluge/gui/ui/browser/dx_browser.cpp delete mode 100644 src/deluge/gui/ui/browser/dx_browser.h create mode 100644 src/deluge/gui/ui/load/load_dx_cartridge.cpp rename src/deluge/gui/{menu_item/dx/cartridge.h => ui/load/load_dx_cartridge.h} (56%) diff --git a/src/deluge/gui/menu_item/dx/browse.cpp b/src/deluge/gui/menu_item/dx/browse.cpp deleted file mode 100644 index 7a5614bec8..0000000000 --- a/src/deluge/gui/menu_item/dx/browse.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright © 2015-2023 Synthstrom Audible Limited - * - * This file is part of The Synthstrom Audible Deluge Firmware. - * - * The Synthstrom Audible Deluge Firmware 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 3 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, see . - */ - -#include "browse.h" -#include "gui/ui/browser/dx_browser.h" -#include "gui/ui/sound_editor.h" -#include "gui/ui_timer_manager.h" - -namespace deluge::gui::menu_item { - -DxBrowseMenu dxBrowseMenu{l10n::String::STRING_FOR_DX_BROWSER}; - -void DxBrowseMenu::beginSession(MenuItem* navigatedBackwardFrom) { - soundEditor.shouldGoUpOneLevelOnBegin = true; - // if (getRootUI() == &keyboardScreen && currentUIMode == UI_MODE_AUDITIONING) { - // keyboardScreen.exitAuditionMode(); - // } - bool success = openUI(&dxBrowser); - if (!success) { - // if (getCurrentUI() == &soundEditor) soundEditor.goUpOneLevel(); - uiTimerManager.unsetTimer(TimerName::SHORTCUT_BLINK); - } -} - -} // namespace deluge::gui::menu_item diff --git a/src/deluge/gui/menu_item/dx/browse.h b/src/deluge/gui/menu_item/dx/browse.h deleted file mode 100644 index d9d28a32ad..0000000000 --- a/src/deluge/gui/menu_item/dx/browse.h +++ /dev/null @@ -1,33 +0,0 @@ - -/* - * Copyright © 2015-2023 Synthstrom Audible Limited - * - * This file is part of The Synthstrom Audible Deluge Firmware. - * - * The Synthstrom Audible Deluge Firmware 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 3 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, see . - */ - -#pragma once - -#include "gui/menu_item/menu_item.h" - -namespace deluge::gui::menu_item { - -class DxBrowseMenu final : public MenuItem { -public: - using MenuItem::MenuItem; - DxBrowseMenu(l10n::String newName) : MenuItem(newName) {} - void beginSession(MenuItem* navigatedBackwardFrom) override; -}; - -extern DxBrowseMenu dxBrowseMenu; -} // namespace deluge::gui::menu_item diff --git a/src/deluge/gui/menu_item/dx/cartridge.cpp b/src/deluge/gui/menu_item/dx/cartridge.cpp deleted file mode 100644 index 47660a9138..0000000000 --- a/src/deluge/gui/menu_item/dx/cartridge.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright © 2015-2023 Synthstrom Audible Limited - * - * This file is part of The Synthstrom Audible Deluge Firmware. - * - * The Synthstrom Audible Deluge Firmware 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 3 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, see . - */ - -#include "cartridge.h" -#include "dsp/dx/engine.h" -#include "gui/ui/browser/dx_browser.h" -#include "gui/ui/sound_editor.h" -#include "gui/ui_timer_manager.h" -#include "hid/display/display.h" -#include "memory/general_memory_allocator.h" -#include "model/song/song.h" -#include "processing/sound/sound.h" -#include "processing/source.h" -#include "storage/DX7Cartridge.h" -#include "util/container/static_vector.hpp" - -static bool openFile(const char* path, DX7Cartridge* data) { - using deluge::l10n::String; - bool didLoad = false; - const int minSize = SMALL_SYSEX_SIZE; - - FILINFO fno; - int result = f_stat(path, &fno); - FSIZE_t fileSize = fno.fsize; - if (fileSize < minSize) { - display->displayPopup(get(String::STRING_FOR_DX_ERROR_FILE_TOO_SMALL)); - } - - FIL file; - // Open the file - result = f_open(&file, path, FA_READ); - if (result != FR_OK) { - display->displayPopup(get(String::STRING_FOR_DX_ERROR_READ_ERROR)); - return false; - } - - String error = String::EMPTY_STRING; - UINT numBytesRead; - int readsize = std::min((int)fileSize, 8192); - auto buffer = (uint8_t*)GeneralMemoryAllocator::get().allocLowSpeed(readsize); - if (!buffer) { - display->displayPopup(get(String::STRING_FOR_DX_ERROR_READ_ERROR)); - goto close; - } - result = f_read(&file, buffer, fileSize, &numBytesRead); - if (numBytesRead < minSize) { - display->displayPopup(get(String::STRING_FOR_DX_ERROR_FILE_TOO_SMALL)); - goto free; - } - - error = data->load(buffer, numBytesRead); - if (error != String::EMPTY_STRING) { - display->displayPopup(get(error), 3); - } - else { - didLoad = true; - } - -free: - GeneralMemoryAllocator::get().dealloc(buffer); -close: - f_close(&file); - return didLoad; -} -namespace deluge::gui::menu_item { - -DxCartridge dxCartridge{l10n::String::STRING_FOR_DX_CARTRIDGE}; - -void DxCartridge::beginSession(MenuItem* navigatedBackwardFrom) { - readValueAgain(); -} - -void DxCartridge::readValueAgain() { - if (pd == nullptr) { - return; - } - if (display->haveOLED()) { - renderUIsForOled(); - } - else { - drawValue(); - } - - DxPatch* patch = soundEditor.currentSource->ensureDxPatch(); - pd->unpackProgram(patch->params, currentValue); - Instrument* instrument = getCurrentInstrument(); - if (instrument->type == OutputType::SYNTH && !instrument->existsOnCard) { - char name[11]; - pd->getProgramName(currentValue, name); - if (name[0] != 0) { - instrument->name.set(name); - } - } -} - -void DxCartridge::drawPixelsForOled() { - if (pd == nullptr) { - return; - } - char names[32][11]; - pd->getProgramNames(names); - - static_vector itemNames = {}; - for (int i = 0; i < pd->numPatches(); i++) { - itemNames.push_back(names[i]); - } - drawItemsForOled(itemNames, currentValue - scrollPos, scrollPos); -} - -void DxCartridge::drawValue() { - char names[32][11]; - pd->getProgramNames(names); - - display->setScrollingText(names[currentValue]); -} - -bool DxCartridge::tryLoad(const char* path) { - if (pd == nullptr) { - pd = new DX7Cartridge(); - } - currentValue = 0; - scrollPos = 0; - - return openFile(path, pd); -} - -void DxCartridge::selectEncoderAction(int32_t offset) { - int32_t newValue = currentValue + offset; - int32_t numValues = pd->numPatches(); - - if (display->haveOLED()) { - if (newValue >= numValues || newValue < 0) { - return; - } - } - else { - if (newValue >= numValues) { - newValue %= numValues; - } - else if (newValue < 0) { - newValue = (newValue % numValues + numValues) % numValues; - } - } - - currentValue = newValue; - - if (display->haveOLED()) { - if (currentValue < scrollPos) { - scrollPos = currentValue; - } - else if (currentValue >= scrollPos + kOLEDMenuNumOptionsVisible) { - scrollPos++; - } - } - - readValueAgain(); -} - -MenuItem* DxCartridge::selectButtonPress() { - soundEditor.exitCompletely(); - return nullptr; -} - -} // namespace deluge::gui::menu_item diff --git a/src/deluge/gui/ui/browser/dx_browser.cpp b/src/deluge/gui/ui/browser/dx_browser.cpp deleted file mode 100644 index e135b281d9..0000000000 --- a/src/deluge/gui/ui/browser/dx_browser.cpp +++ /dev/null @@ -1,125 +0,0 @@ - -/* - * Copyright © 2015-2023 Synthstrom Audible Limited - * - * This file is part of The Synthstrom Audible Deluge Firmware. - * - * The Synthstrom Audible Deluge Firmware 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 3 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, see . - */ - -#include "dx_browser.h" -#include "definitions_cxx.hpp" -#include "gui/menu_item/dx/cartridge.h" -#include "gui/ui/sound_editor.h" -#include "hid/display/oled.h" - -using namespace deluge::gui; - -DxSyxBrowser::DxSyxBrowser() { - fileIcon = deluge::hid::display::OLED::waveIcon; - title = "DX7 syx files"; - shouldWrapFolderContents = false; -} - -static char const* allowedFileExtensionsSyx[] = {"SYX", NULL}; -bool DxSyxBrowser::opened() { - - bool success = Browser::opened(); - if (!success) - return false; - - allowedFileExtensions = allowedFileExtensionsSyx; - - allowFoldersSharingNameWithFile = true; - outputTypeToLoad = OutputType::NONE; - qwertyVisible = false; - - fileIndexSelected = 0; - - Error error = StorageManager::initSD(); - if (error != Error::NONE) - goto sdError; - - currentDir.set("DX7"); - - // TODO: fill in last used name! - error = arrivedInNewFolder(1, "", "DX7"); - if (error != Error::NONE) - goto sdError; - - return true; -sdError: - display->displayError(error); - return false; -} - -// TODO: this is identical to SampleBrowser, move to parent class? -Error DxSyxBrowser::getCurrentFilePath(String* path) { - Error error; - - path->set(¤tDir); - int oldLength = path->getLength(); - if (oldLength) { - error = path->concatenateAtPos("/", oldLength); - if (error != Error::NONE) { -gotError: - path->clear(); - return error; - } - } - - FileItem* currentFileItem = getCurrentFileItem(); - - error = path->concatenate(¤tFileItem->filename); - if (error != Error::NONE) - goto gotError; - - return Error::NONE; -} - -void DxSyxBrowser::enterKeyPress() { - FileItem* currentFileItem = getCurrentFileItem(); - if (!currentFileItem) { - return; - } - - if (currentFileItem->isFolder) { - // [SIC] - char const* filenameChars = - currentFileItem->filename - .get(); // Extremely weirdly, if we try to just put this inside the parentheses in the next line, - // it returns an empty string (¬hing). Surely this is a compiler error?? - - Error error = goIntoFolder(filenameChars); - if (error != Error::NONE) { - display->displayError(error); - close(); // Don't use goBackToSoundEditor() because that would do a left-scroll - return; - } - } - else { - // TODO: c.f. slotbrowser, we might just be able to pass a file pointer to the FAT loader - String path; - getCurrentFilePath(&path); - close(); - - if (!path.isEmpty()) { - if (menu_item::dxCartridge.tryLoad(path.get())) { - soundEditor.enterSubmenu(&menu_item::dxCartridge); - } - } - - // dx7ui.openFile(path.get()); - } -} - -DxSyxBrowser dxBrowser{}; diff --git a/src/deluge/gui/ui/browser/dx_browser.h b/src/deluge/gui/ui/browser/dx_browser.h deleted file mode 100644 index fedab9be5d..0000000000 --- a/src/deluge/gui/ui/browser/dx_browser.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2015-2023 Synthstrom Audible Limited - * - * This file is part of The Synthstrom Audible Deluge Firmware. - * - * The Synthstrom Audible Deluge Firmware 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 3 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, see . - */ - -#pragma once - -#include "definitions_cxx.hpp" -#include "gui/ui/browser/browser.h" -#include "hid/button.h" - -class DxSyxBrowser final : public Browser { -public: - DxSyxBrowser(); - bool opened(); - void enterKeyPress(); - Error getCurrentFilePath(String* path) override; - const char* getName() { return "dx_browser"; } -}; - -extern DxSyxBrowser dxBrowser; diff --git a/src/deluge/gui/ui/load/load_dx_cartridge.cpp b/src/deluge/gui/ui/load/load_dx_cartridge.cpp new file mode 100644 index 0000000000..9cc0a9c687 --- /dev/null +++ b/src/deluge/gui/ui/load/load_dx_cartridge.cpp @@ -0,0 +1,253 @@ + +#include "load_dx_cartridge.h" +#include "dsp/dx/dx7note.h" +#include "ff.h" +#include "gui/l10n/l10n.h" +#include "gui/ui/keyboard/keyboard_screen.h" +#include "gui/ui/load/load_instrument_preset_ui.h" +#include "gui/views/automation_view.h" +#include "gui/views/instrument_clip_view.h" +#include "hid/display/display.h" +#include "hid/led/indicator_leds.h" +#include "hid/led/pad_leds.h" +#include "memory/general_memory_allocator.h" +#include "model/song/song.h" +#include "processing/sound/sound.h" +#include "processing/sound/sound_instrument.h" +#include "util/container/static_vector.hpp" + +using namespace deluge; + +static bool openFile(const char* path, DX7Cartridge* data) { + using deluge::l10n::String; + bool didLoad = false; + const int minSize = SMALL_SYSEX_SIZE; + + FILINFO fno; + int result = f_stat(path, &fno); + FSIZE_t fileSize = fno.fsize; + if (fileSize < minSize) { + display->displayPopup(get(String::STRING_FOR_DX_ERROR_FILE_TOO_SMALL)); + } + + FIL file; + // Open the file + result = f_open(&file, path, FA_READ); + if (result != FR_OK) { + display->displayPopup(get(String::STRING_FOR_DX_ERROR_READ_ERROR)); + return false; + } + + String error = String::EMPTY_STRING; + UINT numBytesRead; + int readsize = std::min((int)fileSize, 8192); + auto buffer = (uint8_t*)GeneralMemoryAllocator::get().allocLowSpeed(readsize); + if (!buffer) { + display->displayPopup(get(String::STRING_FOR_DX_ERROR_READ_ERROR)); + goto close; + } + result = f_read(&file, buffer, fileSize, &numBytesRead); + if (numBytesRead < minSize) { + display->displayPopup(get(String::STRING_FOR_DX_ERROR_FILE_TOO_SMALL)); + goto free; + } + + error = data->load(buffer, numBytesRead); + if (error != String::EMPTY_STRING) { + display->displayPopup(get(error), 3); + } + else { + didLoad = true; + } + +free: + GeneralMemoryAllocator::get().dealloc(buffer); +close: + f_close(&file); + return didLoad; +} + +bool LoadDxCartridgeUI::tryLoad(const char* path) { + if (pd == nullptr) { + pd = new DX7Cartridge(); + } + currentValue = 0; + scrollPos = 0; + + return openFile(path, pd); +} + +void LoadDxCartridgeUI::readValue() { + if (pd == nullptr) { + return; + } + if (display->haveOLED()) { + renderUIsForOled(); + } + else { + drawValue(); + } + + DxPatch* patch = currentSound->sources[0].ensureDxPatch(); + pd->unpackProgram(patch->params, currentValue); + currentSound->unassignAllVoices(); + // TODO: redundant with currentSound :p + Instrument* instrument = getCurrentInstrument(); + if (instrument->type == OutputType::SYNTH && !instrument->existsOnCard) { + char name[11]; + pd->getProgramName(currentValue, name); + if (name[0] != 0) { + instrument->name.set(name); + } + ((SoundInstrument *)instrument)->syxSlot = currentValue; + } +} + +void LoadDxCartridgeUI::selectEncoderAction(int8_t offset) { + navigate(offset, !display->haveOLED()); + if (display->haveOLED()) { + if (currentValue < scrollPos) { + scrollPos = currentValue; + } + else if (currentValue >= scrollPos + kOLEDMenuNumOptionsVisible) { + scrollPos++; + } + } + +} + +void LoadDxCartridgeUI::navigate(int8_t offset, bool wrapAround) { + int32_t newValue = currentValue + offset; + int32_t numValues = pd->numPatches(); + + if (wrapAround) { + if (newValue >= numValues) { + newValue %= numValues; + } + else if (newValue < 0) { + newValue = (newValue % numValues + numValues) % numValues; + } + } + else { + if (newValue >= numValues || newValue < 0) { + return; + } + } + + currentValue = newValue; + + readValue(); +} + +ActionResult LoadDxCartridgeUI::buttonAction(deluge::hid::Button b, bool on, bool inCardRoutine) { + using namespace deluge::hid::button; + if (b == BACK) { + if (on && !currentUIMode) { + close(); + // we cannot "stack" them as we like to see through KeyboardScreen + // in this UI + openUI(&loadInstrumentPresetUI); + } + } else if (b == LOAD || b == SELECT_ENC) { + if (on && !currentUIMode) { + close(); + } + } else if (b == KEYBOARD && on) { + if (inCardRoutine) { + return ActionResult::REMIND_ME_OUTSIDE_CARD_ROUTINE; + } + // This is a duplicate of SoundEditor, refactor! + Clip* clip = getCurrentClip(); + + if (getRootUI() == &keyboardScreen) { + if (clip->onAutomationClipView) { + swapOutRootUILowLevel(&automationView); + automationView.openedInBackground(); + } + + else { + swapOutRootUILowLevel(&instrumentClipView); + instrumentClipView.openedInBackground(); + } + } + else if (getRootUI() == &instrumentClipView) { + swapOutRootUILowLevel(&keyboardScreen); + keyboardScreen.openedInBackground(); + } + else if (getRootUI() == &automationView) { + if (automationView.onMenuView) { + clip->onAutomationClipView = false; + automationView.onMenuView = false; + indicator_leds::setLedState(IndicatorLED::CLIP_VIEW, true); + } + automationView.resetInterpolationShortcutBlinking(); + automationView.resetPadSelectionShortcutBlinking(); + automationView.resetSelectedNoteRowBlinking(); + swapOutRootUILowLevel(&keyboardScreen); + keyboardScreen.openedInBackground(); + } + + PadLEDs::reassessGreyout(); + + indicator_leds::setLedState(IndicatorLED::KEYBOARD, getRootUI() == &keyboardScreen); + } + return ActionResult::NOT_DEALT_WITH; +} + +ActionResult LoadDxCartridgeUI::padAction(int32_t x, int32_t y, int32_t on) { + if (sdRoutineLock) { + return ActionResult::REMIND_ME_OUTSIDE_CARD_ROUTINE; + } + + // TODO: very similar to soundEditor, share? + RootUI* rootUI = getRootUI(); + if (rootUI == &keyboardScreen) { + keyboardScreen.padAction(x, y, on); + return ActionResult::DEALT_WITH; + } + + // Audition pads + else if (rootUI == &instrumentClipView) { + if (x == kDisplayWidth + 1) { + instrumentClipView.padAction(x, y, on); + return ActionResult::DEALT_WITH; + } + } + + else if (rootUI == &automationView) { + ActionResult result = automationView.padAction(x, y, on); + if (result == ActionResult::DEALT_WITH) { + return result; + } + } + + return ActionResult::NOT_DEALT_WITH; +} + +void LoadDxCartridgeUI::renderOLED(deluge::hid::display::oled_canvas::Canvas& canvas) { + if (!currentSound->syxPath.isEmpty()) { + canvas.drawScreenTitle(currentSound->syxPath.get()); + } + + if (pd == nullptr) { + return; + } + char names[32][11]; + pd->getProgramNames(names); + + static_vector itemNames = {}; + for (int i = 0; i < pd->numPatches(); i++) { + itemNames.push_back(names[i]); + } + MenuItem::drawItemsForOled(itemNames, currentValue - scrollPos, scrollPos); +} + +void LoadDxCartridgeUI::drawValue() { + char names[32][11]; + pd->getProgramNames(names); + + display->setScrollingText(names[currentValue]); +} + + +LoadDxCartridgeUI loadDxCartridgeUI; diff --git a/src/deluge/gui/menu_item/dx/cartridge.h b/src/deluge/gui/ui/load/load_dx_cartridge.h similarity index 56% rename from src/deluge/gui/menu_item/dx/cartridge.h rename to src/deluge/gui/ui/load/load_dx_cartridge.h index 1d3fab9b29..93c6b1284d 100644 --- a/src/deluge/gui/menu_item/dx/cartridge.h +++ b/src/deluge/gui/ui/load/load_dx_cartridge.h @@ -1,6 +1,6 @@ /* - * Copyright © 2015-2023 Synthstrom Audible Limited + * Copyright © 2018-2023 Synthstrom Audible Limited * * This file is part of The Synthstrom Audible Deluge Firmware. * @@ -18,30 +18,35 @@ #pragma once -#include "gui/menu_item/menu_item.h" +#include "definitions_cxx.hpp" +#include "gui/ui/ui.h" +#include "storage/DX7Cartridge.h" -class DX7Cartridge; +class SoundInstrument; -namespace deluge::gui::menu_item { +class LoadDxCartridgeUI : public UI { + void renderOLED(deluge::hid::display::oled_canvas::Canvas& canvas) override; + void drawValue(); + + void selectEncoderAction(int8_t offset) override; + ActionResult buttonAction(deluge::hid::Button b, bool on, bool inCardRoutine) override; + ActionResult padAction(int32_t x, int32_t y, int32_t on) override; + UIType getUIType() override { return UIType::BROWSER; } + void readValue(); -class DxCartridge final : public MenuItem { public: - using MenuItem::MenuItem; - DxCartridge(l10n::String newName) : MenuItem(newName), pd(nullptr) {} - void beginSession(MenuItem* navigatedBackwardFrom) override; - bool tryLoad(const char* path); - void drawPixelsForOled() override; - void readValueAgain() final; - void selectEncoderAction(int32_t offset) final; - MenuItem* selectButtonPress() final; - void drawValue(); + LoadDxCartridgeUI() {}; + + bool tryLoad(const char* path); + void navigate(int8_t offset, bool wrapAround = true); // this thing is big. allocate external mem on demand - DX7Cartridge* pd; + DX7Cartridge* pd = nullptr; int32_t currentValue = 0; int scrollPos = 0; // Each instance needs to store this separately + + SoundInstrument *currentSound = nullptr; }; -extern DxCartridge dxCartridge; -} // namespace deluge::gui::menu_item +extern LoadDxCartridgeUI loadDxCartridgeUI; diff --git a/src/deluge/gui/ui/load/load_instrument_preset_ui.cpp b/src/deluge/gui/ui/load/load_instrument_preset_ui.cpp index 9b1b593f76..47af5a4db5 100644 --- a/src/deluge/gui/ui/load/load_instrument_preset_ui.cpp +++ b/src/deluge/gui/ui/load/load_instrument_preset_ui.cpp @@ -20,6 +20,7 @@ #include "extern.h" #include "gui/context_menu/load_instrument_preset.h" #include "gui/ui/keyboard/keyboard_screen.h" +#include "gui/ui/load/load_dx_cartridge.h" #include "gui/ui/root_ui.h" #include "gui/views/arranger_view.h" #include "gui/views/instrument_clip_view.h" @@ -38,6 +39,8 @@ #include "model/instrument/midi_instrument.h" #include "model/song/song.h" #include "processing/engines/audio_engine.h" +#include "processing/sound/sound_instrument.h" +#include "storage/DX7Cartridge.h" #include "storage/audio/audio_file_manager.h" #include "storage/file_item.h" #include "storage/storage_manager.h" @@ -47,6 +50,13 @@ using namespace deluge; namespace encoders = deluge::hid::encoders; using encoders::EncoderName; +char const* allowedFileExtensionsXMLandSYX[] = {"XML", "Json", "syx", NULL}; + +static bool isSYXFile(FileItem *item) { + size_t len = item->filename.getLength(); + return(len >= 4 && strcasecmp(item->filename.get()+(len-4), ".syx") == 0); +} + LoadInstrumentPresetUI loadInstrumentPresetUI{}; LoadInstrumentPresetUI::LoadInstrumentPresetUI() { @@ -62,6 +72,7 @@ bool LoadInstrumentPresetUI::getGreyoutColsAndRows(uint32_t* cols, uint32_t* row return true; } + bool LoadInstrumentPresetUI::opened() { if (getRootUI() == &keyboardScreen) { @@ -130,12 +141,15 @@ Error LoadInstrumentPresetUI::setupForOutputType() { indicator_leds::setLedState(IndicatorLED::MIDI, false); indicator_leds::setLedState(IndicatorLED::CV, false); + allowedFileExtensions = allowedFileExtensionsXML; + if (loadingSynthToKitRow) { indicator_leds::blinkLed(IndicatorLED::SYNTH); indicator_leds::blinkLed(IndicatorLED::KIT); } else if (outputTypeToLoad == OutputType::SYNTH) { indicator_leds::blinkLed(IndicatorLED::SYNTH); + allowedFileExtensions = allowedFileExtensionsXMLandSYX; } else if (outputTypeToLoad == OutputType::MIDI_OUT) { indicator_leds::blinkLed(IndicatorLED::MIDI); @@ -289,6 +303,17 @@ void LoadInstrumentPresetUI::currentFileChanged(int32_t movementDirection) { //} } +static Error getRawFilePath(String* path, FileItem *item) { + path->set(&Browser::currentDir); + + Error error = path->concatenate("/"); + if (error != Error::NONE) { + return error; + } + +return path->concatenate(&item->filename); +} + void LoadInstrumentPresetUI::enterKeyPress() { FileItem* currentFileItem = getCurrentFileItem(); @@ -307,8 +332,46 @@ void LoadInstrumentPresetUI::enterKeyPress() { return; } } + else if (isSYXFile(currentFileItem)) { + String path; + getRawFilePath(&path, currentFileItem); - else { + // TODO: do a quick check this is a yamaha syx file - nice to leave in a slot where other SYX files can be detected as well + if (!loadDxCartridgeUI.tryLoad(path.get())) { + // display->displayPopup(path.get()); + return; + } + + // TODO: not if we currently is at a freshly loaded DX7 instrument, in case we can just modify it in place + ParamManagerForTimeline newParamManager; + SoundInstrument* newInstrument = (SoundInstrument *)StorageManager::createNewInstrument(OutputType::SYNTH, &newParamManager); + newInstrument->dirPath.set(¤tDir); + newInstrument->syxPath.set(¤tFileItem->filename); + newInstrument->setupAsBlankSynth(&newParamManager, true); + // TODO: oldInstrumentShouldBeReplaced ?????? + // This is how we feed a ParamManager into the replaceInstrument() function + currentSong->backUpParamManager((ModControllableAudio*)newInstrument->toModControllable(), nullptr, + &newParamManager, true); + currentSong->replaceInstrument(instrumentToReplace, newInstrument); + currentInstrument = newInstrument; + instrumentToReplace = newInstrument; + actionLogger.deleteAllLogs(); // Can't undo past this! + display->removeWorkingAnimation(); + close(); + + if (loadDxCartridgeUI.pd->isCartridge()) { + loadDxCartridgeUI.currentSound = newInstrument; + openUI(&loadDxCartridgeUI); + // associating the instrument with a file is tricky, we need to + // allocate an "unsaved slot" which can be navigated to.. + } else { + loadDxCartridgeUI.pd->unpackProgram(newInstrument->sources[0].dxPatch->params, 0); + currentFileItem->instrument = newInstrument; + } + + return; + + } else { if (currentInstrumentLoadError != Error::NONE) { if (loadingSynthToKitRow) { @@ -857,6 +920,11 @@ Error LoadInstrumentPresetUI::performLoad(bool doClone) { return Error::NONE; // Happens if navigate over a folder's name (Instrument stays the same), } + if (isSYXFile(currentFileItem)) { + // TODO: if this is a single-patch .syx file, treat it as a synth patch already + return Error::NONE; + } + // then back onto that neighbouring Instrument - you'd incorrectly get a "USED" error without this line. // Work out availabilityRequirement. This can't change as presets are navigated through... I don't think? diff --git a/src/deluge/gui/ui/menus.cpp b/src/deluge/gui/ui/menus.cpp index a0cb9b785e..7836418602 100644 --- a/src/deluge/gui/ui/menus.cpp +++ b/src/deluge/gui/ui/menus.cpp @@ -48,7 +48,6 @@ #include "gui/menu_item/delay/ping_pong.h" #include "gui/menu_item/delay/sync.h" #include "gui/menu_item/drum_name.h" -#include "gui/menu_item/dx/browse.h" #include "gui/menu_item/dx/engine_select.h" #include "gui/menu_item/dx/global_params.h" #include "gui/menu_item/dx/param.h" @@ -1077,8 +1076,7 @@ submenu::Modulator modulator0Menu{STRING_FOR_FM_MODULATOR_1, modulatorMenuItems, submenu::Modulator modulator1Menu{STRING_FOR_FM_MODULATOR_2, modulatorMenuItems, 1}; // Submenu::SubmenuReferringToOneThing if we need to address osc1 and osc2 directly -std::array dxMenuItems = { - &dxBrowseMenu, +std::array dxMenuItems = { &dxGlobalParams, &dxEngineSelect, }; diff --git a/src/deluge/gui/views/view.cpp b/src/deluge/gui/views/view.cpp index 8ed8bea997..540521b136 100644 --- a/src/deluge/gui/views/view.cpp +++ b/src/deluge/gui/views/view.cpp @@ -26,6 +26,7 @@ #include "gui/l10n/l10n.h" #include "gui/menu_item/colour.h" #include "gui/ui/keyboard/keyboard_screen.h" +#include "gui/ui/load/load_dx_cartridge.h" #include "gui/ui/load/load_instrument_preset_ui.h" #include "gui/ui/load/load_song_ui.h" #include "gui/ui/root_ui.h" @@ -2322,6 +2323,16 @@ void View::navigateThroughPresetsForInstrumentClip(int32_t offset, ModelStackWit // Or if we're on a Kit or Synth... else { + if (oldInstrument->type == OutputType::SYNTH) { + auto soundInstrument = (SoundInstrument *)oldInstrument; + // TODO: if the there is a syxPath, but loadDxCartridgeUI does _not_ match, we should load the right syx file + if (!soundInstrument->syxPath.isEmpty() && soundInstrument->syxSlot > -1 && loadDxCartridgeUI.currentSound == soundInstrument) { + loadDxCartridgeUI.currentValue = soundInstrument->syxSlot; + loadDxCartridgeUI.navigate(offset); + return; + } + } + PresetNavigationResult results = loadInstrumentPresetUI.doPresetNavigation(offset, oldInstrument, availabilityRequirement, false); if (results.error == Error::NO_ERROR_BUT_GET_OUT) { diff --git a/src/deluge/processing/sound/sound_instrument.cpp b/src/deluge/processing/sound/sound_instrument.cpp index 83a54f0432..15377d1780 100644 --- a/src/deluge/processing/sound/sound_instrument.cpp +++ b/src/deluge/processing/sound/sound_instrument.cpp @@ -64,6 +64,10 @@ bool SoundInstrument::writeDataToFile(Serializer& writer, Clip* clipForSavingOut } } + // sound saved elsewhere, no longer associated with syx file "slot" + syxPath.clear(false); + syxSlot = -1; + Sound::writeToFile(writer, clipForSavingOutputOnly == NULL, paramManager, clipForSavingOutputOnly ? &((InstrumentClip*)clipForSavingOutputOnly)->arpSettings : NULL, NULL); diff --git a/src/deluge/processing/sound/sound_instrument.h b/src/deluge/processing/sound/sound_instrument.h index 395ed16ea3..69694a4c2b 100644 --- a/src/deluge/processing/sound/sound_instrument.h +++ b/src/deluge/processing/sound/sound_instrument.h @@ -82,4 +82,11 @@ class SoundInstrument final : public Sound, public MelodicInstrument { ArpeggiatorBase* getArp(); char const* getXMLTag() { return "sound"; } ArpeggiatorSettings defaultArpSettings; + + // when not NULL, the syx file in `dirPath` this instrument was loaded from. Only used when loading + // to browse neighbouring patches. Ignored on save as then + // this will be saved as an .xml file with deluge setttings in additon. + String syxPath{}; + // slot number (0-31), OR -1 in case this was a single-patch syx file + int8_t syxSlot{-1}; };