gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 536104fcda25a224f4756949df44bad659900d5a
parent 8d93cf0bede9b79dac3d1875d034a59704b69588
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat, 20 Apr 2024 15:48:15 +0200

first version that compiles again

Diffstat:
Msource/CMakeLists.txt | 8+++++++-
Dsource/jucePlugin/CMakeLists.txt | 49-------------------------------------------------
Dsource/jucePlugin/PluginEditor.cpp | 85-------------------------------------------------------------------------------
Dsource/jucePlugin/PluginEditor.h | 30------------------------------
Dsource/jucePlugin/PluginEditorState.cpp | 110-------------------------------------------------------------------------------
Dsource/jucePlugin/PluginEditorState.h | 16----------------
Dsource/jucePlugin/PluginProcessor.cpp | 156-------------------------------------------------------------------------------
Dsource/jucePlugin/PluginProcessor.h | 77-----------------------------------------------------------------------------
Dsource/jucePlugin/VirusController.cpp | 832-------------------------------------------------------------------------------
Dsource/jucePlugin/VirusController.h | 180-------------------------------------------------------------------------------
Dsource/jucePlugin/ui3/ArpUserPattern.cpp | 125-------------------------------------------------------------------------------
Dsource/jucePlugin/ui3/FxPage.cpp | 30------------------------------
Dsource/jucePlugin/ui3/Leds.cpp | 56--------------------------------------------------------
Dsource/jucePlugin/ui3/PartButton.cpp | 83-------------------------------------------------------------------------------
Dsource/jucePlugin/ui3/Parts.cpp | 217-------------------------------------------------------------------------------
Dsource/jucePlugin/ui3/PatchManager.cpp | 438-------------------------------------------------------------------------------
Dsource/jucePlugin/ui3/VirusEditor.cpp | 471-------------------------------------------------------------------------------
Dsource/jucePlugin/ui3/VirusEditor.h | 111-------------------------------------------------------------------------------
Asource/osTIrusJucePlugin/CMakeLists.txt | 19+++++++++++++++++++
Asource/osTIrusJucePlugin/PluginEditorState.cpp | 12++++++++++++
Asource/osTIrusJucePlugin/PluginEditorState.h | 11+++++++++++
Asource/osTIrusJucePlugin/PluginProcessor.cpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/osTIrusJucePlugin/PluginProcessor.h | 13+++++++++++++
Asource/osTIrusJucePlugin/version.h | 4++++
Rsource/jucePlugin/version.h.in -> source/osTIrusJucePlugin/version.h.in | 0
Rsource/jucePlugin/.gitignore -> source/osirusJucePlugin/.gitignore | 0
Asource/osirusJucePlugin/CMakeLists.txt | 21+++++++++++++++++++++
Asource/osirusJucePlugin/PluginEditorState.cpp | 14++++++++++++++
Asource/osirusJucePlugin/PluginEditorState.h | 11+++++++++++
Asource/osirusJucePlugin/PluginProcessor.cpp | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/osirusJucePlugin/PluginProcessor.h | 14++++++++++++++
Rsource/jucePlugin/version.h.in -> source/osirusJucePlugin/version.h.in | 0
Asource/virusJucePlugin/ArpUserPattern.cpp | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/ArpUserPattern.h -> source/virusJucePlugin/ArpUserPattern.h | 0
Asource/virusJucePlugin/CMakeLists.txt | 45+++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/ControllerLinks.cpp -> source/virusJucePlugin/ControllerLinks.cpp | 0
Rsource/jucePlugin/ui3/ControllerLinks.h -> source/virusJucePlugin/ControllerLinks.h | 0
Asource/virusJucePlugin/FxPage.cpp | 30++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/FxPage.h -> source/virusJucePlugin/FxPage.h | 0
Asource/virusJucePlugin/Leds.cpp | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/Leds.h -> source/virusJucePlugin/Leds.h | 0
Rsource/jucePlugin/ParameterNames.h -> source/virusJucePlugin/ParameterNames.h | 0
Asource/virusJucePlugin/PartButton.cpp | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/PartButton.h -> source/virusJucePlugin/PartButton.h | 0
Asource/virusJucePlugin/Parts.cpp | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/Parts.h -> source/virusJucePlugin/Parts.h | 0
Asource/virusJucePlugin/PatchManager.cpp | 438+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/PatchManager.h -> source/virusJucePlugin/PatchManager.h | 0
Asource/virusJucePlugin/PluginEditorState.cpp | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusJucePlugin/PluginEditorState.h | 16++++++++++++++++
Asource/virusJucePlugin/PluginProcessor.cpp | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusJucePlugin/PluginProcessor.h | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsource/jucePlugin/ui3/Tabs.cpp -> source/virusJucePlugin/Tabs.cpp | 0
Rsource/jucePlugin/ui3/Tabs.h -> source/virusJucePlugin/Tabs.h | 0
Asource/virusJucePlugin/VirusController.cpp | 833+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusJucePlugin/VirusController.h | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusJucePlugin/VirusEditor.cpp | 461+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusJucePlugin/VirusEditor.h | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusJucePlugin/version.h | 4++++
Rsource/jucePlugin/version.h.in -> source/virusJucePlugin/version.h.in | 0
60 files changed, 3156 insertions(+), 3067 deletions(-)

diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt @@ -44,6 +44,12 @@ if(${CMAKE_PROJECT_NAME}_SYNTH_OSIRUS OR ${CMAKE_PROJECT_NAME}_SYNTH_OSTIRUS) add_subdirectory(virusTestConsole) add_subdirectory(virusIntegrationTest) if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) - add_subdirectory(jucePlugin) + add_subdirectory(virusJucePlugin) + if(${CMAKE_PROJECT_NAME}_SYNTH_OSIRUS) + add_subdirectory(osirusJucePlugin) + endif() + if(${CMAKE_PROJECT_NAME}_SYNTH_OSTIRUS) + add_subdirectory(osTIrusJucePlugin) + endif() endif() endif() diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -1,49 +0,0 @@ -cmake_minimum_required(VERSION 3.15) -project(jucePlugin VERSION ${CMAKE_PROJECT_VERSION}) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h) - -set(SOURCES - parameterDescriptions_C.json - parameterDescriptions_TI.json - ParameterNames.h - PluginEditorState.cpp - PluginEditorState.h - PluginProcessor.cpp - PluginProcessor.h - VirusController.cpp - VirusController.h - version.h - - ui3/ArpUserPattern.cpp - ui3/ArpUserPattern.h - ui3/ControllerLinks.cpp - ui3/ControllerLinks.h - ui3/FxPage.cpp - ui3/FxPage.h - ui3/Leds.cpp - ui3/Leds.h - ui3/Parts.cpp - ui3/Parts.h - ui3/PatchManager.cpp - ui3/PatchManager.h - ui3/PartButton.cpp - ui3/PartButton.h - ui3/Tabs.cpp - ui3/Tabs.h - ui3/VirusEditor.cpp - ui3/VirusEditor.h -) - -# https://forum.juce.com/t/help-needed-using-binarydata-with-cmake-juce-6/40486 -# "This might be because the BinaryData files are generated during the build, so the IDE may not be able to find them until the build has been run once (and even then, some IDEs might need a bit of a nudge to re-index the binary directory…)" -SET(ASSETS - "parameterDescriptions_C.json" - "parameterDescriptions_TI.json" -) - -include(skins/TrancyTI/assets.cmake) - -juce_add_binary_data(jucePlugin_BinaryData SOURCES ${ASSETS} ${ASSETS_VirusTI_Trancy}) - -createJucePluginWithFX(jucePlugin "OsTIrus" "Ttip" "Ttif" jucePlugin_BinaryData virusLib) diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp @@ -1,85 +0,0 @@ -#include "PluginEditor.h" - -#include "PluginEditorState.h" -#include "PluginProcessor.h" - -#include "VirusController.h" - -//============================================================================== -AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p, PluginEditorState& s) : - AudioProcessorEditor(&p), processorRef(p), m_state(s) -{ - addMouseListener(this, true); - - m_state.evSkinLoaded = [&](juce::Component* _component) - { - setUiRoot(_component); - }; - - m_state.evSetGuiScale = [&](const int _scale) - { - if(getNumChildComponents() > 0) - setGuiScale(getChildComponent(0), _scale); - }; - - m_state.enableBindings(); - - setUiRoot(m_state.getUiRoot()); -} - -AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() -{ - m_state.evSetGuiScale = [&](int){}; - m_state.evSkinLoaded = [&](juce::Component*){}; - - m_state.disableBindings(); - - setUiRoot(nullptr); -} - -void AudioPluginAudioProcessorEditor::setGuiScale(juce::Component* _comp, int percent) -{ - if(!_comp) - return; - - const auto s = static_cast<float>(percent)/100.0f * m_state.getRootScale(); - _comp->setTransform(juce::AffineTransform::scale(s,s)); - - setSize(static_cast<int>(m_state.getWidth() * s), static_cast<int>(m_state.getHeight() * s)); - - auto* config = processorRef.getController().getConfig(); - config->setValue("scale", percent); - config->saveIfNeeded(); -} - -void AudioPluginAudioProcessorEditor::setUiRoot(juce::Component* _component) -{ - removeAllChildren(); - - if(!_component) - return; - - const auto& config = processorRef.getController().getConfig(); - const auto scale = config->getIntValue("scale", 100); - - setGuiScale(_component, scale); - addAndMakeVisible(_component); -} - -void AudioPluginAudioProcessorEditor::mouseDown(const juce::MouseEvent& event) -{ - if(!event.mods.isPopupMenu()) - { - AudioProcessorEditor::mouseDown(event); - return; - } - - // file browsers have their own menu, do not display two menus at once - if(event.eventComponent && event.eventComponent->findParentComponentOfClass<juce::FileBrowserComponent>()) - return; - - if(dynamic_cast<juce::TextEditor*>(event.eventComponent)) - return; - - m_state.openMenu(); -} diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h @@ -1,30 +0,0 @@ -#pragma once - -#include <juce_audio_processors/juce_audio_processors.h> - -class AudioPluginAudioProcessor; -class PluginEditorState; - -//============================================================================== -class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor -{ -public: - explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&, PluginEditorState&); - ~AudioPluginAudioProcessorEditor() override; - - void mouseDown(const juce::MouseEvent& event) override; - - void paint(juce::Graphics& g) override {} - -private: - void setGuiScale(juce::Component* _component, int percent); - void setUiRoot(juce::Component* _component); - - // This reference is provided as a quick way for your editor to - // access the processor object that created it. - AudioPluginAudioProcessor& processorRef; - - PluginEditorState& m_state; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessorEditor) -}; diff --git a/source/jucePlugin/PluginEditorState.cpp b/source/jucePlugin/PluginEditorState.cpp @@ -1,110 +0,0 @@ -#include "PluginEditorState.h" - -#include "PluginProcessor.h" - -#include "ui3/VirusEditor.h" - -#include "../synthLib/os.h" - -const std::vector<PluginEditorState::Skin> g_includedSkins = -{ - {"TI Trancy", "VirusTI_Trancy.json", ""} -}; - -PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller) : jucePluginEditorLib::PluginEditorState(_processor, _controller, g_includedSkins) -{ - loadDefaultSkin(); -} - -genericUI::Editor* PluginEditorState::createEditor(const Skin& _skin, std::function<void()> _openMenuCallback) -{ - return new genericVirusUI::VirusEditor(m_parameterBinding, static_cast<AudioPluginAudioProcessor&>(m_processor), _skin.jsonFilename, _skin.folder, _openMenuCallback); -} - -void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) -{ - jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); - auto& p = m_processor; - - { - juce::PopupMenu gainMenu; - - const auto gain = m_processor.getOutputGain(); - - gainMenu.addItem("-12 db", true, gain == 0.25f, [&p] { p.setOutputGain(0.25f); }); - gainMenu.addItem("-6 db", true, gain == 0.5f, [&p] { p.setOutputGain(0.5f); }); - gainMenu.addItem("0 db (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); - gainMenu.addItem("+6 db", true, gain == 2, [&p] { p.setOutputGain(2); }); - gainMenu.addItem("+12 db", true, gain == 4, [&p] { p.setOutputGain(4); }); - - _menu.addSubMenu("Output Gain", gainMenu); - } -} - -bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) -{ - jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); - - const auto percent = m_processor.getDspClockPercent(); - const auto hz = m_processor.getDspClockHz(); - - juce::PopupMenu clockMenu; - - auto makeEntry = [&](const int _percent) - { - const auto mhz = hz * _percent / 100 / 1000000; - std::stringstream ss; - ss << _percent << "% (" << mhz << " MHz)"; - if(_percent == 100) - ss << " (Default)"; - clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); - }; - - makeEntry(50); - makeEntry(75); - makeEntry(100); - makeEntry(125); - makeEntry(150); - makeEntry(200); - - _menu.addSubMenu("DSP Clock", clockMenu); - - const auto samplerates = m_processor.getDeviceSupportedSamplerates(); - - if(samplerates.size() > 1) - { - juce::PopupMenu srMenu; - - const auto current = m_processor.getPreferredDeviceSamplerate(); - - const auto preferred = m_processor.getDevicePreferredSamplerates(); - - srMenu.addItem("Automatic (Match with host)", true, current == 0.0f, [this] { m_processor.setPreferredDeviceSamplerate(0.0f); }); - srMenu.addSeparator(); - srMenu.addSectionHeader("Official, used automatically"); - - auto addSRs = [&](bool _usePreferred) - { - for (const float samplerate : samplerates) - { - const auto isPreferred = std::find(preferred.begin(), preferred.end(), samplerate) != preferred.end(); - - if(isPreferred != _usePreferred) - continue; - - const auto title = std::to_string(static_cast<int>(std::floor(samplerate + 0.5f))) + " Hz"; - - srMenu.addItem(title, _enabled, std::fabs(samplerate - current) < 1.0f, [this, samplerate] { m_processor.setPreferredDeviceSamplerate(samplerate); }); - } - }; - - addSRs(true); - srMenu.addSeparator(); - srMenu.addSectionHeader("Undocumented, use with care"); - addSRs(false); - - _menu.addSubMenu("Device Samplerate", srMenu); - } - - return true; -} diff --git a/source/jucePlugin/PluginEditorState.h b/source/jucePlugin/PluginEditorState.h @@ -1,16 +0,0 @@ -#pragma once - -#include "../jucePluginEditorLib/pluginEditorState.h" - -class AudioPluginAudioProcessor; - -class PluginEditorState : public jucePluginEditorLib::PluginEditorState -{ -public: - explicit PluginEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller); - - genericUI::Editor* createEditor(const Skin& _skin, std::function<void()> _openMenuCallback) override; - - void initContextMenu(juce::PopupMenu& _menu) override; - bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; -}; diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp @@ -1,156 +0,0 @@ -#include "PluginProcessor.h" -#include "PluginEditorState.h" -#include "ParameterNames.h" - -#include "../virusLib/romloader.h" - -#include "../synthLib/deviceException.h" -#include "../synthLib/binarystream.h" -#include "../synthLib/os.h" - -namespace -{ - juce::PropertiesFile::Options getConfigOptions() - { - juce::PropertiesFile::Options opts; - opts.applicationName = "DSP56300Emulator_OsTIrus"; - opts.filenameSuffix = ".settings"; - opts.folderName = "DSP56300Emulator_OsTIrus"; - opts.osxLibrarySubFolder = "Application Support/DSP56300Emulator_OsTIrus"; - return opts; - } -} - -//============================================================================== -AudioPluginAudioProcessor::AudioPluginAudioProcessor() : - jucePluginEditorLib::Processor(BusesProperties() - .withInput("Input", juce::AudioChannelSet::stereo(), true) - .withOutput("Output", juce::AudioChannelSet::stereo(), true) -#if JucePlugin_IsSynth - .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) - .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) - .withOutput("USB 1", juce::AudioChannelSet::stereo(), true) - .withOutput("USB 2", juce::AudioChannelSet::stereo(), true) - .withOutput("USB 3", juce::AudioChannelSet::stereo(), true) -#endif - , ::getConfigOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect}) - , m_roms(virusLib::ROMLoader::findROMs(virusLib::DeviceModel::TI2, virusLib::DeviceModel::Snow)) -{ - evRomChanged.retain(getSelectedRom()); - - m_clockTempoParam = getController().getParameterIndexByName(Virus::g_paramClockTempo); - - const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); - Processor::setLatencyBlocks(latencyBlocks); -} - -AudioPluginAudioProcessor::~AudioPluginAudioProcessor() -{ - destroyEditorState(); -} - -//============================================================================== - -jucePluginEditorLib::PluginEditorState* AudioPluginAudioProcessor::createEditorState() -{ - return new PluginEditorState(*this, getController()); -} - -void AudioPluginAudioProcessor::processBpm(const float _bpm) -{ - // clamp to virus range, 63-190 - const auto bpmValue = juce::jmin(127, juce::jmax(0, static_cast<int>(_bpm)-63)); - const auto clockParam = getController().getParameter(m_clockTempoParam, 0); - - if (clockParam == nullptr || static_cast<int>(clockParam->getValueObject().getValue()) == bpmValue) - return; - - clockParam->getValueObject().setValue(bpmValue); -} - -bool AudioPluginAudioProcessor::setSelectedRom(const uint32_t _index) -{ - if(_index >= m_roms.size()) - return false; - if(_index == m_selectedRom) - return true; - m_selectedRom = _index; - - try - { - synthLib::Device* device = createDevice(); - getPlugin().setDevice(device); - (void)m_device.release(); - m_device.reset(device); - - evRomChanged.retain(getSelectedRom()); - - return true; - } - catch(const synthLib::DeviceException& e) - { - juce::NativeMessageBox::showMessageBox(juce::MessageBoxIconType::WarningIcon, - "Device creation failed:", - std::string("Failed to create device:\n\n") + - e.what() + "\n\n" - "Will continue using old ROM"); - return false; - } -} - -synthLib::Device* AudioPluginAudioProcessor::createDevice() -{ - const auto* rom = getSelectedRom(); - return new virusLib::Device(rom ? *rom : virusLib::ROMFile::invalid(), getPreferredDeviceSamplerate(), getHostSamplerate(), true); -} - -pluginLib::Controller* AudioPluginAudioProcessor::createController() -{ - // force creation of device as the controller decides how to initialize based on the used ROM - getPlugin(); - - return new Virus::Controller(*this); -} - -void AudioPluginAudioProcessor::saveChunkData(synthLib::BinaryStream& s) -{ - auto* rom = getSelectedRom(); - if(rom) - { - synthLib::ChunkWriter cw(s, "ROM ", 2); - const auto romName = synthLib::getFilenameWithoutPath(rom->getFilename()); - s.write<uint8_t>(static_cast<uint8_t>(rom->getModel())); - s.write(romName); - } - Processor::saveChunkData(s); -} - -void AudioPluginAudioProcessor::loadChunkData(synthLib::ChunkReader& _cr) -{ - _cr.add("ROM ", 2, [this](synthLib::BinaryStream& _binaryStream, unsigned _version) - { - auto model = virusLib::DeviceModel::ABC; - - if(_version > 1) - model = static_cast<virusLib::DeviceModel>(_binaryStream.read<uint8_t>()); - - const auto romName = _binaryStream.readString(); - - const auto& roms = getRoms(); - for(uint32_t i=0; i<static_cast<uint32_t>(roms.size()); ++i) - { - const auto& rom = roms[i]; - if(rom.getModel() == model && synthLib::getFilenameWithoutPath(rom.getFilename()) == romName) - setSelectedRom(i); - } - }); - - Processor::loadChunkData(_cr); -} - -//============================================================================== -// This creates new instances of the plugin.. -juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() -{ - return new AudioPluginAudioProcessor(); -} diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h @@ -1,77 +0,0 @@ -#pragma once - -#include "../synthLib/plugin.h" -#include "../virusLib/device.h" - -#include "../jucePluginLib/event.h" - -#include "VirusController.h" - -#include "../jucePluginEditorLib/pluginProcessor.h" - -//============================================================================== -class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor -{ -public: - AudioPluginAudioProcessor(); - ~AudioPluginAudioProcessor() override; - - jucePluginEditorLib::PluginEditorState* createEditorState() override; - - void processBpm(float _bpm) override; - - // _____________ - // - - std::string getRomName() const - { - const auto* rom = getSelectedRom(); - if(!rom) - return "<invalid>"; - return juce::File(juce::String(rom->getFilename())).getFileNameWithoutExtension().toStdString(); - } - - const virusLib::ROMFile* getSelectedRom() const - { - if(m_selectedRom >= m_roms.size()) - return {}; - return &m_roms[m_selectedRom]; - } - - virusLib::DeviceModel getModel() const - { - auto* rom = getSelectedRom(); - return rom ? rom->getModel() : virusLib::DeviceModel::Invalid; - } - - const auto& getRoms() const { return m_roms; } - - bool setSelectedRom(uint32_t _index); - uint32_t getSelectedRomIndex() const { return m_selectedRom; } - - uint32_t getPartCount() const - { - return getModel() == virusLib::DeviceModel::Snow ? 4 : 16; - } - - // _____________ - // -private: - synthLib::Device* createDevice() override; - - pluginLib::Controller* createController() override; - - void saveChunkData(synthLib::BinaryStream& s) override; - void loadChunkData(synthLib::ChunkReader& _cr) override; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) - - std::vector<virusLib::ROMFile> m_roms; - uint32_t m_selectedRom = 0; - - uint32_t m_clockTempoParam = 0xffffffff; - -public: - pluginLib::Event<const virusLib::ROMFile*> evRomChanged; -}; diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -1,832 +0,0 @@ -#include "VirusController.h" - -#include <fstream> - -#include "ParameterNames.h" -#include "PluginProcessor.h" - -#include "../virusLib/microcontrollerTypes.h" -#include "../synthLib/os.h" - -#include "ui3/VirusEditor.h" - -using MessageType = virusLib::SysexMessageType; - -namespace Virus -{ - constexpr const char* g_midiPacketNames[] = - { - "requestsingle", - "requestmulti", - "requestsinglebank", - "requestmultibank", - "requestarrangement", - "requestglobal", - "requesttotal", - "requestcontrollerdump", - "parameterchange", - "singledump", - "multidump", - "singledump_C", - }; - - static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count)); - - const char* midiPacketName(Controller::MidiPacketType _type) - { - return g_midiPacketNames[static_cast<uint32_t>(_type)]; - } - - Controller::Controller(AudioPluginAudioProcessor &p, unsigned char deviceId) : pluginLib::Controller(p, loadParameterDescriptions(p.getModel())), m_processor(p), m_deviceId(deviceId) - { - switch(p.getModel()) - { - default: - case virusLib::DeviceModel::A: - case virusLib::DeviceModel::B: - case virusLib::DeviceModel::C: m_singles.resize(8); break; - case virusLib::DeviceModel::Snow: - case virusLib::DeviceModel::TI: - case virusLib::DeviceModel::TI2: - m_singles.resize( - virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI) + - virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI2) + - virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::Snow) + - 2 - ); break; - } - - registerParams(p); - - // add lambda to enforce updating patches when virus switch from/to multi/single. - const auto paramIdx = getParameterIndexByName(g_paramPlayMode); - auto* parameter = getParameter(paramIdx); - if(parameter) - { - parameter->onValueChanged.emplace_back(std::make_pair(0, [this] { - const uint8_t prg = isMultiMode() ? 0x0 : virusLib::SINGLE; - requestSingle(0, prg); - requestMulti(0, prg); - - if (onMsgDone) - { - onMsgDone(); - } - })); - } - requestTotal(); - requestArrangement(); - - for(uint8_t i=3; i<=getBankCount(); ++i) - requestSingleBank(i); - - startTimer(5); - } - - Controller::~Controller() - { - stopTimer(); - } - - void Controller::parseSysexMessage(const pluginLib::SysEx& _msg) - { - std::string name; - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::ParamValues parameterValues; - - if(parseMidiPacket(name, data, parameterValues, _msg)) - { - const auto deviceId = data[pluginLib::MidiDataType::DeviceId]; - - if(deviceId != m_deviceId && deviceId != virusLib::OMNI_DEVICE_ID) - return; // not intended to this device! - - if(name == midiPacketName(MidiPacketType::SingleDump) || name == midiPacketName(MidiPacketType::SingleDump_C)) - parseSingle(_msg, data, parameterValues); - else if(name == midiPacketName(MidiPacketType::MultiDump)) - parseMulti(_msg, data, parameterValues); - else if(name == midiPacketName(MidiPacketType::ParameterChange)) - parseParamChange(data); - else - { - LOG("Controller: Begin unhandled SysEx! --"); - printMessage(_msg); - LOG("Controller: End unhandled SysEx! --"); - } - return; - } - - LOG("Controller: Begin unknown SysEx! --"); - printMessage(_msg); - LOG("Controller: End unknown SysEx! --"); - } - - juce::Value* Controller::getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex) - { - const auto& params = findSynthParam(ch, static_cast<uint8_t>(virusLib::PAGE_A + bank), paramIndex); - if (params.empty()) - { - // unregistered param? - return nullptr; - } - return &params.front()->getValueObject(); - } - - void Controller::parseParamChange(const pluginLib::MidiPacket::Data& _data) - { - const auto page = _data.find(pluginLib::MidiDataType::Page)->second; - const auto part = _data.find(pluginLib::MidiDataType::Part)->second; - const auto index = _data.find(pluginLib::MidiDataType::ParameterIndex)->second; - const auto value = _data.find(pluginLib::MidiDataType::ParameterValue)->second; - - const auto& partParams = findSynthParam(part, page, index); - - if (partParams.empty() && part != 0 && part != virusLib::SINGLE) - { - // ensure it's not global - const auto& globalParams = findSynthParam(0, page, index); - if (globalParams.empty()) - { - jassertfalse; - return; - } - for (const auto& param : globalParams) - { - if (!param->getDescription().isNonPartSensitive()) - { - jassertfalse; - return; - } - } - for (const auto& param : globalParams) - param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::ControlChange); - } - for (const auto& param : partParams) - param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::ControlChange); - // TODO: - /** - If a - global parameter or a Multi parameter is ac- - cessed, which is not part-sensitive (e.g. Input - Boost or Multi Delay Time), the part number is - ignored - */ - } - - juce::StringArray Controller::getSinglePresetNames(virusLib::BankNumber _bank) const - { - if (_bank == virusLib::BankNumber::EditBuffer) - { - jassertfalse; - return {}; - } - - const auto bank = virusLib::toArrayIndex(_bank); - - if (bank >= m_singles.size() || bank < 0) - { - jassertfalse; - return {}; - } - - juce::StringArray bankNames; - for (auto i = 0; i < 128; i++) - bankNames.add(m_singles[bank][i].name); - return bankNames; - } - - std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const - { - return getPresetName("SingleName", _values); - } - - std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const - { - return getPresetName("SingleName", _values); - } - - std::string Controller::getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const - { - return getPresetName("MultiName", _values); - } - - std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const - { - std::string name; - for(uint32_t i=0; i<kNameLength; ++i) - { - const std::string paramName = _paramNamePrefix + std::to_string(i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); - if(it == _values.end()) - break; - - name += static_cast<char>(it->second); - } - return name; - } - - std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::AnyPartParamValues& _values) const - { - std::string name; - for(uint32_t i=0; i<kNameLength; ++i) - { - const std::string paramName = _paramNamePrefix + std::to_string(i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto it = _values[idx]; - if(!it) - break; - - name += static_cast<char>(*it); - } - return name; - } - - void Controller::setSinglePresetName(const uint8_t _part, const juce::String& _name) const - { - for (int i=0; i<kNameLength; i++) - { - const std::string paramName = "SingleName" + std::to_string(i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - auto* param = getParameter(idx, _part); - if(!param) - break; - auto& v = param->getValueObject(); - if(i >= _name.length()) - v.setValue(static_cast<uint8_t>(' ')); - else - v.setValue(static_cast<uint8_t>(_name[i])); - } - } - - void Controller::setSinglePresetName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _name) const - { - for(uint32_t i=0; i<kNameLength; ++i) - { - const std::string paramName = "SingleName" + std::to_string(i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - _values[idx] = (i < _name.size()) ? _name[i] : ' '; - } - } - - bool Controller::isMultiMode() const - { - const auto paramIdx = getParameterIndexByName(g_paramPlayMode); - const auto& value = getParameter(paramIdx)->getValueObject(); - return value.getValue(); - } - - juce::String Controller::getCurrentPartPresetName(const uint8_t _part) const - { - std::string name; - for (int i=0; i<kNameLength; i++) - { - const std::string paramName = "SingleName" + std::to_string(i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - auto* param = getParameter(idx, _part); - if(!param) - break; - const int v = param->getValueObject().getValue(); - name += static_cast<char>(v); - } - return name; - } - - void Controller::setCurrentPartPreset(uint8_t _part, const virusLib::BankNumber _bank, uint8_t _prg) - { - if(_bank == virusLib::BankNumber::EditBuffer || _prg > m_singles[0].size()) - { - jassertfalse; - return; - } - - const auto bank = virusLib::toArrayIndex(_bank); - - if (bank >= m_singles.size()) - { - jassertfalse; - return; - } - - const uint8_t pt = isMultiMode() ? _part : virusLib::SINGLE; - - sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_BANK_SELECT, virusLib::toMidiByte(_bank)); - sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_PROGRAM_CHANGE, _prg); - - requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), pt); - - m_currentBank[_part] = _bank; - m_currentProgram[_part] = _prg; - m_currentPresetSource[_part] = PresetSource::Rom; - } - - void Controller::setCurrentPartPresetSource(uint8_t _part, PresetSource _source) - { - m_currentPresetSource[_part] = _source; - } - - virusLib::BankNumber Controller::getCurrentPartBank(const uint8_t _part) const - { - return m_currentBank[_part]; - } - - uint8_t Controller::getCurrentPartProgram(const uint8_t _part) const - { - return m_currentProgram[_part]; - } - - Controller::PresetSource Controller::getCurrentPartPresetSource(uint8_t _part) const - { - return m_currentPresetSource[_part]; - } - - bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg) const - { - MidiPacketType unused; - return parseSingle(_data, _parameterValues, _msg, unused); - } - - bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg, MidiPacketType& usedPacketType) const - { - const auto packetName = midiPacketName(MidiPacketType::SingleDump); - - auto* m = getMidiPacket(packetName); - - if(!m) - return false; - - usedPacketType = MidiPacketType::SingleDump; - - if(_msg.size() > m->size()) - { - pluginLib::SysEx temp; - temp.insert(temp.begin(), _msg.begin(), _msg.begin() + (m->size()-1)); - temp.push_back(0xf7); - return parseMidiPacket(*m, _data, _parameterValues, temp); - } - - if(_msg.size() < m->size()) - { - const auto* mc = getMidiPacket(midiPacketName(MidiPacketType::SingleDump_C)); - if(!mc) - return false; - usedPacketType = MidiPacketType::SingleDump_C; - return parseMidiPacket(*mc, _data, _parameterValues, _msg); - } - - return parseMidiPacket(*m, _data, _parameterValues, _msg); - } - - std::string Controller::loadParameterDescriptions(const virusLib::DeviceModel _model) - { - const auto name = _model == virusLib::DeviceModel::Invalid || virusLib::isTIFamily(_model) ? "parameterDescriptions_TI.json" : "parameterDescriptions_C.json"; - const auto path = synthLib::getModulePath() + name; - - const std::ifstream f(path.c_str(), std::ios::in); - if(f.is_open()) - { - std::stringstream buf; - buf << f.rdbuf(); - return buf.str(); - } - - uint32_t size; - const auto res = genericVirusUI::VirusEditor::findEmbeddedResource(name, size); - if(res) - return {res, size}; - return {}; - } - - void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) - { - SinglePatch patch; - - patch.bankNumber = virusLib::fromMidiByte(_data.find(pluginLib::MidiDataType::Bank)->second); - patch.progNumber = _data.find(pluginLib::MidiDataType::Program)->second; - - patch.name = getSinglePresetName(_parameterValues); - - patch.data = _msg; - - if (patch.bankNumber == virusLib::BankNumber::EditBuffer) - { - if(patch.progNumber == virusLib::SINGLE) - m_singleEditBuffer = patch; - else - m_singleEditBuffers[patch.progNumber] = patch; - - // virus sends also the single buffer not matter what's the mode. (?? no, both is requested, so both is sent) - // instead of keeping both, we 'encapsulate' this into first channel. - // the logic to maintain this is done by listening the global single/multi param. - if (isMultiMode() && patch.progNumber == virusLib::SINGLE) - return; - if (!isMultiMode() && patch.progNumber == 0x0) - return; - - const uint8_t ch = patch.progNumber == virusLib::SINGLE ? 0 : patch.progNumber; - - const auto locked = getLockedParameterNames(); - - for(auto it = _parameterValues.begin(); it != _parameterValues.end(); ++it) - { - auto* p = getParameter(it->first.second, ch); - - if(locked.find(p->getDescription().name) == locked.end()) - p->setValueFromSynth(it->second, false, pluginLib::Parameter::ChangedBy::PresetChange); - } - - m_processor.updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); - - if(m_currentPresetSource[ch] != PresetSource::Browser) - { - bool found = false; - for(size_t b=0; b<m_singles.size() && !found; ++b) - { - const auto& singlePatches = m_singles[b]; - - for(size_t s=0; s<singlePatches.size(); ++s) - { - const auto& singlePatch = singlePatches[s]; - - if(singlePatch.name == patch.name) - { - m_currentBank[ch] = virusLib::fromArrayIndex(static_cast<uint8_t>(b)); - m_currentProgram[ch] = static_cast<uint8_t>(s); - m_currentPresetSource[ch] = PresetSource::Rom; - found = true; - break; - } - } - } - - if(!found) - { - m_currentProgram[ch] = 0; - m_currentBank[ch] = virusLib::BankNumber::EditBuffer; - m_currentPresetSource[ch] = PresetSource::Unknown; - } - } - else - { - m_currentProgram[ch] = 0; - m_currentBank[ch] = virusLib::BankNumber::EditBuffer; - } - - if (onProgramChange) - onProgramChange(patch.progNumber); - } - else - { - const auto bank = toArrayIndex(patch.bankNumber); - const auto program = patch.progNumber; - - m_singles[bank][program] = patch; - - if(onRomPatchReceived) - onRomPatchReceived(patch.bankNumber, program); - } - } - - void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) - { - const auto bankNumber = _data.find(pluginLib::MidiDataType::Bank)->second; - - /* If it's a multi edit buffer, set the part page C parameters to their multi equivalents */ - if (bankNumber == 0) - { - m_multiEditBuffer.progNumber = _data.find(pluginLib::MidiDataType::Program)->second; - m_multiEditBuffer.name = getMultiPresetName(_parameterValues); - m_multiEditBuffer.data = _msg; - - for (const auto & paramValue : _parameterValues) - { - const auto part = paramValue.first.first; - const auto index = paramValue.first.second; - const auto value = paramValue.second; - - auto* param = getParameter(index, part); - if(!param) - continue; - - const auto& desc = param->getDescription(); - - if(desc.page != virusLib::PAGE_C) - continue; - - param->setValueFromSynth(value, false, pluginLib::Parameter::ChangedBy::PresetChange); - } - - m_processor.updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); - } - } - - void Controller::parseControllerDump(const synthLib::SMidiEvent& m) - { - const uint8_t status = m.a & 0xf0; - const uint8_t part = m.a & 0x0f; - - uint8_t page; - - if (status == synthLib::M_CONTROLCHANGE) - page = virusLib::PAGE_A; - else if (status == synthLib::M_POLYPRESSURE) - page = virusLib::PAGE_B; - else - return; - - const auto& params = findSynthParam(part, page, m.b); - for (const auto & p : params) - p->setValueFromSynth(m.c, true, pluginLib::Parameter::ChangedBy::ControlChange); - } - - void Controller::printMessage(const pluginLib::SysEx &msg) - { - std::stringstream ss; - ss << "[size " << msg.size() << "] "; - for(size_t i=0; i<msg.size(); ++i) - { - ss << HEXN(static_cast<int>(msg[i]), 2); - if(i < msg.size()-1) - ss << ','; - } - const auto s(ss.str()); - LOG(s); - } - - void Controller::onStateLoaded() - { - requestTotal(); - requestArrangement(); - } - - bool Controller::requestProgram(uint8_t _bank, uint8_t _program, bool _multi) const - { - std::map<pluginLib::MidiDataType, uint8_t> data; - - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); - - return sendSysEx(_multi ? MidiPacketType::RequestMulti : MidiPacketType::RequestSingle, data); - } - - bool Controller::requestSingle(uint8_t _bank, uint8_t _program) const - { - return requestProgram(_bank, _program, false); - } - - bool Controller::requestMulti(uint8_t _bank, uint8_t _program) const - { - return requestProgram(_bank, _program, true); - } - - bool Controller::requestSingleBank(uint8_t _bank) const - { - std::map<pluginLib::MidiDataType, uint8_t> data; - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); - - return sendSysEx(MidiPacketType::RequestSingleBank, data); - } - - bool Controller::requestTotal() const - { - return sendSysEx(MidiPacketType::RequestTotal); - } - - bool Controller::requestArrangement() const - { - return sendSysEx(MidiPacketType::RequestArrangement); - } - - bool Controller::sendSysEx(MidiPacketType _type) const - { - std::map<pluginLib::MidiDataType, uint8_t> params; - return sendSysEx(_type, params); - } - - bool Controller::sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const - { - _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); - } - - void Controller::timerCallback() - { - std::vector<synthLib::SMidiEvent> virusOut; - getPluginMidiOut(virusOut); - - for (const auto& msg : virusOut) - { - if (msg.sysex.empty()) - { - // no sysex - parseControllerDump(msg); - } - else - { - parseSysexMessage(msg.sysex); - } - } - } - - void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) - { - const auto& desc = _parameter.getDescription(); - - sendParameterChange(desc.page, _parameter.getPart(), desc.index, _value); - } - - bool Controller::sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const - { - std::map<pluginLib::MidiDataType, uint8_t> data; - - data.insert(std::make_pair(pluginLib::MidiDataType::Page, _page)); - data.insert(std::make_pair(pluginLib::MidiDataType::Part, _part)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, _index)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); - - return sendSysEx(MidiPacketType::ParameterChange, data); - } - - std::vector<uint8_t> Controller::createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program) - { - pluginLib::MidiPacket::Data data; - - data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); - - std::vector<uint8_t> dst; - - if(!createMidiDataFromPacket(dst, midiPacketName(MidiPacketType::SingleDump), data, _part)) - return {}; - - return dst; - } - - std::vector<uint8_t> Controller::createSingleDump(MidiPacketType _packet, uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::AnyPartParamValues& _paramValues) - { - const auto* m = getMidiPacket(midiPacketName(_packet)); - assert(m && "midi packet not found"); - - if(!m) - return {}; - - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::NamedParamValues paramValues; - - data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); - - if(!createNamedParamValues(paramValues, _paramValues)) - return {}; - - pluginLib::MidiPacket::Sysex dst; - if(!m->create(dst, data, paramValues)) - return {}; - return dst; - } - - std::vector<uint8_t> Controller::modifySingleDump(const std::vector<uint8_t>& _sysex, const virusLib::BankNumber _newBank, const uint8_t _newProgram) const - { - auto* m = getMidiPacket(midiPacketName(MidiPacketType::SingleDump)); - assert(m); - - const auto idxBank = m->getByteIndexForType(pluginLib::MidiDataType::Bank); - const auto idxProgram = m->getByteIndexForType(pluginLib::MidiDataType::Program); - - assert(idxBank != pluginLib::MidiPacket::InvalidIndex); - assert(idxProgram != pluginLib::MidiPacket::InvalidIndex); - - auto data = _sysex; - - data[idxBank] = toMidiByte(_newBank); - data[idxProgram] = _newProgram; - - return data; - } - - void Controller::selectPrevPreset(const uint8_t _part) - { - if(getCurrentPartProgram(_part) > 0) - { - setCurrentPartPreset(_part, getCurrentPartBank(_part), getCurrentPartProgram(_part) - 1); - } - } - - void Controller::selectNextPreset(uint8_t _part) - { - if(getCurrentPartProgram(_part) < m_singles[0].size()) - { - setCurrentPartPreset(_part, getCurrentPartBank(_part), getCurrentPartProgram(_part) + 1); - } - } - - std::string Controller::getBankName(uint32_t _index) const - { - char temp[32]{0}; - - if(getBankCount() <= 26) - { - snprintf(temp, sizeof(temp), "Bank %c", 'A' + _index); - } - else if(_index < 2) - { - snprintf(temp, sizeof(temp), "RAM Bank %c", 'A' + _index); - } - else - { - _index -= 2; - - const auto countSnow = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::Snow); - const auto countTI = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI); - const auto countTI2 = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI2); - - switch(m_processor.getModel()) - { - case virusLib::DeviceModel::Snow: - if(_index < countSnow) sprintf(temp, "Snow Rom %c", 'A' + _index); - else if(_index < countTI + countSnow) sprintf(temp, "TI Rom %c", 'A' + (_index - countSnow)); - else sprintf(temp, "TI2 Rom %c", 'A' + (_index - countTI - countSnow)); - break; - case virusLib::DeviceModel::TI: - if(_index < countTI) sprintf(temp, "TI Rom %c", 'A' + _index); - else if(_index < countTI + countTI2) sprintf(temp, "TI2 Rom %c", 'A' + (_index - countTI)); - else sprintf(temp, "Snow Rom %c", 'A' + (_index - countTI - countTI2)); - break; - case virusLib::DeviceModel::TI2: - if(_index < countTI2) sprintf(temp, "TI2 Rom %c", 'A' + _index); - else if(_index < countTI2 + countTI) sprintf(temp, "TI Rom %c", 'A' + (_index - countTI2)); - else sprintf(temp, "Snow Rom %c", 'A' + (_index - countTI2 - countTI)); - break; - default: - assert(false); - break; - } - } - return temp; - } - - bool Controller::activatePatch(const std::vector<unsigned char>& _sysex) - { - return activatePatch(_sysex, isMultiMode() ? getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE)); - } - - bool Controller::activatePatch(const std::vector<unsigned char>& _sysex, uint32_t _part) - { - if(_part == virusLib::ProgramType::SINGLE) - { - if(isMultiMode()) - _part = 0; - } - else if(_part >= 16) - { - return false; - } - else if(!isMultiMode() && _part == 0) - { - _part = virusLib::ProgramType::SINGLE; - } - - const auto program = static_cast<uint8_t>(_part); - - // re-pack, force to edit buffer - const auto msg = modifySingleDump(_sysex, virusLib::BankNumber::EditBuffer, program); - - if(msg.empty()) - return false; - - // if we have locked parameters, get them, send the preset and then send each locked parameter value afterward. - // Modifying the preset directly does not work because a preset might be an old version that we do not know - const auto lockedParameters = getLockedParameters(static_cast<uint8_t>(_part == virusLib::SINGLE ? 0 : _part)); - - sendSysEx(msg); - - for (const auto& lockedParameter : lockedParameters) - { - const auto v = lockedParameter->getUnnormalizedValue(); - sendParameterChange(*lockedParameter, static_cast<uint8_t>(v)); - } - - requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), program); - - setCurrentPartPresetSource(program == virusLib::ProgramType::SINGLE ? 0 : program, PresetSource::Browser); - - return true; - } -}; // namespace Virus diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -1,180 +0,0 @@ -#pragma once - -#include "../jucePluginLib/parameterdescriptions.h" -#include "../jucePluginLib/controller.h" - -#include "../virusLib/microcontrollerTypes.h" -#include "../virusLib/romfile.h" - -#include "../synthLib/plugin.h" - -class AudioPluginAudioProcessor; - -namespace Virus -{ - class Controller : public pluginLib::Controller, juce::Timer - { - public: - struct Patch - { - std::string name; - std::vector<uint8_t> data; - uint8_t progNumber = 0; - }; - - struct SinglePatch : Patch - { - virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); - }; - - struct MultiPatch : Patch {}; - - using Singles = std::vector<std::array<SinglePatch, 128>>; - - static constexpr auto kNameLength = 10; - - enum class MidiPacketType - { - RequestSingle, - RequestMulti, - RequestSingleBank, - RequestMultiBank, - RequestArrangement, - RequestGlobal, - RequestTotal, - RequestControllerDump, - ParameterChange, - SingleDump, - MultiDump, - SingleDump_C, - - Count - }; - - enum class PresetSource - { - Unknown, - Rom, - Browser - }; - - struct CurrentPreset - { - uint8_t program = 0; - virusLib::BankNumber bank = virusLib::BankNumber::EditBuffer; - PresetSource source = PresetSource::Unknown; - }; - - Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00); - ~Controller() override; - - std::vector<uint8_t> createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program); - std::vector<uint8_t> createSingleDump(MidiPacketType _packet, uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::AnyPartParamValues& _paramValues); - std::vector<uint8_t> modifySingleDump(const std::vector<uint8_t>& _sysex, virusLib::BankNumber _newBank, uint8_t _newProgram) const; - - void selectPrevPreset(uint8_t _part); - void selectNextPreset(uint8_t _part); - std::string getBankName(uint32_t _index) const; - - bool activatePatch(const std::vector<unsigned char>& _sysex); - bool activatePatch(const std::vector<unsigned char>& _sysex, uint32_t _part); - - static void printMessage(const pluginLib::SysEx &); - - juce::Value* getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex); - - juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; - std::string getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const; - std::string getSinglePresetName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; - std::string getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const; - std::string getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const; - std::string getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::AnyPartParamValues& _values) const; - - const Singles& getSinglePresets() const - { - return m_singles; - } - - const SinglePatch& getSingleEditBuffer() const - { - return m_singleEditBuffer; - } - - const SinglePatch& getSingleEditBuffer(const uint8_t _part) const - { - return m_singleEditBuffers[_part]; - } - - const MultiPatch& getMultiEditBuffer() const - { - return m_multiEditBuffer; - } - - void setSinglePresetName(uint8_t _part, const juce::String& _name) const; - void setSinglePresetName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _name) const; - - bool isMultiMode() const; - - // part 0 - 15 (ignored when single! 0x40...) - void setCurrentPartPreset(uint8_t _part, virusLib::BankNumber _bank, uint8_t _prg); - void setCurrentPartPresetSource(uint8_t _part, PresetSource _source); - - virusLib::BankNumber getCurrentPartBank(uint8_t _part) const; - uint8_t getCurrentPartProgram(uint8_t _part) const; - PresetSource getCurrentPartPresetSource(uint8_t _part) const; - - juce::String getCurrentPartPresetName(uint8_t _part) const; - uint32_t getBankCount() const { return static_cast<uint32_t>(m_singles.size()); } - void parseSysexMessage(const pluginLib::SysEx &) override; - void onStateLoaded() override; - std::function<void(int)> onProgramChange = {}; - std::function<void()> onMsgDone = {}; - std::function<void(virusLib::BankNumber _bank, uint32_t _program)> onRomPatchReceived = {}; - - bool requestProgram(uint8_t _bank, uint8_t _program, bool _multi) const; - bool requestSingle(uint8_t _bank, uint8_t _program) const; - bool requestMulti(uint8_t _bank, uint8_t _program) const; - - bool requestSingleBank(uint8_t _bank) const; - - bool requestTotal() const; - bool requestArrangement() const; - - void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; - bool sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const; - - using pluginLib::Controller::sendSysEx; - - bool sendSysEx(MidiPacketType _type) const; - bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; - - uint8_t getDeviceId() const { return m_deviceId; } - - bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg) const; - bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg, MidiPacketType& usedPacketType) const; - - private: - static std::string loadParameterDescriptions(const virusLib::DeviceModel _model); - - void timerCallback() override; - - Singles m_singles; - SinglePatch m_singleEditBuffer; // single mode - std::array<SinglePatch, 16> m_singleEditBuffers; // multi mode - - MultiPatch m_multiEditBuffer; - - void parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); - - void parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); - - void parseParamChange(const pluginLib::MidiPacket::Data& _data); - void parseControllerDump(const synthLib::SMidiEvent&); - - AudioPluginAudioProcessor& m_processor; - unsigned char m_deviceId; - virusLib::BankNumber m_currentBank[16]{}; - uint8_t m_currentProgram[16]{}; - PresetSource m_currentPresetSource[16]{PresetSource::Unknown}; - }; -}; // namespace Virus diff --git a/source/jucePlugin/ui3/ArpUserPattern.cpp b/source/jucePlugin/ui3/ArpUserPattern.cpp @@ -1,125 +0,0 @@ -#include "ArpUserPattern.h" - -#include "VirusEditor.h" - -#include "../VirusController.h" - -#include "../juceUiLib/uiObjectStyle.h" - -namespace genericVirusUI -{ - namespace - { - constexpr uint32_t g_listenerId = 0xaa; - } - - ArpUserPattern::ArpUserPattern(const VirusEditor& _editor) : m_controller(_editor.getController()) - { - bindParameters(); - } - - void ArpUserPattern::paint(juce::Graphics& g) - { - if(!m_patternLength) - return; - - if(m_gradientStrength <= 0.0f) - { - genericUI::UiObjectStyle::parseColor(m_colRectFillActive, getProperties()["colorActive"].toString().toStdString()); - genericUI::UiObjectStyle::parseColor(m_colRectFillInactive, getProperties()["colorInactive"].toString().toStdString()); - - m_gradientStrength = getProperties()["gradient"]; - - if(m_gradientStrength <= 0.0f) - m_gradientStrength = 0.5f; - } - - const auto w = (float)getWidth(); - const auto h = (float)getHeight(); - - const auto maxstepW = static_cast<float>(getWidth()) / static_cast<float>(m_steps.size()); - - auto makeRectGradient = [&](const juce::Colour& _color) - { - const auto& c = _color; - return juce::ColourGradient::vertical(c, 0.0f, c.darker(m_gradientStrength), h); - }; - - const auto rectGradientActive = makeRectGradient(m_colRectFillActive); - const auto rectGradientInactive = makeRectGradient(m_colRectFillInactive); - - float x = 0.0f; - - for(int i=0; i<std::min(m_patternLength->getUnnormalizedValue() + 1, static_cast<int>(m_steps.size())); ++i) - { - const auto stepW = m_steps[i].length->getValue() * maxstepW; - const auto stepH = m_steps[i].velocity->getValue() * h; - const auto y = h - stepH; - - g.setGradientFill(m_steps[i].bitfield->getUnnormalizedValue() > 0 ? rectGradientActive : rectGradientInactive); - g.fillRect(x, y, stepW, stepH); - - x += maxstepW; - } - } - - void ArpUserPattern::onCurrentPartChanged() - { - } - - void ArpUserPattern::bindParameters() - { - for(size_t s=0; s<m_steps.size(); ++s) - { - auto& step = m_steps[s]; - - step.length = bindParameter("Step " + std::to_string(s+1) +" Length"); - step.velocity = bindParameter("Step " + std::to_string(s+1) +" Velocity"); - step.bitfield = bindParameter("Step " + std::to_string(s+1) +" Bitfield"); - } - - m_patternLength = bindParameter("Arpeggiator/UserPatternLength"); - } - - void ArpUserPattern::unbindParameter(pluginLib::Parameter*& _parameter) - { - assert(_parameter); - _parameter->removeListener(g_listenerId); - _parameter = nullptr; - } - - void ArpUserPattern::unbindParameters() - { - if(!m_patternLength) - return; - - unbindParameter(m_patternLength); - - for (auto& s : m_steps) - { - unbindParameter(s.length); - unbindParameter(s.velocity); - unbindParameter(s.bitfield); - } - } - - pluginLib::Parameter* ArpUserPattern::bindParameter(const std::string& _name) - { - const auto idx = m_controller.getParameterIndexByName(_name); - assert(idx != pluginLib::Controller::InvalidParameterIndex); - auto* p = m_controller.getParameter(idx, m_controller.getCurrentPart()); - assert(p); - - p->onValueChanged.emplace_back(g_listenerId, [this]() - { - onParameterChanged(); - }); - - return p; - } - - void ArpUserPattern::onParameterChanged() - { - repaint(); - } -} diff --git a/source/jucePlugin/ui3/FxPage.cpp b/source/jucePlugin/ui3/FxPage.cpp @@ -1,30 +0,0 @@ -#include "FxPage.h" - -#include "VirusEditor.h" - -#include "../ParameterNames.h" -#include "../VirusController.h" - -namespace genericVirusUI -{ - FxPage::FxPage(const VirusEditor& _editor) - { - const auto delayReverbMode = _editor.getController().getParameterIndexByName(Virus::g_paramDelayReverbMode); - const auto p = _editor.getController().getParamValueObject(delayReverbMode, 0); - - if(!p) - return; - - const auto containerReverb = _editor.findComponent("ContainerReverb"); - const auto containerDelay = _editor.findComponent("ContainerDelay"); - - m_conditionReverb.reset(new genericUI::ConditionByParameterValues(*containerReverb, p, 0, {2,3,4})); - m_conditionDelay.reset(new genericUI::ConditionByParameterValues(*containerDelay, p, 0, {0,1,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26})); - } - - FxPage::~FxPage() - { - m_conditionReverb.reset(); - m_conditionDelay.reset(); - } -} diff --git a/source/jucePlugin/ui3/Leds.cpp b/source/jucePlugin/ui3/Leds.cpp @@ -1,56 +0,0 @@ -#include "Leds.h" - -#include "VirusEditor.h" - -#include "../PluginProcessor.h" - -namespace genericVirusUI -{ - constexpr const char* g_lfoNames[3] = {"Lfo1LedOn", "Lfo2LedOn", "Lfo3LedOn"}; - - Leds::Leds(const genericUI::Editor& _editor, AudioPluginAudioProcessor& _processor) - { - for(size_t i=0; i<m_lfos.size(); ++i) - { - if(auto* comp = _editor.findComponentT<juce::Component>(g_lfoNames[i], false)) - { - m_lfos[i].reset(new jucePluginEditorLib::Led(comp)); - m_lfos[i]->setSourceCallback([i, &_processor] - { - auto* d = dynamic_cast<virusLib::Device*>(_processor.getPlugin().getDevice()); - - if(!d) - return 0.0f; - - const auto v = std::clamp(d->getFrontpanelState().m_lfoPhases[i], 0.0f, 1.0f); - return std::pow(1.0f - v, 0.2f); - }); - } - } - - if(auto* comp = _editor.findComponentT<juce::Component>("logolight", false)) - { - m_logo.reset(new jucePluginEditorLib::Led(comp)); - - m_logo->setSourceCallback([&_processor] - { - auto* d = dynamic_cast<virusLib::Device*>(_processor.getPlugin().getDevice()); - - if(!d) - return 0.0f; - - const auto& s = d->getFrontpanelState(); - - const auto v = std::clamp(_processor.getModel() == virusLib::DeviceModel::Snow ? s.m_bpm : s.m_logo, 0.0f, 1.0f); - - return std::pow(1.0f - v, 0.2f); - }); - } - } - - Leds::~Leds() - { - for (auto& led : m_lfos) - led.reset(); - } -} diff --git a/source/jucePlugin/ui3/PartButton.cpp b/source/jucePlugin/ui3/PartButton.cpp @@ -1,83 +0,0 @@ -#include "PartButton.h" - -#include "VirusEditor.h" -#include "../VirusController.h" -#include "../../jucePluginEditorLib/patchmanager/list.h" - -#include "../../jucePluginEditorLib/patchmanager/savepatchdesc.h" - -namespace genericVirusUI -{ - PartButton::PartButton(VirusEditor& _editor) : jucePluginEditorLib::PartButton<TextButton>(_editor), m_editor(_editor) // NOLINT(clang-diagnostic-undefined-func-template) - { - } - - bool PartButton::isInterestedInDragSource(const SourceDetails& _dragSourceDetails) - { - if(getPart() > 0 && !m_editor.getController().isMultiMode()) - return false; - - return jucePluginEditorLib::PartButton<TextButton>::isInterestedInDragSource(_dragSourceDetails); // NOLINT(clang-diagnostic-undefined-func-template) - } - - void PartButton::paint(juce::Graphics& g) - { - jucePluginEditorLib::PartButton<TextButton>::paint(g); - } - - void PartButton::onClick() - { - selectPreset(getPart()); - } - - void PartButton::selectPreset(uint8_t _part) const - { - pluginLib::patchDB::SearchRequest req; - req.sourceType = pluginLib::patchDB::SourceType::Rom; - - m_editor.getPatchManager()->search(std::move(req), [this](const pluginLib::patchDB::Search& _search) - { - std::map<std::string, std::vector<pluginLib::patchDB::PatchPtr>> patches; - - { - std::shared_lock lock(_search.resultsMutex); - for (const auto& patch : _search.results) - { - const auto s = patch->source.lock(); - if(!s) - continue; - patches[s->name].push_back(patch); - } - } - - for (auto& it : patches) - { - std::sort(it.second.begin(), it.second.end(), [](const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) - { - return _a->program < _b->program; - }); - } - - juce::MessageManager::callAsync([this, patches = std::move(patches)] - { - juce::PopupMenu selector; - - for (const auto& it : patches) - { - juce::PopupMenu p; - for (const auto& patch : it.second) - { - const auto& presetName = patch->getName(); - p.addItem(presetName, [this, patch] - { - if(m_editor.getPatchManager()->activatePatch(patch, getPart())) - m_editor.getPatchManager()->setSelectedPatch(getPart(), patch); - }); - } - selector.addSubMenu(it.first, p); - } - selector.showMenuAsync(juce::PopupMenu::Options()); - }); - }); - } -} diff --git a/source/jucePlugin/ui3/Parts.cpp b/source/jucePlugin/ui3/Parts.cpp @@ -1,217 +0,0 @@ -#include "Parts.h" - -#include "PartButton.h" -#include "VirusEditor.h" - -#include "../VirusController.h" -#include "../PluginProcessor.h" -#include "../ParameterNames.h" - -#include "../../jucePluginEditorLib/pluginProcessor.h" -#include "../../jucePluginEditorLib/patchmanager/savepatchdesc.h" - -#include "../../jucePluginLib/parameterbinding.h" - -#include "../../virusLib/device.h" - -namespace genericVirusUI -{ - Parts::Parts(VirusEditor& _editor) : m_editor(_editor) - { - _editor.findComponents<genericUI::Button<juce::DrawableButton>>(m_partSelect, "SelectPart"); - _editor.findComponents<juce::Button>(m_presetPrev, "PresetPrev"); - _editor.findComponents<juce::Button>(m_presetNext, "PresetNext"); - - _editor.findComponents<juce::Slider>(m_partVolume, "PartVolume"); - _editor.findComponents<juce::Slider>(m_partPan, "PartPan"); - _editor.findComponents<PartButton>(m_presetName, "PresetName"); - _editor.findComponents<juce::Component>(m_partActive, "PartActive"); - - for(size_t i=0; i<m_partSelect.size(); ++i) - { - m_partSelect[i]->onClick = [this, i]{ selectPart(i); }; - m_partSelect[i]->onDown = [this, i](const juce::MouseEvent& _e) - { - if(!_e.mods.isPopupMenu()) - return false; - selectPartMidiChannel(i); - return true; - }; - - if(i < m_presetPrev.size()) - m_presetPrev[i]->onClick = [this, i]{ selectPrevPreset(i); }; - - if(i < m_presetNext.size()) - m_presetNext[i]->onClick = [this, i]{ selectNextPreset(i); }; - - m_presetName[i]->initalize(static_cast<uint8_t>(i)); - - const auto partVolume = _editor.getController().getParameterIndexByName(Virus::g_paramPartVolume); - const auto partPanorama = _editor.getController().getParameterIndexByName(Virus::g_paramPartPanorama); - - _editor.getParameterBinding().bind(*m_partVolume[i], partVolume, static_cast<uint8_t>(i)); - _editor.getParameterBinding().bind(*m_partPan[i], partPanorama, static_cast<uint8_t>(i)); - - m_partVolume[i]->getProperties().set("parameter", static_cast<int>(partVolume)); - m_partPan[i]->getProperties().set("parameter", static_cast<int>(partPanorama)); - - m_partVolume[i]->getProperties().set("part", static_cast<int>(i)); - m_partPan[i]->getProperties().set("part", static_cast<int>(i)); - } - - updateAll(); - - if(!m_partActive.empty()) - { - for (const auto & partActive : m_partActive) - { - partActive->setInterceptsMouseClicks(false, false); - partActive->setVisible(false); - } - - startTimer(1000/20); - } - } - - Parts::~Parts() = default; - - void Parts::onProgramChange() const - { - updateAll(); - } - - void Parts::onPlayModeChanged() const - { - updateAll(); - } - - void Parts::onCurrentPartChanged() const - { - updateSelectedPart(); - } - - void Parts::selectPart(const size_t _part) const - { - m_editor.setPart(_part); - } - - void Parts::selectPartMidiChannel(const size_t _part) const - { - if(!m_editor.getController().isMultiMode()) - return; - - juce::PopupMenu menu; - - const auto idx= m_editor.getController().getParameterIndexByName("Part Midi Channel"); - if(idx == pluginLib::Controller::InvalidParameterIndex) - return; - - const auto v = m_editor.getController().getParameter(idx, static_cast<uint8_t>(_part)); - - for(uint8_t i=0; i<16; ++i) - { - menu.addItem("Midi Channel " + std::to_string(i + 1), true, v->getUnnormalizedValue() == i, [v, i] - { - v->setValue(v->convertTo0to1(i), pluginLib::Parameter::ChangedBy::Ui); - }); - } - - menu.showMenuAsync({}); - } - - void Parts::selectPrevPreset(size_t _part) const - { - if(m_presetPrev.size() == 1) - _part = m_editor.getController().getCurrentPart(); - - auto* pm = m_editor.getPatchManager(); - if(pm && pm->selectPrevPreset(static_cast<uint32_t>(_part))) - return; - m_editor.getController().selectPrevPreset(static_cast<uint8_t>(_part)); - } - - void Parts::selectNextPreset(size_t _part) const - { - if(m_presetNext.size() == 1) - _part = m_editor.getController().getCurrentPart(); - - auto* pm = m_editor.getPatchManager(); - if(pm && pm->selectNextPreset(static_cast<uint32_t>(_part))) - return; - m_editor.getController().selectNextPreset(static_cast<uint8_t>(_part)); - } - - void Parts::updatePresetNames() const - { - for(size_t i=0; i<m_presetName.size(); ++i) - m_presetName[i]->setButtonText(m_editor.getController().getCurrentPartPresetName(static_cast<uint8_t>(i))); - } - - void Parts::updateSelectedPart() const - { - const auto part = m_editor.getController().getCurrentPart(); - - if(part < m_partSelect.size()) - m_partSelect[part]->setToggleState(true, juce::dontSendNotification); - - for(size_t i=0; i<m_partSelect.size(); ++i) - { - if(i == part) - continue; - m_partSelect[i]->setToggleState(false, juce::dontSendNotification); - } - } - - void Parts::updateSingleOrMultiMode() const - { - const auto multiMode = m_editor.getController().isMultiMode(); - - const auto partCount = multiMode ? static_cast<AudioPluginAudioProcessor&>(m_editor.getProcessor()).getPartCount() : 1; - - for(size_t i=0; i<m_partSelect.size(); ++i) - { - const bool visible = i < partCount; - - VirusEditor::setEnabled(*m_partSelect[i], visible); - VirusEditor::setEnabled(*m_partPan[i], visible); - VirusEditor::setEnabled(*m_partVolume[i], visible); - - if(i < m_presetPrev.size()) - VirusEditor::setEnabled(*m_presetPrev[i], visible); - - if(i < m_presetNext.size()) - VirusEditor::setEnabled(*m_presetNext[i], visible); - - m_presetName[i]->setVisible(visible); - } - - const auto volumeParam = m_editor.getController().getParameterIndexByName(multiMode ? Virus::g_paramPartVolume : Virus::g_paramPatchVolume); - m_editor.getParameterBinding().bind(*m_partVolume[0], volumeParam, 0); - m_partVolume[0]->getProperties().set("parameter", static_cast<int>(volumeParam)); - } - - void Parts::timerCallback() - { - auto* device = dynamic_cast<virusLib::Device*>(m_editor.getProcessor().getPlugin().getDevice()); - - if(!device) - return; - - auto& fpState = device->getFrontpanelState(); - - const uint32_t maxPart = m_editor.getController().isMultiMode() ? 16 : 1; - - for(uint32_t i=0; i<m_partActive.size(); ++i) - { - m_partActive[i]->setVisible(i < maxPart && fpState.m_midiEventReceived[i]); - fpState.m_midiEventReceived[i] = false; - } - } - - void Parts::updateAll() const - { - updatePresetNames(); - updateSelectedPart(); - updateSingleOrMultiMode(); - } -} diff --git a/source/jucePlugin/ui3/PatchManager.cpp b/source/jucePlugin/ui3/PatchManager.cpp @@ -1,438 +0,0 @@ -#include "PatchManager.h" - -#include "VirusEditor.h" -#include "../VirusController.h" - -#include "../../jucePluginLib/patchdb/datasource.h" -#include "../../jucePluginEditorLib/pluginEditor.h" - -#include "../../virusLib/microcontroller.h" -#include "../../virusLib/device.h" -#include "../../virusLib/midiFileToRomData.h" - -#include "../../synthLib/midiToSysex.h" -#include "../../synthLib/os.h" - -#include "juce_cryptography/hashing/juce_MD5.h" - -namespace Virus -{ - class Controller; -} - -namespace genericVirusUI -{ - PatchManager::PatchManager(VirusEditor& _editor, juce::Component* _root, const juce::File& _dir) : jucePluginEditorLib::patchManager::PatchManager(_editor, _root, _dir), m_controller(_editor.getController()) - { - addRomPatches(); - - startLoaderThread(); - - // rom patches are received via midi, make sure we add all remaining ones, too - m_controller.onRomPatchReceived = [this](const virusLib::BankNumber _bank, const uint32_t _program) - { - if (_bank == virusLib::BankNumber::EditBuffer) - return; - - const auto index = virusLib::toArrayIndex(_bank); - - const auto& banks = m_controller.getSinglePresets(); - - if(index < banks.size()) - { - const auto& bank = banks[index]; - - if(_program == bank.size() - 1) - addDataSource(createRomDataSource(index)); - } - }; - addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomA, "Virus Model"); - addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomB, "Virus Features"); - } - - PatchManager::~PatchManager() - { - stopLoaderThread(); - m_controller.onRomPatchReceived = {}; - } - - bool PatchManager::loadRomData(pluginLib::patchDB::DataList& _results, const uint32_t _bank, const uint32_t _program) - { - const auto bankIndex = _bank; - - const auto& singles = m_controller.getSinglePresets(); - - if (bankIndex >= singles.size()) - return false; - - const auto& bank = singles[bankIndex]; - - if(_program != pluginLib::patchDB::g_invalidProgram) - { - if (_program >= bank.size()) - return false; - const auto& s = bank[_program]; - if (s.data.empty()) - return false; - _results.push_back(s.data); - } - else - { - _results.reserve(bank.size()); - for (const auto& patch : bank) - _results.push_back(patch.data); - } - return true; - } - - std::shared_ptr<pluginLib::patchDB::Patch> PatchManager::initializePatch(std::vector<uint8_t>&& _sysex) - { - if (_sysex.size() < 267) - return nullptr; - - const auto& c = static_cast<const Virus::Controller&>(m_controller); - - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::AnyPartParamValues parameterValues; - - if (!c.parseSingle(data, parameterValues, _sysex)) - return nullptr; - - const auto idxVersion = c.getParameterIndexByName("Version"); - const auto idxCategory1 = c.getParameterIndexByName("Category1"); - const auto idxCategory2 = c.getParameterIndexByName("Category2"); - const auto idxUnison = c.getParameterIndexByName("Unison Mode"); -// const auto idxTranspose = c.getParameterIndexByName("Transpose"); - const auto idxArpMode = c.getParameterIndexByName("Arp Mode"); - const auto idxPhaserMix = c.getParameterIndexByName("Phaser Mix"); - const auto idxChorusMix = c.getParameterIndexByName("Chorus Mix"); - - auto patch = std::make_shared<pluginLib::patchDB::Patch>(); - - { - const auto it = data.find(pluginLib::MidiDataType::Bank); - if (it != data.end()) - patch->bank = it->second; - } - { - const auto it = data.find(pluginLib::MidiDataType::Program); - if (it != data.end()) - patch->program = it->second; - } - - { - constexpr auto frontOffset = 9; // remove bank number, program number and other stuff that we don't need, first index is the patch version - constexpr auto backOffset = 2; // remove f7 and checksum - const juce::MD5 md5(_sysex.data() + frontOffset, _sysex.size() - frontOffset - backOffset); - - static_assert(sizeof(juce::MD5) >= sizeof(pluginLib::patchDB::PatchHash)); - memcpy(patch->hash.data(), md5.getChecksumDataArray(), std::size(patch->hash)); - } - - patch->sysex = std::move(_sysex); - - patch->name = m_controller.getSinglePresetName(parameterValues); - - const auto version = virusLib::Microcontroller::getPresetVersion(*parameterValues[idxVersion]); - const auto unison = *parameterValues[idxUnison]; -// const auto transpose = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxTranspose))->second; - const auto arpMode = *parameterValues[idxArpMode]; - - const auto category1 = *parameterValues[idxCategory1]; - const auto category2 = *parameterValues[idxCategory2]; - - const auto* paramCategory1 = c.getParameter(idxCategory1, 0); - const auto* paramCategory2 = c.getParameter(idxCategory2, 0); - - auto addCategory = [&patch, version](const uint8_t _value, const pluginLib::Parameter* _param) - { - if(!_value) - return; - const auto& values = _param->getDescription().valueList; - if(_value >= values.texts.size()) - return; - - // Virus < TI had less categories - if(version < virusLib::D && _value > 16) - return; - - const auto t = _param->getDescription().valueList.valueToText(_value); - patch->tags.add(pluginLib::patchDB::TagType::Category, t); - }; - - addCategory(category1, paramCategory1); - addCategory(category2, paramCategory2); - - switch (version) - { - case virusLib::A: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "A"); break; - case virusLib::B: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "B"); break; - case virusLib::C: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "C"); break; - case virusLib::D: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "TI"); break; - case virusLib::D2: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "TI2"); break; - } - - if(arpMode) - patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Arp"); - if(unison) - patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Unison"); - if(*parameterValues[idxPhaserMix] > 0) - patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Phaser"); - if(*parameterValues[idxChorusMix] > 0) - patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Chorus"); - return patch; - } - - pluginLib::patchDB::Data PatchManager::prepareSave(const pluginLib::patchDB::PatchPtr& _patch) const - { - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::AnyPartParamValues parameterValues; - - Virus::Controller::MidiPacketType usedPacketType; - if (!m_controller.parseSingle(data, parameterValues, _patch->sysex, usedPacketType)) - return _patch->sysex; - - // apply name - if (!_patch->getName().empty()) - m_controller.setSinglePresetName(parameterValues, _patch->getName()); - - // apply program - auto bank = toMidiByte(virusLib::BankNumber::A); - auto program = data[pluginLib::MidiDataType::Program]; - - if (_patch->program != pluginLib::patchDB::g_invalidProgram) - { - const auto bankOffset = _patch->program / 128; - program = static_cast<uint8_t>(_patch->program - bankOffset * 128); - bank += static_cast<uint8_t>(bankOffset); - } - - // apply categories - const uint32_t indicesCategory[] = { - m_controller.getParameterIndexByName("Category1"), - m_controller.getParameterIndexByName("Category2") - }; - - const pluginLib::Parameter* paramsCategory[] = { - m_controller.getParameter(indicesCategory[0], 0), - m_controller.getParameter(indicesCategory[1], 0) - }; - - uint8_t val0 = 0; - uint8_t val1 = 0; - - const auto& tags = _patch->getTags(pluginLib::patchDB::TagType::Category); - - size_t i = 0; - for (const auto& tag : tags.getAdded()) - { - const auto categoryValue = paramsCategory[i]->getDescription().valueList.textToValue(tag); - if(categoryValue != 0) - { - auto& v = i ? val1 : val0; - v = static_cast<uint8_t>(categoryValue); - ++i; - if (i == 2) - break; - } - } - - parameterValues[indicesCategory[0]] = val0; - parameterValues[indicesCategory[1]] = val1; - - return m_controller.createSingleDump(usedPacketType, bank, program, parameterValues); - } - - bool PatchManager::parseFileData(pluginLib::patchDB::DataList& _results, const pluginLib::patchDB::Data& _data) - { - { - std::vector<synthLib::SMidiEvent> events; - virusLib::Device::parseTIcontrolPreset(events, _data); - - for (const auto& e : events) - { - if (!e.sysex.empty()) - _results.push_back(e.sysex); - } - - if (!_results.empty()) - return true; - } - - if (virusLib::Device::parsePowercorePreset(_results, _data)) - return true; - - if(!synthLib::MidiToSysex::extractSysexFromData(_results, _data)) - return false; - - if(!_results.empty()) - { - if(_data.size() > 500000) - { - virusLib::MidiFileToRomData romLoader; - - for (const auto& result : _results) - { - if(!romLoader.add(result)) - break; - } - if(romLoader.isComplete()) - { - const auto& data = romLoader.getData(); - - if(data.size() > 0x10000) - { - // presets are written to ROM address 0x50000, the second half of an OS update is therefore at 0x10000 - constexpr ptrdiff_t startAddr = 0x10000; - ptrdiff_t addr = startAddr; - uint32_t index = 0; - - while(addr + 0x100 <= static_cast<ptrdiff_t>(data.size())) - { - std::vector chunk(data.begin() + addr, data.begin() + addr + 0x100); - - // validate -// const auto idxH = chunk[2]; - const auto idxL = chunk[3]; - - if(/*idxH != (index >> 7) || */idxL != (index & 0x7f)) - break; - - bool validName = true; - for(size_t i=240; i<240+10; ++i) - { - if(chunk[i] < 32 || chunk[i] > 128) - { - validName = false; - break; - } - } - - if(!validName) - continue; - - addr += 0x100; - ++index; - } - - if(index > 0) - { - _results.clear(); - - for(uint32_t i=0; i<index; ++i) - { - // pack into sysex - std::vector<uint8_t>& sysex = _results.emplace_back(std::vector<uint8_t> - {0xf0, 0x00, 0x20, 0x33, 0x01, virusLib::OMNI_DEVICE_ID, 0x10, static_cast<uint8_t>(0x01 + (i >> 7)), static_cast<uint8_t>(i & 0x7f)} - ); - sysex.insert(sysex.end(), data.begin() + i * 0x100 + startAddr, data.begin() + i * 0x100 + 0x100 + startAddr); - sysex.push_back(virusLib::Microcontroller::calcChecksum(sysex, 5)); - sysex.push_back(0xf7); - } - } - } - } - } - - } - - return !_results.empty(); - } - - bool PatchManager::requestPatchForPart(pluginLib::patchDB::Data& _data, const uint32_t _part) - { - _data = m_controller.createSingleDump(static_cast<uint8_t>(_part), toMidiByte(virusLib::BankNumber::A), 0); - return !_data.empty(); - } - - uint32_t PatchManager::getCurrentPart() const - { - return m_controller.getCurrentPart(); - } - - bool PatchManager::equals(const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) const - { - pluginLib::MidiPacket::Data dataA, dataB; - pluginLib::MidiPacket::AnyPartParamValues parameterValuesA, parameterValuesB; - - if (!m_controller.parseSingle(dataA, parameterValuesA, _a->sysex) || !m_controller.parseSingle(dataB, parameterValuesB, _b->sysex)) - return false; - - if(parameterValuesA.size() != parameterValuesB.size()) - return false; - - for(uint32_t i=0; i<parameterValuesA.size(); ++i) - { - const auto& itA = parameterValuesA[i]; - const auto& itB = parameterValuesB[i]; - - if(!itA) - { - if(itB) - return false; - continue; - } - - if(!itB) - return false; - - auto vA = *itA; - auto vB = *itB; - - if(vA != vB) - { - // parameters might be out of range because some dumps have values that are out of range indeed, clamp to valid range and compare again - const auto* param = m_controller.getParameter(i); - if(!param) - return false; - - if(param->getDescription().isNonPartSensitive()) - continue; - - vA = static_cast<uint8_t>(param->getDescription().range.clipValue(vA)); - vB = static_cast<uint8_t>(param->getDescription().range.clipValue(vB)); - - if(vA != vB) - return false; - } - } - return true; - } - - bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch) - { - return m_controller.activatePatch(_patch->sysex); - } - - bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _part) - { - return m_controller.activatePatch(_patch->sysex, _part); - } - - void PatchManager::addRomPatches() - { - const auto& singles = m_controller.getSinglePresets(); - - for (uint32_t b = 0; b < singles.size(); ++b) - { - const auto& bank = singles[b]; - - const auto& single = bank[bank.size()-1]; - - if (single.data.empty()) - continue; - - addDataSource(createRomDataSource(b)); - } - } - - pluginLib::patchDB::DataSource PatchManager::createRomDataSource(const uint32_t _bank) const - { - pluginLib::patchDB::DataSource ds; - ds.type = pluginLib::patchDB::SourceType::Rom; - ds.bank = _bank; - ds.name = m_controller.getBankName(_bank); - return ds; - } - -} diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp @@ -1,471 +0,0 @@ -#include "VirusEditor.h" - -#include "ArpUserPattern.h" -#include "BinaryData.h" -#include "PartButton.h" - -#include "../ParameterNames.h" -#include "../PluginProcessor.h" -#include "../VirusController.h" -#include "../version.h" - -#include "../../jucePluginLib/parameterbinding.h" -#include "../../jucePluginEditorLib/patchmanager/savepatchdesc.h" - -#include "../../synthLib/os.h" - -namespace genericVirusUI -{ - VirusEditor::VirusEditor(pluginLib::ParameterBinding& _binding, AudioPluginAudioProcessor& _processorRef, const std::string& _jsonFilename, std::string _skinFolder, std::function<void()> _openMenuCallback) : - Editor(_processorRef, _binding, std::move(_skinFolder)), - m_processor(_processorRef), - m_parameterBinding(_binding), - m_openMenuCallback(std::move(_openMenuCallback)), - m_romChangedListener(_processorRef.evRomChanged) - { - create(_jsonFilename); - - m_parts.reset(new Parts(*this)); - m_leds.reset(new Leds(*this, _processorRef)); - - // be backwards compatible with old skins - if(getTabGroupCount() == 0) - m_tabs.reset(new Tabs(*this)); - - // be backwards compatible with old skins - if(getControllerLinkCountRecursive() == 0) - m_controllerLinks.reset(new ControllerLinks(*this)); - - m_midiPorts.reset(new jucePluginEditorLib::MidiPorts(*this, getProcessor())); - - // be backwards compatible with old skins - if(!getConditionCountRecursive()) - m_fxPage.reset(new FxPage(*this)); - - const auto configOptions = getProcessor().getConfigOptions(); - const auto dir = configOptions.getDefaultFile().getParentDirectory(); - - { - auto pmParent = findComponent("ContainerPatchManager", false); - if(!pmParent) - pmParent = findComponent("page_presets", false); - if(!pmParent) - pmParent = findComponent("page_2_browser"); - setPatchManager(new PatchManager(*this, pmParent, dir)); - } - - m_presetName = findComponentT<juce::Label>("PatchName"); - - m_focusedParameter.reset(new jucePluginEditorLib::FocusedParameter(getController(), m_parameterBinding, *this)); - - m_romSelector = findComponentT<juce::ComboBox>("RomSelector"); - - m_playModeSingle = findComponentT<juce::Button>("PlayModeSingle", false); - m_playModeMulti = findComponentT<juce::Button>("PlayModeMulti", false); - - if(m_playModeSingle && m_playModeMulti) - { - m_playModeSingle->onClick = [this]{ if(m_playModeSingle->getToggleState()) setPlayMode(virusLib::PlayMode::PlayModeSingle); }; - m_playModeMulti->onClick = [this]{ if(m_playModeMulti->getToggleState()) setPlayMode(virusLib::PlayMode::PlayModeMulti); }; - } - else - { - m_playModeToggle = findComponentT<juce::Button>("PlayModeToggle"); - m_playModeToggle->onClick = [this]{ setPlayMode(m_playModeToggle->getToggleState() ? virusLib::PlayMode::PlayModeMulti : virusLib::PlayMode::PlayModeSingle); }; - } - - if(m_romSelector) - { - const auto roms = m_processor.getRoms(); - - if(roms.empty()) - { - m_romSelector->addItem("<No ROM found>", 1); - } - else - { - int id = 1; - - for (const auto& rom : roms) - { - const auto name = juce::File(rom.getFilename()).getFileNameWithoutExtension(); - m_romSelector->addItem(name + " (" + rom.getModelName() + ')', id++); - } - } - - m_romSelector->setSelectedId(static_cast<int>(m_processor.getSelectedRomIndex()) + 1, juce::dontSendNotification); - - m_romSelector->onChange = [this, roms] - { - const auto oldIndex = m_processor.getSelectedRomIndex(); - const auto newIndex = m_romSelector->getSelectedId() - 1; - if(!m_processor.setSelectedRom(newIndex)) - m_romSelector->setSelectedId(static_cast<int>(oldIndex) + 1); - }; - } - - getController().onProgramChange = [this](int _part) { onProgramChange(_part); }; - - addMouseListener(this, true); - - if(auto* versionInfo = findComponentT<juce::Label>("VersionInfo", false)) - { - const std::string message = "DSP 56300 Emulator Version " + std::string(g_pluginVersionString) + " - " __DATE__ " " __TIME__; - versionInfo->setText(message, juce::dontSendNotification); - } - - if(auto* versionNumber = findComponentT<juce::Label>("VersionNumber", false)) - { - versionNumber->setText(g_pluginVersionString, juce::dontSendNotification); - } - - m_deviceModel = findComponentT<juce::Label>("DeviceModel", false); - - auto* presetSave = findComponentT<juce::Button>("PresetSave", false); - if(presetSave) - presetSave->onClick = [this] { savePreset(); }; - - auto* presetLoad = findComponentT<juce::Button>("PresetLoad", false); - if(presetLoad) - presetLoad->onClick = [this] { loadPreset(); }; - - m_presetName->setEditable(false, true, true); - m_presetName->onTextChange = [this]() - { - const auto text = m_presetName->getText(); - if (text.trim().length() > 0) - { - getController().setSinglePresetName(getController().getCurrentPart(), text); - onProgramChange(getController().getCurrentPart()); - } - }; - m_presetNameMouseListener = new PartMouseListener(pluginLib::MidiPacket::AnyPart, [this](const juce::MouseEvent& _mouseEvent, int ) - { - startDragging(new jucePluginEditorLib::patchManager::SavePatchDesc(getController().getCurrentPart()), m_presetName); - }); - m_presetName->addMouseListener(m_presetNameMouseListener, false); - - auto* menuButton = findComponentT<juce::Button>("Menu", false); - - if(menuButton) - menuButton->onClick = m_openMenuCallback; - - updatePresetName(); - updatePlayModeButtons(); - - m_romChangedListener = [this](auto) - { - updateDeviceModel(); - updateKeyValueConditions("deviceModel", virusLib::getModelName(m_processor.getModel())); - m_parts->onPlayModeChanged(); - }; - } - - VirusEditor::~VirusEditor() - { - m_presetName->removeMouseListener(m_presetNameMouseListener); - delete m_presetNameMouseListener; - m_presetNameMouseListener = nullptr; - - m_focusedParameter.reset(); - - m_parameterBinding.clearBindings(); - - getController().onProgramChange = nullptr; - } - - Virus::Controller& VirusEditor::getController() const - { - return static_cast<Virus::Controller&>(m_processor.getController()); - } - - const char* VirusEditor::findEmbeddedResource(const std::string& _filename, uint32_t& _size) - { - for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) - { - if (BinaryData::originalFilenames[i] != _filename) - continue; - - int size = 0; - const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); - _size = static_cast<uint32_t>(size); - return res; - } - return nullptr; - } - - const char* VirusEditor::findResourceByFilename(const std::string& _filename, uint32_t& _size) - { - return findEmbeddedResource(_filename, _size); - } - - std::pair<std::string, std::string> VirusEditor::getDemoRestrictionText() const - { - return { - JucePlugin_Name " - Demo Mode", - JucePlugin_Name " runs in demo mode, the following restrictions apply:\n" - "\n" - "* The plugin state is not preserved\n" - "* Preset saving is disabled"}; - } - - genericUI::Button<juce::TextButton>* VirusEditor::createJuceComponent(genericUI::Button<juce::TextButton>* _button, genericUI::UiObject& _object) - { - if(_object.getName() == "PresetName") - return new PartButton(*this); - - return Editor::createJuceComponent(_button, _object); - } - - juce::Component* VirusEditor::createJuceComponent(juce::Component* _component, genericUI::UiObject& _object) - { - if(_object.getName() == "ArpUserGraphics") - return new ArpUserPattern(*this); - - return Editor::createJuceComponent(_component, _object); - } - - void VirusEditor::onProgramChange(int _part) - { - m_parts->onProgramChange(); - updatePresetName(); - updatePlayModeButtons(); - if(getPatchManager()) - getPatchManager()->onProgramChanged(_part); - } - - void VirusEditor::onPlayModeChanged() - { - m_parts->onPlayModeChanged(); - updatePresetName(); - updatePlayModeButtons(); - } - - void VirusEditor::onCurrentPartChanged() - { - m_parts->onCurrentPartChanged(); - updatePresetName(); - } - - void VirusEditor::mouseEnter(const juce::MouseEvent& event) - { - m_focusedParameter->onMouseEnter(event); - } - - void VirusEditor::updatePresetName() const - { - m_presetName->setText(getController().getCurrentPartPresetName(getController().getCurrentPart()), juce::dontSendNotification); - } - - void VirusEditor::updatePlayModeButtons() const - { - if(m_playModeSingle) - m_playModeSingle->setToggleState(!getController().isMultiMode(), juce::dontSendNotification); - if(m_playModeMulti) - m_playModeMulti->setToggleState(getController().isMultiMode(), juce::dontSendNotification); - if(m_playModeToggle) - m_playModeToggle->setToggleState(getController().isMultiMode(), juce::dontSendNotification); - } - - void VirusEditor::updateDeviceModel() - { - if(!m_deviceModel) - return; - - std::string m; - - switch(m_processor.getModel()) - { - case virusLib::DeviceModel::Invalid: - return; - case virusLib::DeviceModel::ABC: - { - auto* rom = m_processor.getSelectedRom(); - if(!rom) - return; - - virusLib::ROMFile::TPreset data; - if(!rom->getSingle(0, 0, data)) - return; - - switch(virusLib::Microcontroller::getPresetVersion(data.front())) - { - case virusLib::A: m = "A"; break; - case virusLib::B: m = "B"; break; - case virusLib::C: m = "C"; break; - case virusLib::D: m = "TI"; break; - case virusLib::D2: m = "TI2"; break; - default: m = "?"; break; - } - } - break; - case virusLib::DeviceModel::Snow: m = "Snow"; break; - case virusLib::DeviceModel::TI: m = "TI"; break; - case virusLib::DeviceModel::TI2: m = "TI2"; break; - } - - m_deviceModel->setText(m, juce::dontSendNotification); - } - - void VirusEditor::savePreset() - { - juce::PopupMenu menu; - - const auto countAdded = getPatchManager()->createSaveMenuEntries(menu, getPatchManager()->getCurrentPart()); - - if(countAdded) - menu.addSeparator(); - - auto addEntry = [&](juce::PopupMenu& _menu, const std::string& _name, const std::function<void(jucePluginEditorLib::FileType)>& _callback) - { - juce::PopupMenu subMenu; - - subMenu.addItem(".syx", [_callback](){_callback(jucePluginEditorLib::FileType::Syx); }); - subMenu.addItem(".mid", [_callback](){_callback(jucePluginEditorLib::FileType::Mid); }); - - _menu.addSubMenu(_name, subMenu); - }; - - addEntry(menu, "Export Current Single (Edit Buffer)", [this](jucePluginEditorLib::FileType _type) - { - savePresets(SaveType::CurrentSingle, _type); - }); - - if(getController().isMultiMode()) - { - addEntry(menu, "Export Arrangement (Multi + 16 Singles)", [this](jucePluginEditorLib::FileType _type) - { - savePresets(SaveType::Arrangement, _type); - }); - } - - juce::PopupMenu banksMenu; - for(uint8_t b=0; b<static_cast<uint8_t>(getController().getBankCount()); ++b) - { - addEntry(banksMenu, getController().getBankName(b), [this, b](const jucePluginEditorLib::FileType _type) - { - savePresets(SaveType::Bank, _type, b); - }); - } - - menu.addSubMenu("Export Bank", banksMenu); - - menu.showMenuAsync(juce::PopupMenu::Options()); - } - - void VirusEditor::loadPreset() - { - Editor::loadPreset([this](const juce::File& _result) - { - pluginLib::patchDB::DataList results; - - if(!getPatchManager()->loadFile(results, _result.getFullPathName().toStdString())) - return; - - auto& c = getController(); - - // we attempt to convert all results as some of them might not be valid preset data - for(size_t i=0; i<results.size();) - { - // convert to load to edit buffer of current part - const auto data = c.modifySingleDump(results[i], virusLib::BankNumber::EditBuffer, c.isMultiMode() ? c.getCurrentPart() : virusLib::SINGLE); - if(data.empty()) - results.erase(results.begin() + i); - else - results[i++] = data; - } - - if (results.size() == 1) - { - c.activatePatch(results.front()); - } - else if(results.size() > 1) - { - juce::NativeMessageBox::showMessageBox(juce::AlertWindow::InfoIcon, "Information", - "The selected file contains more than one patch. Please add this file as a data source in the Patch Manager instead.\n\n" - "Go to the Patch Manager, right click the 'Data Sources' node and select 'Add File...' to import it." - ); - } - }); - } - - void VirusEditor::setPlayMode(uint8_t _playMode) - { - const auto playMode = getController().getParameterIndexByName(Virus::g_paramPlayMode); - - auto* param = getController().getParameter(playMode); - param->setValue(param->convertTo0to1(_playMode), pluginLib::Parameter::ChangedBy::Ui); - - // we send this directly here as we request a new arrangement below, we don't want to wait on juce to inform the knob to have changed - getController().sendParameterChange(*param, _playMode); - - if (_playMode == virusLib::PlayModeSingle && getController().getCurrentPart() != 0) - setPart(0); - - onPlayModeChanged(); - - getController().requestArrangement(); - } - - void VirusEditor::savePresets(SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber/* = 0*/) - { - Editor::savePreset([this, _saveType, _bankNumber, _fileType](const juce::File& _result) - { - jucePluginEditorLib::FileType fileType = _fileType; - const auto file = createValidFilename(fileType, _result); - savePresets(file, _saveType, fileType, _bankNumber); - }); - } - - bool VirusEditor::savePresets(const std::string& _pathName, SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber/* = 0*/) const - { -#if SYNTHLIB_DEMO_MODE - return false; -#else - std::vector< std::vector<uint8_t> > messages; - - switch (_saveType) - { - case SaveType::CurrentSingle: - { - const auto dump = getController().createSingleDump(getController().getCurrentPart(), toMidiByte(virusLib::BankNumber::A), 0); - messages.push_back(dump); - } - break; - case SaveType::Bank: - { - const auto& presets = getController().getSinglePresets(); - if(_bankNumber < presets.size()) - { - const auto& bankPresets = presets[_bankNumber]; - for (const auto& bankPreset : bankPresets) - messages.push_back(bankPreset.data); - } - } - break; - case SaveType::Arrangement: - { - messages.push_back(getController().getMultiEditBuffer().data); - - for(uint8_t i=0; i<16; ++i) - { - const auto dump = getController().createSingleDump(i, toMidiByte(virusLib::BankNumber::EditBuffer), i); - messages.push_back(dump); - } - } - break; - default: - return false; - } - - return Editor::savePresets(_fileType, _pathName, messages); -#endif - } - - void VirusEditor::setPart(size_t _part) - { - m_parameterBinding.setPart(static_cast<uint8_t>(_part)); - onCurrentPartChanged(); - setCurrentPart(static_cast<uint8_t>(_part)); - } -} diff --git a/source/jucePlugin/ui3/VirusEditor.h b/source/jucePlugin/ui3/VirusEditor.h @@ -1,111 +0,0 @@ -#pragma once - -#include "../../jucePluginEditorLib/midiPorts.h" -#include "../../jucePluginEditorLib/pluginEditor.h" -#include "../../jucePluginEditorLib/focusedParameter.h" - -#include "../../jucePluginLib/event.h" - -#include "Parts.h" -#include "Tabs.h" -#include "FxPage.h" -#include "PatchManager.h" -#include "ControllerLinks.h" -#include "Leds.h" - -namespace virusLib -{ - class ROMFile; -} - -namespace jucePluginEditorLib -{ - class FocusedParameter; -} - -namespace pluginLib -{ - class Parameter; - class ParameterBinding; -} - -class AudioPluginAudioProcessor; - -namespace genericVirusUI -{ - class VirusEditor : public jucePluginEditorLib::Editor - { - public: - enum class SaveType - { - CurrentSingle, - Bank, - Arrangement - }; - - VirusEditor(pluginLib::ParameterBinding& _binding, AudioPluginAudioProcessor& _processorRef, const std::string& _jsonFilename, - std::string _skinFolder, std::function<void()> _openMenuCallback); - ~VirusEditor() override; - - void setPart(size_t _part); - - pluginLib::ParameterBinding& getParameterBinding() const { return m_parameterBinding; } - - Virus::Controller& getController() const; - - static const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size); - const char* findResourceByFilename(const std::string& _filename, uint32_t& _size) override; - - std::pair<std::string, std::string> getDemoRestrictionText() const override; - - genericUI::Button<juce::TextButton>* createJuceComponent(genericUI::Button<juce::TextButton>*, genericUI::UiObject& _object) override; - juce::Component* createJuceComponent(juce::Component*, genericUI::UiObject& _object) override; - - private: - void onProgramChange(int _part); - void onPlayModeChanged(); - void onCurrentPartChanged(); - - void mouseEnter(const juce::MouseEvent& event) override; - - void updatePresetName() const; - void updatePlayModeButtons() const; - - void updateDeviceModel(); - - void savePreset(); - void loadPreset(); - - void setPlayMode(uint8_t _playMode); - - void savePresets(SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber = 0); - bool savePresets(const std::string& _pathName, SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber = 0) const; - - AudioPluginAudioProcessor& m_processor; - pluginLib::ParameterBinding& m_parameterBinding; - - std::unique_ptr<Parts> m_parts; - std::unique_ptr<Leds> m_leds; - std::unique_ptr<Tabs> m_tabs; - std::unique_ptr<jucePluginEditorLib::MidiPorts> m_midiPorts; - std::unique_ptr<FxPage> m_fxPage; - std::unique_ptr<ControllerLinks> m_controllerLinks; - - juce::Label* m_presetName = nullptr; - PartMouseListener* m_presetNameMouseListener = nullptr; - - std::unique_ptr<jucePluginEditorLib::FocusedParameter> m_focusedParameter; - - juce::ComboBox* m_romSelector = nullptr; - - juce::Button* m_playModeSingle = nullptr; - juce::Button* m_playModeMulti = nullptr; - juce::Button* m_playModeToggle = nullptr; - - juce::Label* m_deviceModel = nullptr; - - std::function<void()> m_openMenuCallback; - - pluginLib::EventListener<const virusLib::ROMFile*> m_romChangedListener; - }; -} diff --git a/source/osTIrusJucePlugin/CMakeLists.txt b/source/osTIrusJucePlugin/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.15) +project(osTIrusJucePlugin VERSION ${CMAKE_PROJECT_VERSION}) + +set(SOURCES + parameterDescriptions_TI.json + PluginEditorState.cpp + PluginEditorState.h + PluginProcessor.cpp + PluginProcessor.h + version.h +) + +SET(ASSETS "parameterDescriptions_TI.json") + +include(skins/TrancyTI/assets.cmake) + +juce_add_binary_data(osTIrusJucePlugin_BinaryData SOURCES ${ASSETS} ${ASSETS_VirusTI_Trancy}) + +createJucePluginWithFX(osTIrusJucePlugin "OsTIrus" "Ttip" "Ttif" osTIrusJucePlugin_BinaryData virusJucePlugin) diff --git a/source/osTIrusJucePlugin/PluginEditorState.cpp b/source/osTIrusJucePlugin/PluginEditorState.cpp @@ -0,0 +1,12 @@ +#include "PluginEditorState.h" + +#include "PluginProcessor.h" + +const std::vector<jucePluginEditorLib::PluginEditorState::Skin> g_includedSkins = +{ + {"TI Trancy", "VirusTI_Trancy.json", ""} +}; + +OsTIrusEditorState::OsTIrusEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller) : PluginEditorState(_processor, _controller, g_includedSkins) +{ +} diff --git a/source/osTIrusJucePlugin/PluginEditorState.h b/source/osTIrusJucePlugin/PluginEditorState.h @@ -0,0 +1,11 @@ +#pragma once + +#include "../virusJucePlugin/PluginEditorState.h" + +class AudioPluginAudioProcessor; + +class OsTIrusEditorState : public PluginEditorState +{ +public: + explicit OsTIrusEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller); +}; diff --git a/source/osTIrusJucePlugin/PluginProcessor.cpp b/source/osTIrusJucePlugin/PluginProcessor.cpp @@ -0,0 +1,65 @@ +#include "PluginProcessor.h" +#include "PluginEditorState.h" +#include "BinaryData.h" + +#include "../virusLib/romloader.h" + +namespace +{ + juce::PropertiesFile::Options getConfigOptions() + { + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300Emulator_OsTIrus"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300Emulator_OsTIrus"; + opts.osxLibrarySubFolder = "Application Support/DSP56300Emulator_OsTIrus"; + return opts; + } +} + +//============================================================================== +OsTIrusProcessor::OsTIrusProcessor() : + AudioPluginAudioProcessor(BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true) +#if JucePlugin_IsSynth + .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) + .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) + .withOutput("USB 1", juce::AudioChannelSet::stereo(), true) + .withOutput("USB 2", juce::AudioChannelSet::stereo(), true) + .withOutput("USB 3", juce::AudioChannelSet::stereo(), true) +#endif + , ::getConfigOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect} + , virusLib::ROMLoader::findROMs(virusLib::DeviceModel::TI2, virusLib::DeviceModel::Snow)) +{ +} + +OsTIrusProcessor::~OsTIrusProcessor() +{ + destroyEditorState(); +} + +const char* OsTIrusProcessor::findEmbeddedResource(const char* _name, uint32_t& _size) const +{ + for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + { + if (std::string(BinaryData::originalFilenames[i]) != std::string(_name)) + continue; + + int size = 0; + const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); + _size = static_cast<uint32_t>(size); + return res; + } + return nullptr; +} + +jucePluginEditorLib::PluginEditorState* OsTIrusProcessor::createEditorState() +{ + return new OsTIrusEditorState(*this, getController()); +} + +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new OsTIrusProcessor(); +} diff --git a/source/osTIrusJucePlugin/PluginProcessor.h b/source/osTIrusJucePlugin/PluginProcessor.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../virusJucePlugin/PluginProcessor.h" + +//============================================================================== +class OsTIrusProcessor : public AudioPluginAudioProcessor +{ +public: + OsTIrusProcessor(); + ~OsTIrusProcessor() override; + const char* findEmbeddedResource(const char* _name, uint32_t& _size) const override; + jucePluginEditorLib::PluginEditorState* createEditorState() override; +}; diff --git a/source/osTIrusJucePlugin/version.h b/source/osTIrusJucePlugin/version.h @@ -0,0 +1,4 @@ +#pragma once + +static constexpr const char* const g_pluginVersionString = "1.3.12"; +static constexpr uint32_t g_pluginVersion = 1312; diff --git a/source/jucePlugin/version.h.in b/source/osTIrusJucePlugin/version.h.in diff --git a/source/jucePlugin/.gitignore b/source/osirusJucePlugin/.gitignore diff --git a/source/osirusJucePlugin/CMakeLists.txt b/source/osirusJucePlugin/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.15) +project(osirusJucePlugin VERSION ${CMAKE_PROJECT_VERSION}) + +set(SOURCES + parameterDescriptions_C.json + PluginEditorState.cpp + PluginEditorState.h + PluginProcessor.cpp + PluginProcessor.h + version.h +) + +SET(ASSETS "parameterDescriptions_C.json") + +include(skins/Galaxpel/assets.cmake) +include(skins/Hoverland/assets.cmake) +include(skins/Trancy/assets.cmake) + +juce_add_binary_data(osirusJucePlugin_BinaryData SOURCES ${ASSETS} ${ASSETS_VirusC_Galaxpel} ${ASSETS_VirusC_Hoverland} ${ASSETS_VirusC_Trancy}) + +createJucePluginWithFX(osirusJucePlugin "Osirus" "TusV" "TusF" osirusJucePlugin_BinaryData virusJucePlugin) diff --git a/source/osirusJucePlugin/PluginEditorState.cpp b/source/osirusJucePlugin/PluginEditorState.cpp @@ -0,0 +1,14 @@ +#include "PluginEditorState.h" + +#include "PluginProcessor.h" + +const std::vector<jucePluginEditorLib::PluginEditorState::Skin> g_includedSkins = +{ + {"Hoverland", "VirusC_Hoverland.json", ""}, + {"Trancy", "VirusC_Trancy.json", ""}, + {"Galaxpel", "VirusC_Galaxpel.json", ""} +}; + +OsirusEditorState::OsirusEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller) : PluginEditorState(_processor, _controller, g_includedSkins) +{ +} diff --git a/source/osirusJucePlugin/PluginEditorState.h b/source/osirusJucePlugin/PluginEditorState.h @@ -0,0 +1,11 @@ +#pragma once + +#include "../virusJucePlugin/pluginEditorState.h" + +class AudioPluginAudioProcessor; + +class OsirusEditorState : public PluginEditorState +{ +public: + explicit OsirusEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller); +}; diff --git a/source/osirusJucePlugin/PluginProcessor.cpp b/source/osirusJucePlugin/PluginProcessor.cpp @@ -0,0 +1,62 @@ +#include "PluginProcessor.h" +#include "PluginEditorState.h" +#include "BinaryData.h" + +#include "../virusLib/romloader.h" + +namespace +{ + juce::PropertiesFile::Options getConfigOptions() + { + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300 Emulator"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300 Emulator"; + opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; + return opts; + } +} + +//============================================================================== +OsirusProcessor::OsirusProcessor() : + AudioPluginAudioProcessor(BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true) +#if JucePlugin_IsSynth + .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) + .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) +#endif + , ::getConfigOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect} + , virusLib::ROMLoader::findROMs(virusLib::DeviceModel::ABC)) +{ +} + +OsirusProcessor::~OsirusProcessor() +{ + destroyEditorState(); +} + +const char* OsirusProcessor::findEmbeddedResource(const char* _name, uint32_t& _size) const +{ + for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + { + if (std::string(BinaryData::originalFilenames[i]) != std::string(_name)) + continue; + + int size = 0; + const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); + _size = static_cast<uint32_t>(size); + return res; + } + return nullptr; +} + +jucePluginEditorLib::PluginEditorState* OsirusProcessor::createEditorState() +{ + return new OsirusEditorState(*this, getController()); +} + +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new OsirusProcessor(); +} diff --git a/source/osirusJucePlugin/PluginProcessor.h b/source/osirusJucePlugin/PluginProcessor.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../virusJucePlugin/PluginProcessor.h" + +class OsirusProcessor : public AudioPluginAudioProcessor +{ +public: + OsirusProcessor(); + ~OsirusProcessor() override; + + const char* findEmbeddedResource(const char* _name, uint32_t& _size) const override; + + jucePluginEditorLib::PluginEditorState* createEditorState() override; +}; diff --git a/source/jucePlugin/version.h.in b/source/osirusJucePlugin/version.h.in diff --git a/source/virusJucePlugin/ArpUserPattern.cpp b/source/virusJucePlugin/ArpUserPattern.cpp @@ -0,0 +1,125 @@ +#include "ArpUserPattern.h" + +#include "VirusEditor.h" + +#include "VirusController.h" + +#include "../juceUiLib/uiObjectStyle.h" + +namespace genericVirusUI +{ + namespace + { + constexpr uint32_t g_listenerId = 0xaa; + } + + ArpUserPattern::ArpUserPattern(const VirusEditor& _editor) : m_controller(_editor.getController()) + { + bindParameters(); + } + + void ArpUserPattern::paint(juce::Graphics& g) + { + if(!m_patternLength) + return; + + if(m_gradientStrength <= 0.0f) + { + genericUI::UiObjectStyle::parseColor(m_colRectFillActive, getProperties()["colorActive"].toString().toStdString()); + genericUI::UiObjectStyle::parseColor(m_colRectFillInactive, getProperties()["colorInactive"].toString().toStdString()); + + m_gradientStrength = getProperties()["gradient"]; + + if(m_gradientStrength <= 0.0f) + m_gradientStrength = 0.5f; + } + + const auto w = (float)getWidth(); + const auto h = (float)getHeight(); + + const auto maxstepW = static_cast<float>(getWidth()) / static_cast<float>(m_steps.size()); + + auto makeRectGradient = [&](const juce::Colour& _color) + { + const auto& c = _color; + return juce::ColourGradient::vertical(c, 0.0f, c.darker(m_gradientStrength), h); + }; + + const auto rectGradientActive = makeRectGradient(m_colRectFillActive); + const auto rectGradientInactive = makeRectGradient(m_colRectFillInactive); + + float x = 0.0f; + + for(int i=0; i<std::min(m_patternLength->getUnnormalizedValue() + 1, static_cast<int>(m_steps.size())); ++i) + { + const auto stepW = m_steps[i].length->getValue() * maxstepW; + const auto stepH = m_steps[i].velocity->getValue() * h; + const auto y = h - stepH; + + g.setGradientFill(m_steps[i].bitfield->getUnnormalizedValue() > 0 ? rectGradientActive : rectGradientInactive); + g.fillRect(x, y, stepW, stepH); + + x += maxstepW; + } + } + + void ArpUserPattern::onCurrentPartChanged() + { + } + + void ArpUserPattern::bindParameters() + { + for(size_t s=0; s<m_steps.size(); ++s) + { + auto& step = m_steps[s]; + + step.length = bindParameter("Step " + std::to_string(s+1) +" Length"); + step.velocity = bindParameter("Step " + std::to_string(s+1) +" Velocity"); + step.bitfield = bindParameter("Step " + std::to_string(s+1) +" Bitfield"); + } + + m_patternLength = bindParameter("Arpeggiator/UserPatternLength"); + } + + void ArpUserPattern::unbindParameter(pluginLib::Parameter*& _parameter) + { + assert(_parameter); + _parameter->removeListener(g_listenerId); + _parameter = nullptr; + } + + void ArpUserPattern::unbindParameters() + { + if(!m_patternLength) + return; + + unbindParameter(m_patternLength); + + for (auto& s : m_steps) + { + unbindParameter(s.length); + unbindParameter(s.velocity); + unbindParameter(s.bitfield); + } + } + + pluginLib::Parameter* ArpUserPattern::bindParameter(const std::string& _name) + { + const auto idx = m_controller.getParameterIndexByName(_name); + assert(idx != pluginLib::Controller::InvalidParameterIndex); + auto* p = m_controller.getParameter(idx, m_controller.getCurrentPart()); + assert(p); + + p->onValueChanged.emplace_back(g_listenerId, [this]() + { + onParameterChanged(); + }); + + return p; + } + + void ArpUserPattern::onParameterChanged() + { + repaint(); + } +} diff --git a/source/jucePlugin/ui3/ArpUserPattern.h b/source/virusJucePlugin/ArpUserPattern.h diff --git a/source/virusJucePlugin/CMakeLists.txt b/source/virusJucePlugin/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.15) +project(virusJucePlugin VERSION ${CMAKE_PROJECT_VERSION}) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h) + +set(SOURCES + ArpUserPattern.cpp + ArpUserPattern.h + ControllerLinks.cpp + ControllerLinks.h + FxPage.cpp + FxPage.h + Leds.cpp + Leds.h + ParameterNames.h + PartButton.cpp + PartButton.h + Parts.cpp + Parts.h + PatchManager.cpp + PatchManager.h + PluginEditorState.cpp + PluginEditorState.h + PluginProcessor.cpp + PluginProcessor.h + Tabs.cpp + Tabs.h + VirusController.cpp + VirusController.h + VirusEditor.cpp + VirusEditor.h +) + +add_library(virusJucePlugin STATIC) + +target_sources(virusJucePlugin PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(virusJucePlugin PUBLIC jucePluginEditorLib virusLib) + +target_include_directories(virusJucePlugin PUBLIC ../JUCE/modules) + +target_compile_definitions(virusJucePlugin PRIVATE JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1) + +set_property(TARGET virusJucePlugin PROPERTY FOLDER "Virus") diff --git a/source/jucePlugin/ui3/ControllerLinks.cpp b/source/virusJucePlugin/ControllerLinks.cpp diff --git a/source/jucePlugin/ui3/ControllerLinks.h b/source/virusJucePlugin/ControllerLinks.h diff --git a/source/virusJucePlugin/FxPage.cpp b/source/virusJucePlugin/FxPage.cpp @@ -0,0 +1,30 @@ +#include "FxPage.h" + +#include "VirusEditor.h" + +#include "ParameterNames.h" +#include "VirusController.h" + +namespace genericVirusUI +{ + FxPage::FxPage(const VirusEditor& _editor) + { + const auto delayReverbMode = _editor.getController().getParameterIndexByName(Virus::g_paramDelayReverbMode); + const auto p = _editor.getController().getParamValueObject(delayReverbMode, 0); + + if(!p) + return; + + const auto containerReverb = _editor.findComponent("ContainerReverb"); + const auto containerDelay = _editor.findComponent("ContainerDelay"); + + m_conditionReverb.reset(new genericUI::ConditionByParameterValues(*containerReverb, p, 0, {2,3,4})); + m_conditionDelay.reset(new genericUI::ConditionByParameterValues(*containerDelay, p, 0, {0,1,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26})); + } + + FxPage::~FxPage() + { + m_conditionReverb.reset(); + m_conditionDelay.reset(); + } +} diff --git a/source/jucePlugin/ui3/FxPage.h b/source/virusJucePlugin/FxPage.h diff --git a/source/virusJucePlugin/Leds.cpp b/source/virusJucePlugin/Leds.cpp @@ -0,0 +1,56 @@ +#include "Leds.h" + +#include "VirusEditor.h" + +#include "PluginProcessor.h" + +namespace genericVirusUI +{ + constexpr const char* g_lfoNames[3] = {"Lfo1LedOn", "Lfo2LedOn", "Lfo3LedOn"}; + + Leds::Leds(const genericUI::Editor& _editor, AudioPluginAudioProcessor& _processor) + { + for(size_t i=0; i<m_lfos.size(); ++i) + { + if(auto* comp = _editor.findComponentT<juce::Component>(g_lfoNames[i], false)) + { + m_lfos[i].reset(new jucePluginEditorLib::Led(comp)); + m_lfos[i]->setSourceCallback([i, &_processor] + { + auto* d = dynamic_cast<virusLib::Device*>(_processor.getPlugin().getDevice()); + + if(!d) + return 0.0f; + + const auto v = std::clamp(d->getFrontpanelState().m_lfoPhases[i], 0.0f, 1.0f); + return std::pow(1.0f - v, 0.2f); + }); + } + } + + if(auto* comp = _editor.findComponentT<juce::Component>("logolight", false)) + { + m_logo.reset(new jucePluginEditorLib::Led(comp)); + + m_logo->setSourceCallback([&_processor] + { + auto* d = dynamic_cast<virusLib::Device*>(_processor.getPlugin().getDevice()); + + if(!d) + return 0.0f; + + const auto& s = d->getFrontpanelState(); + + const auto v = std::clamp(_processor.getModel() == virusLib::DeviceModel::Snow ? s.m_bpm : s.m_logo, 0.0f, 1.0f); + + return std::pow(1.0f - v, 0.2f); + }); + } + } + + Leds::~Leds() + { + for (auto& led : m_lfos) + led.reset(); + } +} diff --git a/source/jucePlugin/ui3/Leds.h b/source/virusJucePlugin/Leds.h diff --git a/source/jucePlugin/ParameterNames.h b/source/virusJucePlugin/ParameterNames.h diff --git a/source/virusJucePlugin/PartButton.cpp b/source/virusJucePlugin/PartButton.cpp @@ -0,0 +1,83 @@ +#include "PartButton.h" + +#include "VirusEditor.h" +#include "VirusController.h" +#include "../../jucePluginEditorLib/patchmanager/list.h" + +#include "../../jucePluginEditorLib/patchmanager/savepatchdesc.h" + +namespace genericVirusUI +{ + PartButton::PartButton(VirusEditor& _editor) : jucePluginEditorLib::PartButton<TextButton>(_editor), m_editor(_editor) // NOLINT(clang-diagnostic-undefined-func-template) + { + } + + bool PartButton::isInterestedInDragSource(const SourceDetails& _dragSourceDetails) + { + if(getPart() > 0 && !m_editor.getController().isMultiMode()) + return false; + + return jucePluginEditorLib::PartButton<TextButton>::isInterestedInDragSource(_dragSourceDetails); // NOLINT(clang-diagnostic-undefined-func-template) + } + + void PartButton::paint(juce::Graphics& g) + { + jucePluginEditorLib::PartButton<TextButton>::paint(g); + } + + void PartButton::onClick() + { + selectPreset(getPart()); + } + + void PartButton::selectPreset(uint8_t _part) const + { + pluginLib::patchDB::SearchRequest req; + req.sourceType = pluginLib::patchDB::SourceType::Rom; + + m_editor.getPatchManager()->search(std::move(req), [this](const pluginLib::patchDB::Search& _search) + { + std::map<std::string, std::vector<pluginLib::patchDB::PatchPtr>> patches; + + { + std::shared_lock lock(_search.resultsMutex); + for (const auto& patch : _search.results) + { + const auto s = patch->source.lock(); + if(!s) + continue; + patches[s->name].push_back(patch); + } + } + + for (auto& it : patches) + { + std::sort(it.second.begin(), it.second.end(), [](const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) + { + return _a->program < _b->program; + }); + } + + juce::MessageManager::callAsync([this, patches = std::move(patches)] + { + juce::PopupMenu selector; + + for (const auto& it : patches) + { + juce::PopupMenu p; + for (const auto& patch : it.second) + { + const auto& presetName = patch->getName(); + p.addItem(presetName, [this, patch] + { + if(m_editor.getPatchManager()->activatePatch(patch, getPart())) + m_editor.getPatchManager()->setSelectedPatch(getPart(), patch); + }); + } + selector.addSubMenu(it.first, p); + } + selector.showMenuAsync(juce::PopupMenu::Options()); + }); + }); + } +} diff --git a/source/jucePlugin/ui3/PartButton.h b/source/virusJucePlugin/PartButton.h diff --git a/source/virusJucePlugin/Parts.cpp b/source/virusJucePlugin/Parts.cpp @@ -0,0 +1,217 @@ +#include "Parts.h" + +#include "PartButton.h" +#include "VirusEditor.h" + +#include "VirusController.h" +#include "PluginProcessor.h" +#include "ParameterNames.h" + +#include "../jucePluginEditorLib/pluginProcessor.h" +#include "../jucePluginEditorLib/patchmanager/savepatchdesc.h" + +#include "../jucePluginLib/parameterbinding.h" + +#include "../virusLib/device.h" + +namespace genericVirusUI +{ + Parts::Parts(VirusEditor& _editor) : m_editor(_editor) + { + _editor.findComponents<genericUI::Button<juce::DrawableButton>>(m_partSelect, "SelectPart"); + _editor.findComponents<juce::Button>(m_presetPrev, "PresetPrev"); + _editor.findComponents<juce::Button>(m_presetNext, "PresetNext"); + + _editor.findComponents<juce::Slider>(m_partVolume, "PartVolume"); + _editor.findComponents<juce::Slider>(m_partPan, "PartPan"); + _editor.findComponents<PartButton>(m_presetName, "PresetName"); + _editor.findComponents<juce::Component>(m_partActive, "PartActive"); + + for(size_t i=0; i<m_partSelect.size(); ++i) + { + m_partSelect[i]->onClick = [this, i]{ selectPart(i); }; + m_partSelect[i]->onDown = [this, i](const juce::MouseEvent& _e) + { + if(!_e.mods.isPopupMenu()) + return false; + selectPartMidiChannel(i); + return true; + }; + + if(i < m_presetPrev.size()) + m_presetPrev[i]->onClick = [this, i]{ selectPrevPreset(i); }; + + if(i < m_presetNext.size()) + m_presetNext[i]->onClick = [this, i]{ selectNextPreset(i); }; + + m_presetName[i]->initalize(static_cast<uint8_t>(i)); + + const auto partVolume = _editor.getController().getParameterIndexByName(Virus::g_paramPartVolume); + const auto partPanorama = _editor.getController().getParameterIndexByName(Virus::g_paramPartPanorama); + + _editor.getParameterBinding().bind(*m_partVolume[i], partVolume, static_cast<uint8_t>(i)); + _editor.getParameterBinding().bind(*m_partPan[i], partPanorama, static_cast<uint8_t>(i)); + + m_partVolume[i]->getProperties().set("parameter", static_cast<int>(partVolume)); + m_partPan[i]->getProperties().set("parameter", static_cast<int>(partPanorama)); + + m_partVolume[i]->getProperties().set("part", static_cast<int>(i)); + m_partPan[i]->getProperties().set("part", static_cast<int>(i)); + } + + updateAll(); + + if(!m_partActive.empty()) + { + for (const auto & partActive : m_partActive) + { + partActive->setInterceptsMouseClicks(false, false); + partActive->setVisible(false); + } + + startTimer(1000/20); + } + } + + Parts::~Parts() = default; + + void Parts::onProgramChange() const + { + updateAll(); + } + + void Parts::onPlayModeChanged() const + { + updateAll(); + } + + void Parts::onCurrentPartChanged() const + { + updateSelectedPart(); + } + + void Parts::selectPart(const size_t _part) const + { + m_editor.setPart(_part); + } + + void Parts::selectPartMidiChannel(const size_t _part) const + { + if(!m_editor.getController().isMultiMode()) + return; + + juce::PopupMenu menu; + + const auto idx= m_editor.getController().getParameterIndexByName("Part Midi Channel"); + if(idx == pluginLib::Controller::InvalidParameterIndex) + return; + + const auto v = m_editor.getController().getParameter(idx, static_cast<uint8_t>(_part)); + + for(uint8_t i=0; i<16; ++i) + { + menu.addItem("Midi Channel " + std::to_string(i + 1), true, v->getUnnormalizedValue() == i, [v, i] + { + v->setValue(v->convertTo0to1(i), pluginLib::Parameter::ChangedBy::Ui); + }); + } + + menu.showMenuAsync({}); + } + + void Parts::selectPrevPreset(size_t _part) const + { + if(m_presetPrev.size() == 1) + _part = m_editor.getController().getCurrentPart(); + + auto* pm = m_editor.getPatchManager(); + if(pm && pm->selectPrevPreset(static_cast<uint32_t>(_part))) + return; + m_editor.getController().selectPrevPreset(static_cast<uint8_t>(_part)); + } + + void Parts::selectNextPreset(size_t _part) const + { + if(m_presetNext.size() == 1) + _part = m_editor.getController().getCurrentPart(); + + auto* pm = m_editor.getPatchManager(); + if(pm && pm->selectNextPreset(static_cast<uint32_t>(_part))) + return; + m_editor.getController().selectNextPreset(static_cast<uint8_t>(_part)); + } + + void Parts::updatePresetNames() const + { + for(size_t i=0; i<m_presetName.size(); ++i) + m_presetName[i]->setButtonText(m_editor.getController().getCurrentPartPresetName(static_cast<uint8_t>(i))); + } + + void Parts::updateSelectedPart() const + { + const auto part = m_editor.getController().getCurrentPart(); + + if(part < m_partSelect.size()) + m_partSelect[part]->setToggleState(true, juce::dontSendNotification); + + for(size_t i=0; i<m_partSelect.size(); ++i) + { + if(i == part) + continue; + m_partSelect[i]->setToggleState(false, juce::dontSendNotification); + } + } + + void Parts::updateSingleOrMultiMode() const + { + const auto multiMode = m_editor.getController().isMultiMode(); + + const auto partCount = multiMode ? static_cast<AudioPluginAudioProcessor&>(m_editor.getProcessor()).getPartCount() : 1; + + for(size_t i=0; i<m_partSelect.size(); ++i) + { + const bool visible = i < partCount; + + VirusEditor::setEnabled(*m_partSelect[i], visible); + VirusEditor::setEnabled(*m_partPan[i], visible); + VirusEditor::setEnabled(*m_partVolume[i], visible); + + if(i < m_presetPrev.size()) + VirusEditor::setEnabled(*m_presetPrev[i], visible); + + if(i < m_presetNext.size()) + VirusEditor::setEnabled(*m_presetNext[i], visible); + + m_presetName[i]->setVisible(visible); + } + + const auto volumeParam = m_editor.getController().getParameterIndexByName(multiMode ? Virus::g_paramPartVolume : Virus::g_paramPatchVolume); + m_editor.getParameterBinding().bind(*m_partVolume[0], volumeParam, 0); + m_partVolume[0]->getProperties().set("parameter", static_cast<int>(volumeParam)); + } + + void Parts::timerCallback() + { + auto* device = dynamic_cast<virusLib::Device*>(m_editor.getProcessor().getPlugin().getDevice()); + + if(!device) + return; + + auto& fpState = device->getFrontpanelState(); + + const uint32_t maxPart = m_editor.getController().isMultiMode() ? 16 : 1; + + for(uint32_t i=0; i<m_partActive.size(); ++i) + { + m_partActive[i]->setVisible(i < maxPart && fpState.m_midiEventReceived[i]); + fpState.m_midiEventReceived[i] = false; + } + } + + void Parts::updateAll() const + { + updatePresetNames(); + updateSelectedPart(); + updateSingleOrMultiMode(); + } +} diff --git a/source/jucePlugin/ui3/Parts.h b/source/virusJucePlugin/Parts.h diff --git a/source/virusJucePlugin/PatchManager.cpp b/source/virusJucePlugin/PatchManager.cpp @@ -0,0 +1,438 @@ +#include "PatchManager.h" + +#include "VirusEditor.h" +#include "VirusController.h" + +#include "../jucePluginLib/patchdb/datasource.h" +#include "../jucePluginEditorLib/pluginEditor.h" + +#include "../virusLib/microcontroller.h" +#include "../virusLib/device.h" +#include "../virusLib/midiFileToRomData.h" + +#include "../synthLib/midiToSysex.h" +#include "../synthLib/os.h" + +#include "juce_cryptography/hashing/juce_MD5.h" + +namespace Virus +{ + class Controller; +} + +namespace genericVirusUI +{ + PatchManager::PatchManager(VirusEditor& _editor, juce::Component* _root, const juce::File& _dir) : jucePluginEditorLib::patchManager::PatchManager(_editor, _root, _dir), m_controller(_editor.getController()) + { + addRomPatches(); + + startLoaderThread(); + + // rom patches are received via midi, make sure we add all remaining ones, too + m_controller.onRomPatchReceived = [this](const virusLib::BankNumber _bank, const uint32_t _program) + { + if (_bank == virusLib::BankNumber::EditBuffer) + return; + + const auto index = virusLib::toArrayIndex(_bank); + + const auto& banks = m_controller.getSinglePresets(); + + if(index < banks.size()) + { + const auto& bank = banks[index]; + + if(_program == bank.size() - 1) + addDataSource(createRomDataSource(index)); + } + }; + addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomA, "Virus Model"); + addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomB, "Virus Features"); + } + + PatchManager::~PatchManager() + { + stopLoaderThread(); + m_controller.onRomPatchReceived = {}; + } + + bool PatchManager::loadRomData(pluginLib::patchDB::DataList& _results, const uint32_t _bank, const uint32_t _program) + { + const auto bankIndex = _bank; + + const auto& singles = m_controller.getSinglePresets(); + + if (bankIndex >= singles.size()) + return false; + + const auto& bank = singles[bankIndex]; + + if(_program != pluginLib::patchDB::g_invalidProgram) + { + if (_program >= bank.size()) + return false; + const auto& s = bank[_program]; + if (s.data.empty()) + return false; + _results.push_back(s.data); + } + else + { + _results.reserve(bank.size()); + for (const auto& patch : bank) + _results.push_back(patch.data); + } + return true; + } + + std::shared_ptr<pluginLib::patchDB::Patch> PatchManager::initializePatch(std::vector<uint8_t>&& _sysex) + { + if (_sysex.size() < 267) + return nullptr; + + const auto& c = static_cast<const Virus::Controller&>(m_controller); + + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::AnyPartParamValues parameterValues; + + if (!c.parseSingle(data, parameterValues, _sysex)) + return nullptr; + + const auto idxVersion = c.getParameterIndexByName("Version"); + const auto idxCategory1 = c.getParameterIndexByName("Category1"); + const auto idxCategory2 = c.getParameterIndexByName("Category2"); + const auto idxUnison = c.getParameterIndexByName("Unison Mode"); +// const auto idxTranspose = c.getParameterIndexByName("Transpose"); + const auto idxArpMode = c.getParameterIndexByName("Arp Mode"); + const auto idxPhaserMix = c.getParameterIndexByName("Phaser Mix"); + const auto idxChorusMix = c.getParameterIndexByName("Chorus Mix"); + + auto patch = std::make_shared<pluginLib::patchDB::Patch>(); + + { + const auto it = data.find(pluginLib::MidiDataType::Bank); + if (it != data.end()) + patch->bank = it->second; + } + { + const auto it = data.find(pluginLib::MidiDataType::Program); + if (it != data.end()) + patch->program = it->second; + } + + { + constexpr auto frontOffset = 9; // remove bank number, program number and other stuff that we don't need, first index is the patch version + constexpr auto backOffset = 2; // remove f7 and checksum + const juce::MD5 md5(_sysex.data() + frontOffset, _sysex.size() - frontOffset - backOffset); + + static_assert(sizeof(juce::MD5) >= sizeof(pluginLib::patchDB::PatchHash)); + memcpy(patch->hash.data(), md5.getChecksumDataArray(), std::size(patch->hash)); + } + + patch->sysex = std::move(_sysex); + + patch->name = m_controller.getSinglePresetName(parameterValues); + + const auto version = virusLib::Microcontroller::getPresetVersion(*parameterValues[idxVersion]); + const auto unison = *parameterValues[idxUnison]; +// const auto transpose = parameterValues.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idxTranspose))->second; + const auto arpMode = *parameterValues[idxArpMode]; + + const auto category1 = *parameterValues[idxCategory1]; + const auto category2 = *parameterValues[idxCategory2]; + + const auto* paramCategory1 = c.getParameter(idxCategory1, 0); + const auto* paramCategory2 = c.getParameter(idxCategory2, 0); + + auto addCategory = [&patch, version](const uint8_t _value, const pluginLib::Parameter* _param) + { + if(!_value) + return; + const auto& values = _param->getDescription().valueList; + if(_value >= values.texts.size()) + return; + + // Virus < TI had less categories + if(version < virusLib::D && _value > 16) + return; + + const auto t = _param->getDescription().valueList.valueToText(_value); + patch->tags.add(pluginLib::patchDB::TagType::Category, t); + }; + + addCategory(category1, paramCategory1); + addCategory(category2, paramCategory2); + + switch (version) + { + case virusLib::A: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "A"); break; + case virusLib::B: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "B"); break; + case virusLib::C: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "C"); break; + case virusLib::D: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "TI"); break; + case virusLib::D2: patch->tags.add(pluginLib::patchDB::TagType::CustomA, "TI2"); break; + } + + if(arpMode) + patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Arp"); + if(unison) + patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Unison"); + if(*parameterValues[idxPhaserMix] > 0) + patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Phaser"); + if(*parameterValues[idxChorusMix] > 0) + patch->tags.add(pluginLib::patchDB::TagType::CustomB, "Chorus"); + return patch; + } + + pluginLib::patchDB::Data PatchManager::prepareSave(const pluginLib::patchDB::PatchPtr& _patch) const + { + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::AnyPartParamValues parameterValues; + + Virus::Controller::MidiPacketType usedPacketType; + if (!m_controller.parseSingle(data, parameterValues, _patch->sysex, usedPacketType)) + return _patch->sysex; + + // apply name + if (!_patch->getName().empty()) + m_controller.setSinglePresetName(parameterValues, _patch->getName()); + + // apply program + auto bank = toMidiByte(virusLib::BankNumber::A); + auto program = data[pluginLib::MidiDataType::Program]; + + if (_patch->program != pluginLib::patchDB::g_invalidProgram) + { + const auto bankOffset = _patch->program / 128; + program = static_cast<uint8_t>(_patch->program - bankOffset * 128); + bank += static_cast<uint8_t>(bankOffset); + } + + // apply categories + const uint32_t indicesCategory[] = { + m_controller.getParameterIndexByName("Category1"), + m_controller.getParameterIndexByName("Category2") + }; + + const pluginLib::Parameter* paramsCategory[] = { + m_controller.getParameter(indicesCategory[0], 0), + m_controller.getParameter(indicesCategory[1], 0) + }; + + uint8_t val0 = 0; + uint8_t val1 = 0; + + const auto& tags = _patch->getTags(pluginLib::patchDB::TagType::Category); + + size_t i = 0; + for (const auto& tag : tags.getAdded()) + { + const auto categoryValue = paramsCategory[i]->getDescription().valueList.textToValue(tag); + if(categoryValue != 0) + { + auto& v = i ? val1 : val0; + v = static_cast<uint8_t>(categoryValue); + ++i; + if (i == 2) + break; + } + } + + parameterValues[indicesCategory[0]] = val0; + parameterValues[indicesCategory[1]] = val1; + + return m_controller.createSingleDump(usedPacketType, bank, program, parameterValues); + } + + bool PatchManager::parseFileData(pluginLib::patchDB::DataList& _results, const pluginLib::patchDB::Data& _data) + { + { + std::vector<synthLib::SMidiEvent> events; + virusLib::Device::parseTIcontrolPreset(events, _data); + + for (const auto& e : events) + { + if (!e.sysex.empty()) + _results.push_back(e.sysex); + } + + if (!_results.empty()) + return true; + } + + if (virusLib::Device::parsePowercorePreset(_results, _data)) + return true; + + if(!synthLib::MidiToSysex::extractSysexFromData(_results, _data)) + return false; + + if(!_results.empty()) + { + if(_data.size() > 500000) + { + virusLib::MidiFileToRomData romLoader; + + for (const auto& result : _results) + { + if(!romLoader.add(result)) + break; + } + if(romLoader.isComplete()) + { + const auto& data = romLoader.getData(); + + if(data.size() > 0x10000) + { + // presets are written to ROM address 0x50000, the second half of an OS update is therefore at 0x10000 + constexpr ptrdiff_t startAddr = 0x10000; + ptrdiff_t addr = startAddr; + uint32_t index = 0; + + while(addr + 0x100 <= static_cast<ptrdiff_t>(data.size())) + { + std::vector chunk(data.begin() + addr, data.begin() + addr + 0x100); + + // validate +// const auto idxH = chunk[2]; + const auto idxL = chunk[3]; + + if(/*idxH != (index >> 7) || */idxL != (index & 0x7f)) + break; + + bool validName = true; + for(size_t i=240; i<240+10; ++i) + { + if(chunk[i] < 32 || chunk[i] > 128) + { + validName = false; + break; + } + } + + if(!validName) + continue; + + addr += 0x100; + ++index; + } + + if(index > 0) + { + _results.clear(); + + for(uint32_t i=0; i<index; ++i) + { + // pack into sysex + std::vector<uint8_t>& sysex = _results.emplace_back(std::vector<uint8_t> + {0xf0, 0x00, 0x20, 0x33, 0x01, virusLib::OMNI_DEVICE_ID, 0x10, static_cast<uint8_t>(0x01 + (i >> 7)), static_cast<uint8_t>(i & 0x7f)} + ); + sysex.insert(sysex.end(), data.begin() + i * 0x100 + startAddr, data.begin() + i * 0x100 + 0x100 + startAddr); + sysex.push_back(virusLib::Microcontroller::calcChecksum(sysex, 5)); + sysex.push_back(0xf7); + } + } + } + } + } + + } + + return !_results.empty(); + } + + bool PatchManager::requestPatchForPart(pluginLib::patchDB::Data& _data, const uint32_t _part) + { + _data = m_controller.createSingleDump(static_cast<uint8_t>(_part), toMidiByte(virusLib::BankNumber::A), 0); + return !_data.empty(); + } + + uint32_t PatchManager::getCurrentPart() const + { + return m_controller.getCurrentPart(); + } + + bool PatchManager::equals(const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) const + { + pluginLib::MidiPacket::Data dataA, dataB; + pluginLib::MidiPacket::AnyPartParamValues parameterValuesA, parameterValuesB; + + if (!m_controller.parseSingle(dataA, parameterValuesA, _a->sysex) || !m_controller.parseSingle(dataB, parameterValuesB, _b->sysex)) + return false; + + if(parameterValuesA.size() != parameterValuesB.size()) + return false; + + for(uint32_t i=0; i<parameterValuesA.size(); ++i) + { + const auto& itA = parameterValuesA[i]; + const auto& itB = parameterValuesB[i]; + + if(!itA) + { + if(itB) + return false; + continue; + } + + if(!itB) + return false; + + auto vA = *itA; + auto vB = *itB; + + if(vA != vB) + { + // parameters might be out of range because some dumps have values that are out of range indeed, clamp to valid range and compare again + const auto* param = m_controller.getParameter(i); + if(!param) + return false; + + if(param->getDescription().isNonPartSensitive()) + continue; + + vA = static_cast<uint8_t>(param->getDescription().range.clipValue(vA)); + vB = static_cast<uint8_t>(param->getDescription().range.clipValue(vB)); + + if(vA != vB) + return false; + } + } + return true; + } + + bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch) + { + return m_controller.activatePatch(_patch->sysex); + } + + bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _part) + { + return m_controller.activatePatch(_patch->sysex, _part); + } + + void PatchManager::addRomPatches() + { + const auto& singles = m_controller.getSinglePresets(); + + for (uint32_t b = 0; b < singles.size(); ++b) + { + const auto& bank = singles[b]; + + const auto& single = bank[bank.size()-1]; + + if (single.data.empty()) + continue; + + addDataSource(createRomDataSource(b)); + } + } + + pluginLib::patchDB::DataSource PatchManager::createRomDataSource(const uint32_t _bank) const + { + pluginLib::patchDB::DataSource ds; + ds.type = pluginLib::patchDB::SourceType::Rom; + ds.bank = _bank; + ds.name = m_controller.getBankName(_bank); + return ds; + } + +} diff --git a/source/jucePlugin/ui3/PatchManager.h b/source/virusJucePlugin/PatchManager.h diff --git a/source/virusJucePlugin/PluginEditorState.cpp b/source/virusJucePlugin/PluginEditorState.cpp @@ -0,0 +1,106 @@ +#include "PluginEditorState.h" + +#include "PluginProcessor.h" + +#include "VirusEditor.h" + +#include "../synthLib/os.h" + +PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller, const std::vector<PluginEditorState::Skin>& _includedSkins) + : jucePluginEditorLib::PluginEditorState(_processor, _controller, _includedSkins) +{ + loadDefaultSkin(); +} + +genericUI::Editor* PluginEditorState::createEditor(const Skin& _skin, std::function<void()> _openMenuCallback) +{ + return new genericVirusUI::VirusEditor(m_parameterBinding, static_cast<AudioPluginAudioProcessor&>(m_processor), _skin.jsonFilename, _skin.folder, _openMenuCallback); +} + +void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) +{ + jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); + auto& p = m_processor; + + { + juce::PopupMenu gainMenu; + + const auto gain = m_processor.getOutputGain(); + + gainMenu.addItem("-12 db", true, gain == 0.25f, [&p] { p.setOutputGain(0.25f); }); + gainMenu.addItem("-6 db", true, gain == 0.5f, [&p] { p.setOutputGain(0.5f); }); + gainMenu.addItem("0 db (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); + gainMenu.addItem("+6 db", true, gain == 2, [&p] { p.setOutputGain(2); }); + gainMenu.addItem("+12 db", true, gain == 4, [&p] { p.setOutputGain(4); }); + + _menu.addSubMenu("Output Gain", gainMenu); + } +} + +bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) +{ + jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); + + const auto percent = m_processor.getDspClockPercent(); + const auto hz = m_processor.getDspClockHz(); + + juce::PopupMenu clockMenu; + + auto makeEntry = [&](const int _percent) + { + const auto mhz = hz * _percent / 100 / 1000000; + std::stringstream ss; + ss << _percent << "% (" << mhz << " MHz)"; + if(_percent == 100) + ss << " (Default)"; + clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); + }; + + makeEntry(50); + makeEntry(75); + makeEntry(100); + makeEntry(125); + makeEntry(150); + makeEntry(200); + + _menu.addSubMenu("DSP Clock", clockMenu); + + const auto samplerates = m_processor.getDeviceSupportedSamplerates(); + + if(samplerates.size() > 1) + { + juce::PopupMenu srMenu; + + const auto current = m_processor.getPreferredDeviceSamplerate(); + + const auto preferred = m_processor.getDevicePreferredSamplerates(); + + srMenu.addItem("Automatic (Match with host)", true, current == 0.0f, [this] { m_processor.setPreferredDeviceSamplerate(0.0f); }); + srMenu.addSeparator(); + srMenu.addSectionHeader("Official, used automatically"); + + auto addSRs = [&](bool _usePreferred) + { + for (const float samplerate : samplerates) + { + const auto isPreferred = std::find(preferred.begin(), preferred.end(), samplerate) != preferred.end(); + + if(isPreferred != _usePreferred) + continue; + + const auto title = std::to_string(static_cast<int>(std::floor(samplerate + 0.5f))) + " Hz"; + + srMenu.addItem(title, _enabled, std::fabs(samplerate - current) < 1.0f, [this, samplerate] { m_processor.setPreferredDeviceSamplerate(samplerate); }); + } + }; + + addSRs(true); + srMenu.addSeparator(); + srMenu.addSectionHeader("Undocumented, use with care"); + addSRs(false); + + _menu.addSubMenu("Device Samplerate", srMenu); + } + + return true; +} diff --git a/source/virusJucePlugin/PluginEditorState.h b/source/virusJucePlugin/PluginEditorState.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../jucePluginEditorLib/pluginEditorState.h" + +class AudioPluginAudioProcessor; + +class PluginEditorState : public jucePluginEditorLib::PluginEditorState +{ +public: + explicit PluginEditorState(AudioPluginAudioProcessor& _processor, pluginLib::Controller& _controller, const std::vector<PluginEditorState::Skin>& _includedSkins); + + genericUI::Editor* createEditor(const Skin& _skin, std::function<void()> _openMenuCallback) override; + + void initContextMenu(juce::PopupMenu& _menu) override; + bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; +}; diff --git a/source/virusJucePlugin/PluginProcessor.cpp b/source/virusJucePlugin/PluginProcessor.cpp @@ -0,0 +1,121 @@ +#include "PluginProcessor.h" +#include "PluginEditorState.h" +#include "ParameterNames.h" + +#include "../virusLib/romloader.h" + +#include "../synthLib/deviceException.h" +#include "../synthLib/binarystream.h" +#include "../synthLib/os.h" + +//============================================================================== +AudioPluginAudioProcessor::AudioPluginAudioProcessor(const BusesProperties& _busesProperties, const juce::PropertiesFile::Options& _configOptions, const pluginLib::Processor::Properties& _properties, const std::vector<virusLib::ROMFile>& _roms) + : jucePluginEditorLib::Processor(_busesProperties, _configOptions, _properties) + , m_roms(_roms) +{ + evRomChanged.retain(getSelectedRom()); + + m_clockTempoParam = getController().getParameterIndexByName(Virus::g_paramClockTempo); + + const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); + Processor::setLatencyBlocks(latencyBlocks); +} + +AudioPluginAudioProcessor::~AudioPluginAudioProcessor() +{ + destroyEditorState(); +} + +//============================================================================== + +void AudioPluginAudioProcessor::processBpm(const float _bpm) +{ + // clamp to virus range, 63-190 + const auto bpmValue = juce::jmin(127, juce::jmax(0, static_cast<int>(_bpm)-63)); + const auto clockParam = getController().getParameter(m_clockTempoParam, 0); + + if (clockParam == nullptr || static_cast<int>(clockParam->getValueObject().getValue()) == bpmValue) + return; + + clockParam->getValueObject().setValue(bpmValue); +} + +bool AudioPluginAudioProcessor::setSelectedRom(const uint32_t _index) +{ + if(_index >= m_roms.size()) + return false; + if(_index == m_selectedRom) + return true; + m_selectedRom = _index; + + try + { + synthLib::Device* device = createDevice(); + getPlugin().setDevice(device); + (void)m_device.release(); + m_device.reset(device); + + evRomChanged.retain(getSelectedRom()); + + return true; + } + catch(const synthLib::DeviceException& e) + { + juce::NativeMessageBox::showMessageBox(juce::MessageBoxIconType::WarningIcon, + "Device creation failed:", + std::string("Failed to create device:\n\n") + + e.what() + "\n\n" + "Will continue using old ROM"); + return false; + } +} + +synthLib::Device* AudioPluginAudioProcessor::createDevice() +{ + const auto* rom = getSelectedRom(); + return new virusLib::Device(rom ? *rom : virusLib::ROMFile::invalid(), getPreferredDeviceSamplerate(), getHostSamplerate(), true); +} + +pluginLib::Controller* AudioPluginAudioProcessor::createController() +{ + // force creation of device as the controller decides how to initialize based on the used ROM + getPlugin(); + + return new Virus::Controller(*this); +} + +void AudioPluginAudioProcessor::saveChunkData(synthLib::BinaryStream& s) +{ + auto* rom = getSelectedRom(); + if(rom) + { + synthLib::ChunkWriter cw(s, "ROM ", 2); + const auto romName = synthLib::getFilenameWithoutPath(rom->getFilename()); + s.write<uint8_t>(static_cast<uint8_t>(rom->getModel())); + s.write(romName); + } + Processor::saveChunkData(s); +} + +void AudioPluginAudioProcessor::loadChunkData(synthLib::ChunkReader& _cr) +{ + _cr.add("ROM ", 2, [this](synthLib::BinaryStream& _binaryStream, unsigned _version) + { + auto model = virusLib::DeviceModel::ABC; + + if(_version > 1) + model = static_cast<virusLib::DeviceModel>(_binaryStream.read<uint8_t>()); + + const auto romName = _binaryStream.readString(); + + const auto& roms = getRoms(); + for(uint32_t i=0; i<static_cast<uint32_t>(roms.size()); ++i) + { + const auto& rom = roms[i]; + if(rom.getModel() == model && synthLib::getFilenameWithoutPath(rom.getFilename()) == romName) + setSelectedRom(i); + } + }); + + Processor::loadChunkData(_cr); +} diff --git a/source/virusJucePlugin/PluginProcessor.h b/source/virusJucePlugin/PluginProcessor.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../synthLib/plugin.h" +#include "../virusLib/device.h" + +#include "../jucePluginLib/event.h" + +#include "VirusController.h" + +#include "../jucePluginEditorLib/pluginProcessor.h" + +//============================================================================== +class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor +{ +public: + AudioPluginAudioProcessor(const BusesProperties& _busesProperties, const juce::PropertiesFile::Options& _configOptions, const pluginLib::Processor::Properties& _properties, const std::vector<virusLib::ROMFile>& _roms); + ~AudioPluginAudioProcessor() override; + + void processBpm(float _bpm) override; + + // _____________ + // + + std::string getRomName() const + { + const auto* rom = getSelectedRom(); + if(!rom) + return "<invalid>"; + return juce::File(juce::String(rom->getFilename())).getFileNameWithoutExtension().toStdString(); + } + + const virusLib::ROMFile* getSelectedRom() const + { + if(m_selectedRom >= m_roms.size()) + return {}; + return &m_roms[m_selectedRom]; + } + + virusLib::DeviceModel getModel() const + { + auto* rom = getSelectedRom(); + return rom ? rom->getModel() : virusLib::DeviceModel::Invalid; + } + + const auto& getRoms() const { return m_roms; } + + bool setSelectedRom(uint32_t _index); + uint32_t getSelectedRomIndex() const { return m_selectedRom; } + + uint32_t getPartCount() const + { + return getModel() == virusLib::DeviceModel::Snow ? 4 : 16; + } + + virtual const char* findEmbeddedResource(const char* _name, uint32_t& _size) const = 0; + + // _____________ + // +private: + synthLib::Device* createDevice() override; + + pluginLib::Controller* createController() override; + + void saveChunkData(synthLib::BinaryStream& s) override; + void loadChunkData(synthLib::ChunkReader& _cr) override; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) + + std::vector<virusLib::ROMFile> m_roms; + uint32_t m_selectedRom = 0; + + uint32_t m_clockTempoParam = 0xffffffff; + +public: + pluginLib::Event<const virusLib::ROMFile*> evRomChanged; +}; diff --git a/source/jucePlugin/ui3/Tabs.cpp b/source/virusJucePlugin/Tabs.cpp diff --git a/source/jucePlugin/ui3/Tabs.h b/source/virusJucePlugin/Tabs.h diff --git a/source/virusJucePlugin/VirusController.cpp b/source/virusJucePlugin/VirusController.cpp @@ -0,0 +1,833 @@ +#include "VirusController.h" + +#include <fstream> + +#include "ParameterNames.h" +#include "PluginProcessor.h" + +#include "../virusLib/microcontrollerTypes.h" +#include "../synthLib/os.h" + +#include "VirusEditor.h" + +using MessageType = virusLib::SysexMessageType; + +namespace Virus +{ + constexpr const char* g_midiPacketNames[] = + { + "requestsingle", + "requestmulti", + "requestsinglebank", + "requestmultibank", + "requestarrangement", + "requestglobal", + "requesttotal", + "requestcontrollerdump", + "parameterchange", + "singledump", + "multidump", + "singledump_C", + }; + + static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count)); + + const char* midiPacketName(Controller::MidiPacketType _type) + { + return g_midiPacketNames[static_cast<uint32_t>(_type)]; + } + + Controller::Controller(AudioPluginAudioProcessor &p, unsigned char deviceId) : pluginLib::Controller(p, loadParameterDescriptions(p)), m_processor(p), m_deviceId(deviceId) + { + switch(p.getModel()) + { + default: + case virusLib::DeviceModel::A: + case virusLib::DeviceModel::B: + case virusLib::DeviceModel::C: m_singles.resize(8); break; + case virusLib::DeviceModel::Snow: + case virusLib::DeviceModel::TI: + case virusLib::DeviceModel::TI2: + m_singles.resize( + virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI) + + virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI2) + + virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::Snow) + + 2 + ); break; + } + + registerParams(p); + + // add lambda to enforce updating patches when virus switch from/to multi/single. + const auto paramIdx = getParameterIndexByName(g_paramPlayMode); + auto* parameter = getParameter(paramIdx); + if(parameter) + { + parameter->onValueChanged.emplace_back(std::make_pair(0, [this] { + const uint8_t prg = isMultiMode() ? 0x0 : virusLib::SINGLE; + requestSingle(0, prg); + requestMulti(0, prg); + + if (onMsgDone) + { + onMsgDone(); + } + })); + } + requestTotal(); + requestArrangement(); + + for(uint8_t i=3; i<=getBankCount(); ++i) + requestSingleBank(i); + + startTimer(5); + } + + Controller::~Controller() + { + stopTimer(); + } + + void Controller::parseSysexMessage(const pluginLib::SysEx& _msg) + { + std::string name; + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; + + if(parseMidiPacket(name, data, parameterValues, _msg)) + { + const auto deviceId = data[pluginLib::MidiDataType::DeviceId]; + + if(deviceId != m_deviceId && deviceId != virusLib::OMNI_DEVICE_ID) + return; // not intended to this device! + + if(name == midiPacketName(MidiPacketType::SingleDump) || name == midiPacketName(MidiPacketType::SingleDump_C)) + parseSingle(_msg, data, parameterValues); + else if(name == midiPacketName(MidiPacketType::MultiDump)) + parseMulti(_msg, data, parameterValues); + else if(name == midiPacketName(MidiPacketType::ParameterChange)) + parseParamChange(data); + else + { + LOG("Controller: Begin unhandled SysEx! --"); + printMessage(_msg); + LOG("Controller: End unhandled SysEx! --"); + } + return; + } + + LOG("Controller: Begin unknown SysEx! --"); + printMessage(_msg); + LOG("Controller: End unknown SysEx! --"); + } + + juce::Value* Controller::getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex) + { + const auto& params = findSynthParam(ch, static_cast<uint8_t>(virusLib::PAGE_A + bank), paramIndex); + if (params.empty()) + { + // unregistered param? + return nullptr; + } + return &params.front()->getValueObject(); + } + + void Controller::parseParamChange(const pluginLib::MidiPacket::Data& _data) + { + const auto page = _data.find(pluginLib::MidiDataType::Page)->second; + const auto part = _data.find(pluginLib::MidiDataType::Part)->second; + const auto index = _data.find(pluginLib::MidiDataType::ParameterIndex)->second; + const auto value = _data.find(pluginLib::MidiDataType::ParameterValue)->second; + + const auto& partParams = findSynthParam(part, page, index); + + if (partParams.empty() && part != 0 && part != virusLib::SINGLE) + { + // ensure it's not global + const auto& globalParams = findSynthParam(0, page, index); + if (globalParams.empty()) + { + jassertfalse; + return; + } + for (const auto& param : globalParams) + { + if (!param->getDescription().isNonPartSensitive()) + { + jassertfalse; + return; + } + } + for (const auto& param : globalParams) + param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::ControlChange); + } + for (const auto& param : partParams) + param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::ControlChange); + // TODO: + /** + If a + global parameter or a Multi parameter is ac- + cessed, which is not part-sensitive (e.g. Input + Boost or Multi Delay Time), the part number is + ignored + */ + } + + juce::StringArray Controller::getSinglePresetNames(virusLib::BankNumber _bank) const + { + if (_bank == virusLib::BankNumber::EditBuffer) + { + jassertfalse; + return {}; + } + + const auto bank = virusLib::toArrayIndex(_bank); + + if (bank >= m_singles.size() || bank < 0) + { + jassertfalse; + return {}; + } + + juce::StringArray bankNames; + for (auto i = 0; i < 128; i++) + bankNames.add(m_singles[bank][i].name); + return bankNames; + } + + std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const + { + return getPresetName("SingleName", _values); + } + + std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const + { + return getPresetName("SingleName", _values); + } + + std::string Controller::getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const + { + return getPresetName("MultiName", _values); + } + + std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const + { + std::string name; + for(uint32_t i=0; i<kNameLength; ++i) + { + const std::string paramName = _paramNamePrefix + std::to_string(i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); + if(it == _values.end()) + break; + + name += static_cast<char>(it->second); + } + return name; + } + + std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::AnyPartParamValues& _values) const + { + std::string name; + for(uint32_t i=0; i<kNameLength; ++i) + { + const std::string paramName = _paramNamePrefix + std::to_string(i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + const auto it = _values[idx]; + if(!it) + break; + + name += static_cast<char>(*it); + } + return name; + } + + void Controller::setSinglePresetName(const uint8_t _part, const juce::String& _name) const + { + for (int i=0; i<kNameLength; i++) + { + const std::string paramName = "SingleName" + std::to_string(i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + auto* param = getParameter(idx, _part); + if(!param) + break; + auto& v = param->getValueObject(); + if(i >= _name.length()) + v.setValue(static_cast<uint8_t>(' ')); + else + v.setValue(static_cast<uint8_t>(_name[i])); + } + } + + void Controller::setSinglePresetName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _name) const + { + for(uint32_t i=0; i<kNameLength; ++i) + { + const std::string paramName = "SingleName" + std::to_string(i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + _values[idx] = (i < _name.size()) ? _name[i] : ' '; + } + } + + bool Controller::isMultiMode() const + { + const auto paramIdx = getParameterIndexByName(g_paramPlayMode); + const auto& value = getParameter(paramIdx)->getValueObject(); + return value.getValue(); + } + + juce::String Controller::getCurrentPartPresetName(const uint8_t _part) const + { + std::string name; + for (int i=0; i<kNameLength; i++) + { + const std::string paramName = "SingleName" + std::to_string(i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + auto* param = getParameter(idx, _part); + if(!param) + break; + const int v = param->getValueObject().getValue(); + name += static_cast<char>(v); + } + return name; + } + + void Controller::setCurrentPartPreset(uint8_t _part, const virusLib::BankNumber _bank, uint8_t _prg) + { + if(_bank == virusLib::BankNumber::EditBuffer || _prg > m_singles[0].size()) + { + jassertfalse; + return; + } + + const auto bank = virusLib::toArrayIndex(_bank); + + if (bank >= m_singles.size()) + { + jassertfalse; + return; + } + + const uint8_t pt = isMultiMode() ? _part : virusLib::SINGLE; + + sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_BANK_SELECT, virusLib::toMidiByte(_bank)); + sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_PROGRAM_CHANGE, _prg); + + requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), pt); + + m_currentBank[_part] = _bank; + m_currentProgram[_part] = _prg; + m_currentPresetSource[_part] = PresetSource::Rom; + } + + void Controller::setCurrentPartPresetSource(uint8_t _part, PresetSource _source) + { + m_currentPresetSource[_part] = _source; + } + + virusLib::BankNumber Controller::getCurrentPartBank(const uint8_t _part) const + { + return m_currentBank[_part]; + } + + uint8_t Controller::getCurrentPartProgram(const uint8_t _part) const + { + return m_currentProgram[_part]; + } + + Controller::PresetSource Controller::getCurrentPartPresetSource(uint8_t _part) const + { + return m_currentPresetSource[_part]; + } + + bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg) const + { + MidiPacketType unused; + return parseSingle(_data, _parameterValues, _msg, unused); + } + + bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg, MidiPacketType& usedPacketType) const + { + const auto packetName = midiPacketName(MidiPacketType::SingleDump); + + auto* m = getMidiPacket(packetName); + + if(!m) + return false; + + usedPacketType = MidiPacketType::SingleDump; + + if(_msg.size() > m->size()) + { + pluginLib::SysEx temp; + temp.insert(temp.begin(), _msg.begin(), _msg.begin() + (m->size()-1)); + temp.push_back(0xf7); + return parseMidiPacket(*m, _data, _parameterValues, temp); + } + + if(_msg.size() < m->size()) + { + const auto* mc = getMidiPacket(midiPacketName(MidiPacketType::SingleDump_C)); + if(!mc) + return false; + usedPacketType = MidiPacketType::SingleDump_C; + return parseMidiPacket(*mc, _data, _parameterValues, _msg); + } + + return parseMidiPacket(*m, _data, _parameterValues, _msg); + } + + std::string Controller::loadParameterDescriptions(const AudioPluginAudioProcessor& _processor) + { + const auto model = _processor.getModel(); + const auto name = model == virusLib::DeviceModel::Invalid || isTIFamily(model) ? "parameterDescriptions_TI.json" : "parameterDescriptions_C.json"; + const auto path = synthLib::getModulePath() + name; + + const std::ifstream f(path.c_str(), std::ios::in); + if(f.is_open()) + { + std::stringstream buf; + buf << f.rdbuf(); + return buf.str(); + } + + uint32_t size; + const auto res = _processor.findEmbeddedResource(name, size); + if(res) + return {res, size}; + return {}; + } + + void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) + { + SinglePatch patch; + + patch.bankNumber = virusLib::fromMidiByte(_data.find(pluginLib::MidiDataType::Bank)->second); + patch.progNumber = _data.find(pluginLib::MidiDataType::Program)->second; + + patch.name = getSinglePresetName(_parameterValues); + + patch.data = _msg; + + if (patch.bankNumber == virusLib::BankNumber::EditBuffer) + { + if(patch.progNumber == virusLib::SINGLE) + m_singleEditBuffer = patch; + else + m_singleEditBuffers[patch.progNumber] = patch; + + // virus sends also the single buffer not matter what's the mode. (?? no, both is requested, so both is sent) + // instead of keeping both, we 'encapsulate' this into first channel. + // the logic to maintain this is done by listening the global single/multi param. + if (isMultiMode() && patch.progNumber == virusLib::SINGLE) + return; + if (!isMultiMode() && patch.progNumber == 0x0) + return; + + const uint8_t ch = patch.progNumber == virusLib::SINGLE ? 0 : patch.progNumber; + + const auto locked = getLockedParameterNames(); + + for(auto it = _parameterValues.begin(); it != _parameterValues.end(); ++it) + { + auto* p = getParameter(it->first.second, ch); + + if(locked.find(p->getDescription().name) == locked.end()) + p->setValueFromSynth(it->second, false, pluginLib::Parameter::ChangedBy::PresetChange); + } + + m_processor.updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); + + if(m_currentPresetSource[ch] != PresetSource::Browser) + { + bool found = false; + for(size_t b=0; b<m_singles.size() && !found; ++b) + { + const auto& singlePatches = m_singles[b]; + + for(size_t s=0; s<singlePatches.size(); ++s) + { + const auto& singlePatch = singlePatches[s]; + + if(singlePatch.name == patch.name) + { + m_currentBank[ch] = virusLib::fromArrayIndex(static_cast<uint8_t>(b)); + m_currentProgram[ch] = static_cast<uint8_t>(s); + m_currentPresetSource[ch] = PresetSource::Rom; + found = true; + break; + } + } + } + + if(!found) + { + m_currentProgram[ch] = 0; + m_currentBank[ch] = virusLib::BankNumber::EditBuffer; + m_currentPresetSource[ch] = PresetSource::Unknown; + } + } + else + { + m_currentProgram[ch] = 0; + m_currentBank[ch] = virusLib::BankNumber::EditBuffer; + } + + if (onProgramChange) + onProgramChange(patch.progNumber); + } + else + { + const auto bank = toArrayIndex(patch.bankNumber); + const auto program = patch.progNumber; + + m_singles[bank][program] = patch; + + if(onRomPatchReceived) + onRomPatchReceived(patch.bankNumber, program); + } + } + + void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) + { + const auto bankNumber = _data.find(pluginLib::MidiDataType::Bank)->second; + + /* If it's a multi edit buffer, set the part page C parameters to their multi equivalents */ + if (bankNumber == 0) + { + m_multiEditBuffer.progNumber = _data.find(pluginLib::MidiDataType::Program)->second; + m_multiEditBuffer.name = getMultiPresetName(_parameterValues); + m_multiEditBuffer.data = _msg; + + for (const auto & paramValue : _parameterValues) + { + const auto part = paramValue.first.first; + const auto index = paramValue.first.second; + const auto value = paramValue.second; + + auto* param = getParameter(index, part); + if(!param) + continue; + + const auto& desc = param->getDescription(); + + if(desc.page != virusLib::PAGE_C) + continue; + + param->setValueFromSynth(value, false, pluginLib::Parameter::ChangedBy::PresetChange); + } + + m_processor.updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); + } + } + + void Controller::parseControllerDump(const synthLib::SMidiEvent& m) + { + const uint8_t status = m.a & 0xf0; + const uint8_t part = m.a & 0x0f; + + uint8_t page; + + if (status == synthLib::M_CONTROLCHANGE) + page = virusLib::PAGE_A; + else if (status == synthLib::M_POLYPRESSURE) + page = virusLib::PAGE_B; + else + return; + + const auto& params = findSynthParam(part, page, m.b); + for (const auto & p : params) + p->setValueFromSynth(m.c, true, pluginLib::Parameter::ChangedBy::ControlChange); + } + + void Controller::printMessage(const pluginLib::SysEx &msg) + { + std::stringstream ss; + ss << "[size " << msg.size() << "] "; + for(size_t i=0; i<msg.size(); ++i) + { + ss << HEXN(static_cast<int>(msg[i]), 2); + if(i < msg.size()-1) + ss << ','; + } + const auto s(ss.str()); + LOG(s); + } + + void Controller::onStateLoaded() + { + requestTotal(); + requestArrangement(); + } + + bool Controller::requestProgram(uint8_t _bank, uint8_t _program, bool _multi) const + { + std::map<pluginLib::MidiDataType, uint8_t> data; + + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); + + return sendSysEx(_multi ? MidiPacketType::RequestMulti : MidiPacketType::RequestSingle, data); + } + + bool Controller::requestSingle(uint8_t _bank, uint8_t _program) const + { + return requestProgram(_bank, _program, false); + } + + bool Controller::requestMulti(uint8_t _bank, uint8_t _program) const + { + return requestProgram(_bank, _program, true); + } + + bool Controller::requestSingleBank(uint8_t _bank) const + { + std::map<pluginLib::MidiDataType, uint8_t> data; + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); + + return sendSysEx(MidiPacketType::RequestSingleBank, data); + } + + bool Controller::requestTotal() const + { + return sendSysEx(MidiPacketType::RequestTotal); + } + + bool Controller::requestArrangement() const + { + return sendSysEx(MidiPacketType::RequestArrangement); + } + + bool Controller::sendSysEx(MidiPacketType _type) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + return sendSysEx(_type, params); + } + + bool Controller::sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const + { + _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); + } + + void Controller::timerCallback() + { + std::vector<synthLib::SMidiEvent> virusOut; + getPluginMidiOut(virusOut); + + for (const auto& msg : virusOut) + { + if (msg.sysex.empty()) + { + // no sysex + parseControllerDump(msg); + } + else + { + parseSysexMessage(msg.sysex); + } + } + } + + void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) + { + const auto& desc = _parameter.getDescription(); + + sendParameterChange(desc.page, _parameter.getPart(), desc.index, _value); + } + + bool Controller::sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const + { + std::map<pluginLib::MidiDataType, uint8_t> data; + + data.insert(std::make_pair(pluginLib::MidiDataType::Page, _page)); + data.insert(std::make_pair(pluginLib::MidiDataType::Part, _part)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, _index)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); + + return sendSysEx(MidiPacketType::ParameterChange, data); + } + + std::vector<uint8_t> Controller::createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program) + { + pluginLib::MidiPacket::Data data; + + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); + + std::vector<uint8_t> dst; + + if(!createMidiDataFromPacket(dst, midiPacketName(MidiPacketType::SingleDump), data, _part)) + return {}; + + return dst; + } + + std::vector<uint8_t> Controller::createSingleDump(MidiPacketType _packet, uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::AnyPartParamValues& _paramValues) + { + const auto* m = getMidiPacket(midiPacketName(_packet)); + assert(m && "midi packet not found"); + + if(!m) + return {}; + + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::NamedParamValues paramValues; + + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); + + if(!createNamedParamValues(paramValues, _paramValues)) + return {}; + + pluginLib::MidiPacket::Sysex dst; + if(!m->create(dst, data, paramValues)) + return {}; + return dst; + } + + std::vector<uint8_t> Controller::modifySingleDump(const std::vector<uint8_t>& _sysex, const virusLib::BankNumber _newBank, const uint8_t _newProgram) const + { + auto* m = getMidiPacket(midiPacketName(MidiPacketType::SingleDump)); + assert(m); + + const auto idxBank = m->getByteIndexForType(pluginLib::MidiDataType::Bank); + const auto idxProgram = m->getByteIndexForType(pluginLib::MidiDataType::Program); + + assert(idxBank != pluginLib::MidiPacket::InvalidIndex); + assert(idxProgram != pluginLib::MidiPacket::InvalidIndex); + + auto data = _sysex; + + data[idxBank] = toMidiByte(_newBank); + data[idxProgram] = _newProgram; + + return data; + } + + void Controller::selectPrevPreset(const uint8_t _part) + { + if(getCurrentPartProgram(_part) > 0) + { + setCurrentPartPreset(_part, getCurrentPartBank(_part), getCurrentPartProgram(_part) - 1); + } + } + + void Controller::selectNextPreset(uint8_t _part) + { + if(getCurrentPartProgram(_part) < m_singles[0].size()) + { + setCurrentPartPreset(_part, getCurrentPartBank(_part), getCurrentPartProgram(_part) + 1); + } + } + + std::string Controller::getBankName(uint32_t _index) const + { + char temp[32]{0}; + + if(getBankCount() <= 26) + { + snprintf(temp, sizeof(temp), "Bank %c", 'A' + _index); + } + else if(_index < 2) + { + snprintf(temp, sizeof(temp), "RAM Bank %c", 'A' + _index); + } + else + { + _index -= 2; + + const auto countSnow = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::Snow); + const auto countTI = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI); + const auto countTI2 = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI2); + + switch(m_processor.getModel()) + { + case virusLib::DeviceModel::Snow: + if(_index < countSnow) sprintf(temp, "Snow Rom %c", 'A' + _index); + else if(_index < countTI + countSnow) sprintf(temp, "TI Rom %c", 'A' + (_index - countSnow)); + else sprintf(temp, "TI2 Rom %c", 'A' + (_index - countTI - countSnow)); + break; + case virusLib::DeviceModel::TI: + if(_index < countTI) sprintf(temp, "TI Rom %c", 'A' + _index); + else if(_index < countTI + countTI2) sprintf(temp, "TI2 Rom %c", 'A' + (_index - countTI)); + else sprintf(temp, "Snow Rom %c", 'A' + (_index - countTI - countTI2)); + break; + case virusLib::DeviceModel::TI2: + if(_index < countTI2) sprintf(temp, "TI2 Rom %c", 'A' + _index); + else if(_index < countTI2 + countTI) sprintf(temp, "TI Rom %c", 'A' + (_index - countTI2)); + else sprintf(temp, "Snow Rom %c", 'A' + (_index - countTI2 - countTI)); + break; + default: + assert(false); + break; + } + } + return temp; + } + + bool Controller::activatePatch(const std::vector<unsigned char>& _sysex) + { + return activatePatch(_sysex, isMultiMode() ? getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE)); + } + + bool Controller::activatePatch(const std::vector<unsigned char>& _sysex, uint32_t _part) + { + if(_part == virusLib::ProgramType::SINGLE) + { + if(isMultiMode()) + _part = 0; + } + else if(_part >= 16) + { + return false; + } + else if(!isMultiMode() && _part == 0) + { + _part = virusLib::ProgramType::SINGLE; + } + + const auto program = static_cast<uint8_t>(_part); + + // re-pack, force to edit buffer + const auto msg = modifySingleDump(_sysex, virusLib::BankNumber::EditBuffer, program); + + if(msg.empty()) + return false; + + // if we have locked parameters, get them, send the preset and then send each locked parameter value afterward. + // Modifying the preset directly does not work because a preset might be an old version that we do not know + const auto lockedParameters = getLockedParameters(static_cast<uint8_t>(_part == virusLib::SINGLE ? 0 : _part)); + + sendSysEx(msg); + + for (const auto& lockedParameter : lockedParameters) + { + const auto v = lockedParameter->getUnnormalizedValue(); + sendParameterChange(*lockedParameter, static_cast<uint8_t>(v)); + } + + requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), program); + + setCurrentPartPresetSource(program == virusLib::ProgramType::SINGLE ? 0 : program, PresetSource::Browser); + + return true; + } +}; // namespace Virus diff --git a/source/virusJucePlugin/VirusController.h b/source/virusJucePlugin/VirusController.h @@ -0,0 +1,180 @@ +#pragma once + +#include "../jucePluginLib/parameterdescriptions.h" +#include "../jucePluginLib/controller.h" + +#include "../virusLib/microcontrollerTypes.h" +#include "../virusLib/romfile.h" + +#include "../synthLib/plugin.h" + +class AudioPluginAudioProcessor; + +namespace Virus +{ + class Controller : public pluginLib::Controller, juce::Timer + { + public: + struct Patch + { + std::string name; + std::vector<uint8_t> data; + uint8_t progNumber = 0; + }; + + struct SinglePatch : Patch + { + virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); + }; + + struct MultiPatch : Patch {}; + + using Singles = std::vector<std::array<SinglePatch, 128>>; + + static constexpr auto kNameLength = 10; + + enum class MidiPacketType + { + RequestSingle, + RequestMulti, + RequestSingleBank, + RequestMultiBank, + RequestArrangement, + RequestGlobal, + RequestTotal, + RequestControllerDump, + ParameterChange, + SingleDump, + MultiDump, + SingleDump_C, + + Count + }; + + enum class PresetSource + { + Unknown, + Rom, + Browser + }; + + struct CurrentPreset + { + uint8_t program = 0; + virusLib::BankNumber bank = virusLib::BankNumber::EditBuffer; + PresetSource source = PresetSource::Unknown; + }; + + Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00); + ~Controller() override; + + std::vector<uint8_t> createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program); + std::vector<uint8_t> createSingleDump(MidiPacketType _packet, uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::AnyPartParamValues& _paramValues); + std::vector<uint8_t> modifySingleDump(const std::vector<uint8_t>& _sysex, virusLib::BankNumber _newBank, uint8_t _newProgram) const; + + void selectPrevPreset(uint8_t _part); + void selectNextPreset(uint8_t _part); + std::string getBankName(uint32_t _index) const; + + bool activatePatch(const std::vector<unsigned char>& _sysex); + bool activatePatch(const std::vector<unsigned char>& _sysex, uint32_t _part); + + static void printMessage(const pluginLib::SysEx &); + + juce::Value* getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex); + + juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; + std::string getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getSinglePresetName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; + std::string getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::AnyPartParamValues& _values) const; + + const Singles& getSinglePresets() const + { + return m_singles; + } + + const SinglePatch& getSingleEditBuffer() const + { + return m_singleEditBuffer; + } + + const SinglePatch& getSingleEditBuffer(const uint8_t _part) const + { + return m_singleEditBuffers[_part]; + } + + const MultiPatch& getMultiEditBuffer() const + { + return m_multiEditBuffer; + } + + void setSinglePresetName(uint8_t _part, const juce::String& _name) const; + void setSinglePresetName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _name) const; + + bool isMultiMode() const; + + // part 0 - 15 (ignored when single! 0x40...) + void setCurrentPartPreset(uint8_t _part, virusLib::BankNumber _bank, uint8_t _prg); + void setCurrentPartPresetSource(uint8_t _part, PresetSource _source); + + virusLib::BankNumber getCurrentPartBank(uint8_t _part) const; + uint8_t getCurrentPartProgram(uint8_t _part) const; + PresetSource getCurrentPartPresetSource(uint8_t _part) const; + + juce::String getCurrentPartPresetName(uint8_t _part) const; + uint32_t getBankCount() const { return static_cast<uint32_t>(m_singles.size()); } + void parseSysexMessage(const pluginLib::SysEx &) override; + void onStateLoaded() override; + std::function<void(int)> onProgramChange = {}; + std::function<void()> onMsgDone = {}; + std::function<void(virusLib::BankNumber _bank, uint32_t _program)> onRomPatchReceived = {}; + + bool requestProgram(uint8_t _bank, uint8_t _program, bool _multi) const; + bool requestSingle(uint8_t _bank, uint8_t _program) const; + bool requestMulti(uint8_t _bank, uint8_t _program) const; + + bool requestSingleBank(uint8_t _bank) const; + + bool requestTotal() const; + bool requestArrangement() const; + + void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; + bool sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const; + + using pluginLib::Controller::sendSysEx; + + bool sendSysEx(MidiPacketType _type) const; + bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; + + uint8_t getDeviceId() const { return m_deviceId; } + + bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg) const; + bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg, MidiPacketType& usedPacketType) const; + + private: + static std::string loadParameterDescriptions(const AudioPluginAudioProcessor& _processor); + + void timerCallback() override; + + Singles m_singles; + SinglePatch m_singleEditBuffer; // single mode + std::array<SinglePatch, 16> m_singleEditBuffers; // multi mode + + MultiPatch m_multiEditBuffer; + + void parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); + + void parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); + + void parseParamChange(const pluginLib::MidiPacket::Data& _data); + void parseControllerDump(const synthLib::SMidiEvent&); + + AudioPluginAudioProcessor& m_processor; + unsigned char m_deviceId; + virusLib::BankNumber m_currentBank[16]{}; + uint8_t m_currentProgram[16]{}; + PresetSource m_currentPresetSource[16]{PresetSource::Unknown}; + }; +}; // namespace Virus diff --git a/source/virusJucePlugin/VirusEditor.cpp b/source/virusJucePlugin/VirusEditor.cpp @@ -0,0 +1,461 @@ +#include "VirusEditor.h" + +#include "ArpUserPattern.h" +#include "PartButton.h" + +#include "ParameterNames.h" +#include "PluginProcessor.h" +#include "VirusController.h" + +#include "../jucePluginLib/parameterbinding.h" +#include "../jucePluginEditorLib/patchmanager/savepatchdesc.h" + +#include "../synthLib/os.h" + +#include "version.h" + +namespace genericVirusUI +{ + VirusEditor::VirusEditor(pluginLib::ParameterBinding& _binding, AudioPluginAudioProcessor& _processorRef, const std::string& _jsonFilename, std::string _skinFolder, std::function<void()> _openMenuCallback) : + Editor(_processorRef, _binding, std::move(_skinFolder)), + m_processor(_processorRef), + m_parameterBinding(_binding), + m_openMenuCallback(std::move(_openMenuCallback)), + m_romChangedListener(_processorRef.evRomChanged) + { + create(_jsonFilename); + + m_parts.reset(new Parts(*this)); + m_leds.reset(new Leds(*this, _processorRef)); + + // be backwards compatible with old skins + if(getTabGroupCount() == 0) + m_tabs.reset(new Tabs(*this)); + + // be backwards compatible with old skins + if(getControllerLinkCountRecursive() == 0) + m_controllerLinks.reset(new ControllerLinks(*this)); + + m_midiPorts.reset(new jucePluginEditorLib::MidiPorts(*this, getProcessor())); + + // be backwards compatible with old skins + if(!getConditionCountRecursive()) + m_fxPage.reset(new FxPage(*this)); + + const auto configOptions = getProcessor().getConfigOptions(); + const auto dir = configOptions.getDefaultFile().getParentDirectory(); + + { + auto pmParent = findComponent("ContainerPatchManager", false); + if(!pmParent) + pmParent = findComponent("page_presets", false); + if(!pmParent) + pmParent = findComponent("page_2_browser"); + setPatchManager(new PatchManager(*this, pmParent, dir)); + } + + m_presetName = findComponentT<juce::Label>("PatchName"); + + m_focusedParameter.reset(new jucePluginEditorLib::FocusedParameter(getController(), m_parameterBinding, *this)); + + m_romSelector = findComponentT<juce::ComboBox>("RomSelector"); + + m_playModeSingle = findComponentT<juce::Button>("PlayModeSingle", false); + m_playModeMulti = findComponentT<juce::Button>("PlayModeMulti", false); + + if(m_playModeSingle && m_playModeMulti) + { + m_playModeSingle->onClick = [this]{ if(m_playModeSingle->getToggleState()) setPlayMode(virusLib::PlayMode::PlayModeSingle); }; + m_playModeMulti->onClick = [this]{ if(m_playModeMulti->getToggleState()) setPlayMode(virusLib::PlayMode::PlayModeMulti); }; + } + else + { + m_playModeToggle = findComponentT<juce::Button>("PlayModeToggle"); + m_playModeToggle->onClick = [this]{ setPlayMode(m_playModeToggle->getToggleState() ? virusLib::PlayMode::PlayModeMulti : virusLib::PlayMode::PlayModeSingle); }; + } + + if(m_romSelector) + { + const auto roms = m_processor.getRoms(); + + if(roms.empty()) + { + m_romSelector->addItem("<No ROM found>", 1); + } + else + { + int id = 1; + + for (const auto& rom : roms) + { + const auto name = juce::File(rom.getFilename()).getFileNameWithoutExtension(); + m_romSelector->addItem(name + " (" + rom.getModelName() + ')', id++); + } + } + + m_romSelector->setSelectedId(static_cast<int>(m_processor.getSelectedRomIndex()) + 1, juce::dontSendNotification); + + m_romSelector->onChange = [this, roms] + { + const auto oldIndex = m_processor.getSelectedRomIndex(); + const auto newIndex = m_romSelector->getSelectedId() - 1; + if(!m_processor.setSelectedRom(newIndex)) + m_romSelector->setSelectedId(static_cast<int>(oldIndex) + 1); + }; + } + + getController().onProgramChange = [this](int _part) { onProgramChange(_part); }; + + addMouseListener(this, true); + + if(auto* versionInfo = findComponentT<juce::Label>("VersionInfo", false)) + { + const std::string message = "DSP 56300 Emulator Version " + std::string(g_pluginVersionString) + " - " __DATE__ " " __TIME__; + versionInfo->setText(message, juce::dontSendNotification); + } + + if(auto* versionNumber = findComponentT<juce::Label>("VersionNumber", false)) + { + versionNumber->setText(g_pluginVersionString, juce::dontSendNotification); + } + + m_deviceModel = findComponentT<juce::Label>("DeviceModel", false); + + auto* presetSave = findComponentT<juce::Button>("PresetSave", false); + if(presetSave) + presetSave->onClick = [this] { savePreset(); }; + + auto* presetLoad = findComponentT<juce::Button>("PresetLoad", false); + if(presetLoad) + presetLoad->onClick = [this] { loadPreset(); }; + + m_presetName->setEditable(false, true, true); + m_presetName->onTextChange = [this]() + { + const auto text = m_presetName->getText(); + if (text.trim().length() > 0) + { + getController().setSinglePresetName(getController().getCurrentPart(), text); + onProgramChange(getController().getCurrentPart()); + } + }; + m_presetNameMouseListener = new PartMouseListener(pluginLib::MidiPacket::AnyPart, [this](const juce::MouseEvent& _mouseEvent, int ) + { + startDragging(new jucePluginEditorLib::patchManager::SavePatchDesc(getController().getCurrentPart()), m_presetName); + }); + m_presetName->addMouseListener(m_presetNameMouseListener, false); + + auto* menuButton = findComponentT<juce::Button>("Menu", false); + + if(menuButton) + menuButton->onClick = m_openMenuCallback; + + updatePresetName(); + updatePlayModeButtons(); + + m_romChangedListener = [this](auto) + { + updateDeviceModel(); + updateKeyValueConditions("deviceModel", virusLib::getModelName(m_processor.getModel())); + m_parts->onPlayModeChanged(); + }; + } + + VirusEditor::~VirusEditor() + { + m_presetName->removeMouseListener(m_presetNameMouseListener); + delete m_presetNameMouseListener; + m_presetNameMouseListener = nullptr; + + m_focusedParameter.reset(); + + m_parameterBinding.clearBindings(); + + getController().onProgramChange = nullptr; + } + + Virus::Controller& VirusEditor::getController() const + { + return static_cast<Virus::Controller&>(m_processor.getController()); + } + + const char* VirusEditor::findEmbeddedResource(const std::string& _filename, uint32_t& _size) const + { + return m_processor.findEmbeddedResource(_filename.c_str(), _size); + } + + const char* VirusEditor::findResourceByFilename(const std::string& _filename, uint32_t& _size) + { + return findEmbeddedResource(_filename, _size); + } + + std::pair<std::string, std::string> VirusEditor::getDemoRestrictionText() const + { + return { + m_processor.getProperties().name + " - Demo Mode", + m_processor.getProperties().name + " runs in demo mode, the following restrictions apply:\n" + "\n" + "* The plugin state is not preserved\n" + "* Preset saving is disabled"}; + } + + genericUI::Button<juce::TextButton>* VirusEditor::createJuceComponent(genericUI::Button<juce::TextButton>* _button, genericUI::UiObject& _object) + { + if(_object.getName() == "PresetName") + return new PartButton(*this); + + return Editor::createJuceComponent(_button, _object); + } + + juce::Component* VirusEditor::createJuceComponent(juce::Component* _component, genericUI::UiObject& _object) + { + if(_object.getName() == "ArpUserGraphics") + return new ArpUserPattern(*this); + + return Editor::createJuceComponent(_component, _object); + } + + void VirusEditor::onProgramChange(int _part) + { + m_parts->onProgramChange(); + updatePresetName(); + updatePlayModeButtons(); + if(getPatchManager()) + getPatchManager()->onProgramChanged(_part); + } + + void VirusEditor::onPlayModeChanged() + { + m_parts->onPlayModeChanged(); + updatePresetName(); + updatePlayModeButtons(); + } + + void VirusEditor::onCurrentPartChanged() + { + m_parts->onCurrentPartChanged(); + updatePresetName(); + } + + void VirusEditor::mouseEnter(const juce::MouseEvent& event) + { + m_focusedParameter->onMouseEnter(event); + } + + void VirusEditor::updatePresetName() const + { + m_presetName->setText(getController().getCurrentPartPresetName(getController().getCurrentPart()), juce::dontSendNotification); + } + + void VirusEditor::updatePlayModeButtons() const + { + if(m_playModeSingle) + m_playModeSingle->setToggleState(!getController().isMultiMode(), juce::dontSendNotification); + if(m_playModeMulti) + m_playModeMulti->setToggleState(getController().isMultiMode(), juce::dontSendNotification); + if(m_playModeToggle) + m_playModeToggle->setToggleState(getController().isMultiMode(), juce::dontSendNotification); + } + + void VirusEditor::updateDeviceModel() + { + if(!m_deviceModel) + return; + + std::string m; + + switch(m_processor.getModel()) + { + case virusLib::DeviceModel::Invalid: + return; + case virusLib::DeviceModel::ABC: + { + auto* rom = m_processor.getSelectedRom(); + if(!rom) + return; + + virusLib::ROMFile::TPreset data; + if(!rom->getSingle(0, 0, data)) + return; + + switch(virusLib::Microcontroller::getPresetVersion(data.front())) + { + case virusLib::A: m = "A"; break; + case virusLib::B: m = "B"; break; + case virusLib::C: m = "C"; break; + case virusLib::D: m = "TI"; break; + case virusLib::D2: m = "TI2"; break; + default: m = "?"; break; + } + } + break; + case virusLib::DeviceModel::Snow: m = "Snow"; break; + case virusLib::DeviceModel::TI: m = "TI"; break; + case virusLib::DeviceModel::TI2: m = "TI2"; break; + } + + m_deviceModel->setText(m, juce::dontSendNotification); + } + + void VirusEditor::savePreset() + { + juce::PopupMenu menu; + + const auto countAdded = getPatchManager()->createSaveMenuEntries(menu, getPatchManager()->getCurrentPart()); + + if(countAdded) + menu.addSeparator(); + + auto addEntry = [&](juce::PopupMenu& _menu, const std::string& _name, const std::function<void(jucePluginEditorLib::FileType)>& _callback) + { + juce::PopupMenu subMenu; + + subMenu.addItem(".syx", [_callback](){_callback(jucePluginEditorLib::FileType::Syx); }); + subMenu.addItem(".mid", [_callback](){_callback(jucePluginEditorLib::FileType::Mid); }); + + _menu.addSubMenu(_name, subMenu); + }; + + addEntry(menu, "Export Current Single (Edit Buffer)", [this](jucePluginEditorLib::FileType _type) + { + savePresets(SaveType::CurrentSingle, _type); + }); + + if(getController().isMultiMode()) + { + addEntry(menu, "Export Arrangement (Multi + 16 Singles)", [this](jucePluginEditorLib::FileType _type) + { + savePresets(SaveType::Arrangement, _type); + }); + } + + juce::PopupMenu banksMenu; + for(uint8_t b=0; b<static_cast<uint8_t>(getController().getBankCount()); ++b) + { + addEntry(banksMenu, getController().getBankName(b), [this, b](const jucePluginEditorLib::FileType _type) + { + savePresets(SaveType::Bank, _type, b); + }); + } + + menu.addSubMenu("Export Bank", banksMenu); + + menu.showMenuAsync(juce::PopupMenu::Options()); + } + + void VirusEditor::loadPreset() + { + Editor::loadPreset([this](const juce::File& _result) + { + pluginLib::patchDB::DataList results; + + if(!getPatchManager()->loadFile(results, _result.getFullPathName().toStdString())) + return; + + auto& c = getController(); + + // we attempt to convert all results as some of them might not be valid preset data + for(size_t i=0; i<results.size();) + { + // convert to load to edit buffer of current part + const auto data = c.modifySingleDump(results[i], virusLib::BankNumber::EditBuffer, c.isMultiMode() ? c.getCurrentPart() : virusLib::SINGLE); + if(data.empty()) + results.erase(results.begin() + i); + else + results[i++] = data; + } + + if (results.size() == 1) + { + c.activatePatch(results.front()); + } + else if(results.size() > 1) + { + juce::NativeMessageBox::showMessageBox(juce::AlertWindow::InfoIcon, "Information", + "The selected file contains more than one patch. Please add this file as a data source in the Patch Manager instead.\n\n" + "Go to the Patch Manager, right click the 'Data Sources' node and select 'Add File...' to import it." + ); + } + }); + } + + void VirusEditor::setPlayMode(uint8_t _playMode) + { + const auto playMode = getController().getParameterIndexByName(Virus::g_paramPlayMode); + + auto* param = getController().getParameter(playMode); + param->setValue(param->convertTo0to1(_playMode), pluginLib::Parameter::ChangedBy::Ui); + + // we send this directly here as we request a new arrangement below, we don't want to wait on juce to inform the knob to have changed + getController().sendParameterChange(*param, _playMode); + + if (_playMode == virusLib::PlayModeSingle && getController().getCurrentPart() != 0) + setPart(0); + + onPlayModeChanged(); + + getController().requestArrangement(); + } + + void VirusEditor::savePresets(SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber/* = 0*/) + { + Editor::savePreset([this, _saveType, _bankNumber, _fileType](const juce::File& _result) + { + jucePluginEditorLib::FileType fileType = _fileType; + const auto file = createValidFilename(fileType, _result); + savePresets(file, _saveType, fileType, _bankNumber); + }); + } + + bool VirusEditor::savePresets(const std::string& _pathName, SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber/* = 0*/) const + { +#if SYNTHLIB_DEMO_MODE + return false; +#else + std::vector< std::vector<uint8_t> > messages; + + switch (_saveType) + { + case SaveType::CurrentSingle: + { + const auto dump = getController().createSingleDump(getController().getCurrentPart(), toMidiByte(virusLib::BankNumber::A), 0); + messages.push_back(dump); + } + break; + case SaveType::Bank: + { + const auto& presets = getController().getSinglePresets(); + if(_bankNumber < presets.size()) + { + const auto& bankPresets = presets[_bankNumber]; + for (const auto& bankPreset : bankPresets) + messages.push_back(bankPreset.data); + } + } + break; + case SaveType::Arrangement: + { + messages.push_back(getController().getMultiEditBuffer().data); + + for(uint8_t i=0; i<16; ++i) + { + const auto dump = getController().createSingleDump(i, toMidiByte(virusLib::BankNumber::EditBuffer), i); + messages.push_back(dump); + } + } + break; + default: + return false; + } + + return Editor::savePresets(_fileType, _pathName, messages); +#endif + } + + void VirusEditor::setPart(size_t _part) + { + m_parameterBinding.setPart(static_cast<uint8_t>(_part)); + onCurrentPartChanged(); + setCurrentPart(static_cast<uint8_t>(_part)); + } +} diff --git a/source/virusJucePlugin/VirusEditor.h b/source/virusJucePlugin/VirusEditor.h @@ -0,0 +1,111 @@ +#pragma once + +#include "../../jucePluginEditorLib/midiPorts.h" +#include "../../jucePluginEditorLib/pluginEditor.h" +#include "../../jucePluginEditorLib/focusedParameter.h" + +#include "../../jucePluginLib/event.h" + +#include "Parts.h" +#include "Tabs.h" +#include "FxPage.h" +#include "PatchManager.h" +#include "ControllerLinks.h" +#include "Leds.h" + +namespace virusLib +{ + class ROMFile; +} + +namespace jucePluginEditorLib +{ + class FocusedParameter; +} + +namespace pluginLib +{ + class Parameter; + class ParameterBinding; +} + +class AudioPluginAudioProcessor; + +namespace genericVirusUI +{ + class VirusEditor : public jucePluginEditorLib::Editor + { + public: + enum class SaveType + { + CurrentSingle, + Bank, + Arrangement + }; + + VirusEditor(pluginLib::ParameterBinding& _binding, AudioPluginAudioProcessor& _processorRef, const std::string& _jsonFilename, + std::string _skinFolder, std::function<void()> _openMenuCallback); + ~VirusEditor() override; + + void setPart(size_t _part); + + pluginLib::ParameterBinding& getParameterBinding() const { return m_parameterBinding; } + + Virus::Controller& getController() const; + + const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size) const; + const char* findResourceByFilename(const std::string& _filename, uint32_t& _size) override; + + std::pair<std::string, std::string> getDemoRestrictionText() const override; + + genericUI::Button<juce::TextButton>* createJuceComponent(genericUI::Button<juce::TextButton>*, genericUI::UiObject& _object) override; + juce::Component* createJuceComponent(juce::Component*, genericUI::UiObject& _object) override; + + private: + void onProgramChange(int _part); + void onPlayModeChanged(); + void onCurrentPartChanged(); + + void mouseEnter(const juce::MouseEvent& event) override; + + void updatePresetName() const; + void updatePlayModeButtons() const; + + void updateDeviceModel(); + + void savePreset(); + void loadPreset(); + + void setPlayMode(uint8_t _playMode); + + void savePresets(SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber = 0); + bool savePresets(const std::string& _pathName, SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber = 0) const; + + AudioPluginAudioProcessor& m_processor; + pluginLib::ParameterBinding& m_parameterBinding; + + std::unique_ptr<Parts> m_parts; + std::unique_ptr<Leds> m_leds; + std::unique_ptr<Tabs> m_tabs; + std::unique_ptr<jucePluginEditorLib::MidiPorts> m_midiPorts; + std::unique_ptr<FxPage> m_fxPage; + std::unique_ptr<ControllerLinks> m_controllerLinks; + + juce::Label* m_presetName = nullptr; + PartMouseListener* m_presetNameMouseListener = nullptr; + + std::unique_ptr<jucePluginEditorLib::FocusedParameter> m_focusedParameter; + + juce::ComboBox* m_romSelector = nullptr; + + juce::Button* m_playModeSingle = nullptr; + juce::Button* m_playModeMulti = nullptr; + juce::Button* m_playModeToggle = nullptr; + + juce::Label* m_deviceModel = nullptr; + + std::function<void()> m_openMenuCallback; + + pluginLib::EventListener<const virusLib::ROMFile*> m_romChangedListener; + }; +} diff --git a/source/virusJucePlugin/version.h b/source/virusJucePlugin/version.h @@ -0,0 +1,4 @@ +#pragma once + +static constexpr const char* const g_pluginVersionString = "1.3.12"; +static constexpr uint32_t g_pluginVersion = 1312; diff --git a/source/jucePlugin/version.h.in b/source/virusJucePlugin/version.h.in