commit 7bf0494c14a6a68eeee0d63015b592c3e38ed2d2
parent cd18bb1ea8a9027b37b300b689f48f361662ac56
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sat, 10 Dec 2022 03:41:55 +0100
Osirus 1.2.22
Diffstat:
38 files changed, 1135 insertions(+), 422 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -5,7 +5,7 @@ set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "OS X Architectures")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version")
-project(gearmulator VERSION 1.2.20)
+project(gearmulator VERSION 1.2.22)
include(base.cmake)
@@ -39,6 +39,7 @@ endif()
# ----------------- Console Programs
add_subdirectory(source/virusTestConsole)
+add_subdirectory(source/logicAnalysisParser)
add_subdirectory(source/virusIntegrationTest)
# ----------------- CPack
diff --git a/doc/changelog.txt b/doc/changelog.txt
@@ -1,5 +1,28 @@
Release Notes
+1.2.21 (2022.12.06):
+
+Osirus:
+- [Imp] ROM file will now also be found if put next to the binary in the component folder (VST3 and AU builds)
+- [Imp] Prev/Next buttons now select preset browser patches instead of ROM patches if a preset browser patch was selected last
+- [Imp] More save options have been added, either save one Single, or the current arrangement (if in Multi mode) or a whole bank
+- [Imp] All save options now support saving as either .syx or as .mid
+
+- [Fix] Plugin may not emit any sound after the plugin state was restored (if part of a previously saved project)
+- [Fix] Some UI elements didn't update their state correctly
+- [Fix] UI may send incorrect control changes back to DSP after changing patches
+- [Fix] Loading multiple Singles via bank load (file with more than one Single) didn't load all Singles but only the last one
+- [Fix] GUI related crash after switching skins
+- [Fix] Latency setting was not applied before editor opened
+
+Test Console:
+- Fix demo song couldn't be loaded from ROM
+
+DSP:
+- Performance improvements (5-10%)
+- Stability improvemens
+- Large speedup of code (re)generation. Results in faster startup time, reduces hiccups in Multi mode and when changing patches
+
1.2.20 (2022.07.29):
- [Imp] Performance has improved by 10% - 20% depending on the use case
diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt
@@ -17,6 +17,8 @@ set(SOURCES
ParameterNames.h
PluginEditor.cpp
PluginEditor.h
+ PluginEditorState.cpp
+ PluginEditorState.h
PluginProcessor.cpp
PluginProcessor.h
VirusController.cpp
@@ -27,6 +29,8 @@ set(SOURCES
)
set(SOURCES_UI3
+ ui3/ControllerLinks.cpp
+ ui3/ControllerLinks.h
ui3/FxPage.cpp
ui3/FxPage.h
ui3/MidiPorts.cpp
diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp
@@ -1,249 +1,69 @@
-#include "PluginProcessor.h"
#include "PluginEditor.h"
-#include "VirusController.h"
-
-#include "ui3/VirusEditor.h"
+#include "PluginEditorState.h"
+#include "PluginProcessor.h"
-#include "../synthLib/os.h"
+#include "VirusController.h"
//==============================================================================
-AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) :
- AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p)
+AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p, PluginEditorState& s) :
+ AudioProcessorEditor(&p), processorRef(p), m_state(s)
{
- m_includedSkins.push_back({"Hoverland", "VirusC_Hoverland.json", ""});
- m_includedSkins.push_back({"Trancy", "VirusC_Trancy.json", ""});
- m_includedSkins.push_back({"Galaxpel", "VirusC_Galaxpel.json", ""});
-
addMouseListener(this, true);
- const auto config = processorRef.getController().getConfig();
- const auto scale = config->getIntValue("scale", 100);
-
- Skin skin = readSkinFromConfig();
-
- if(skin.jsonFilename.empty())
- {
- skin = m_includedSkins[0];
- }
-
- loadSkin(skin);
- setGuiScale(scale);
-
- const auto latencyBlocks = config->getIntValue("latencyBlocks", static_cast<int>(processorRef.getPlugin().getLatencyBlocks()));
- setLatencyBlocks(latencyBlocks);
-}
-
-void AudioPluginAudioProcessorEditor::loadSkin(const Skin& _skin)
-{
- if(m_currentSkin == _skin)
- return;
-
- m_currentSkin = _skin;
- writeSkinToConfig(_skin);
-
- if (m_virusEditor)
- {
- m_parameterBinding.clearBindings();
-
- if(getIndexOfChildComponent(m_virusEditor.get()) > -1)
- removeChildComponent(m_virusEditor.get());
- m_virusEditor.reset();
- }
-
- m_rootScale = 1.0f;
-
- try
+ m_state.evSkinLoaded = [&](juce::Component* _component)
{
- auto* editor = new genericVirusUI::VirusEditor(m_parameterBinding, processorRef, _skin.jsonFilename, _skin.folder, [this] { openMenu(); });
- m_virusEditor.reset(editor);
- setSize(m_virusEditor->getWidth(), m_virusEditor->getHeight());
- m_rootScale = editor->getScale();
+ setUiRoot(_component);
+ };
- m_virusEditor->setTopLeftPosition(0, 0);
- addAndMakeVisible(m_virusEditor.get());
- }
- catch(const std::runtime_error& _err)
+ m_state.evSetGuiScale = [&](const int _scale)
{
- LOG("ERROR: Failed to create editor: " << _err.what());
+ if(getNumChildComponents() > 0)
+ setGuiScale(getChildComponent(0), _scale);
+ };
- juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Skin load failed", _err.what(), "OK");
- m_virusEditor.reset();
+ m_state.enableBindings();
- loadSkin(m_includedSkins[0]);
- }
+ setUiRoot(m_state.getUiRoot());
}
-void AudioPluginAudioProcessorEditor::setGuiScale(int percent)
-{
- setScaleFactor(static_cast<float>(percent)/100.0f * m_rootScale);
- auto* config = processorRef.getController().getConfig();
- config->setValue("scale", percent);
- config->saveIfNeeded();
-}
-
-void AudioPluginAudioProcessorEditor::openMenu()
+AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor()
{
- const auto config = processorRef.getController().getConfig();
- const auto scale = config->getIntValue("scale", 100);
-
- juce::PopupMenu menu;
-
- juce::PopupMenu skinMenu;
-
- auto addSkinEntry = [this, &skinMenu](const Skin& _skin)
- {
- skinMenu.addItem(_skin.displayName, true, _skin == m_currentSkin,[this, _skin] {loadSkin(_skin);});
- };
+ m_state.evSetGuiScale = [&](int){};
+ m_state.evSkinLoaded = [&](juce::Component*){};
- for (const auto & skin : m_includedSkins)
- addSkinEntry(skin);
-
- bool haveSkinsOnDisk = false;
-
- // find more skins on disk
- const auto modulePath = synthLib::getModulePath();
-
- std::vector<std::string> entries;
- synthLib::getDirectoryEntries(entries, modulePath + "skins");
-
- for (const auto& entry : entries)
- {
- std::vector<std::string> files;
- synthLib::getDirectoryEntries(files, entry);
+ m_state.disableBindings();
- for (const auto& file : files)
- {
- if(synthLib::hasExtension(file, ".json"))
- {
- if(!haveSkinsOnDisk)
- {
- haveSkinsOnDisk = true;
- skinMenu.addSeparator();
- }
-
- const auto relativePath = entry.substr(modulePath.size());
- auto jsonName = file;
- const auto pathEndPos = jsonName.find_last_of("/\\");
- if(pathEndPos != std::string::npos)
- jsonName = file.substr(pathEndPos+1);
- const Skin skin{jsonName + " (" + relativePath + ")", jsonName, relativePath};
- addSkinEntry(skin);
- }
- }
- }
-
- if(m_virusEditor && m_currentSkin.folder.empty())
- {
- auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get());
- if(editor)
- {
- skinMenu.addSeparator();
- skinMenu.addItem("Export current skin to 'skins' folder on disk", true, false, [this]{exportCurrentSkin();});
- }
- }
-
- juce::PopupMenu scaleMenu;
- scaleMenu.addItem("50%", true, scale == 50, [this] { setGuiScale(50); });
- scaleMenu.addItem("75%", true, scale == 75, [this] { setGuiScale(75); });
- scaleMenu.addItem("100%", true, scale == 100, [this] { setGuiScale(100); });
- scaleMenu.addItem("125%", true, scale == 125, [this] { setGuiScale(125); });
- scaleMenu.addItem("150%", true, scale == 150, [this] { setGuiScale(150); });
- scaleMenu.addItem("200%", true, scale == 200, [this] { setGuiScale(200); });
- scaleMenu.addItem("300%", true, scale == 300, [this] { setGuiScale(300); });
-
- const auto latency = processorRef.getPlugin().getLatencyBlocks();
- juce::PopupMenu latencyMenu;
- latencyMenu.addItem("0 (DAW will report proper CPU usage)", true, latency == 0, [this] { setLatencyBlocks(0); });
- latencyMenu.addItem("1 (default)", true, latency == 1, [this] { setLatencyBlocks(1); });
- latencyMenu.addItem("2", true, latency == 2, [this] { setLatencyBlocks(2); });
- latencyMenu.addItem("4", true, latency == 4, [this] { setLatencyBlocks(4); });
- latencyMenu.addItem("8", true, latency == 8, [this] { setLatencyBlocks(8); });
-
- menu.addSubMenu("GUI Skin", skinMenu);
- menu.addSubMenu("GUI Scale", scaleMenu);
- menu.addSubMenu("Latency (blocks)", latencyMenu);
-
- menu.showMenuAsync(juce::PopupMenu::Options());
+ setUiRoot(nullptr);
}
-void AudioPluginAudioProcessorEditor::exportCurrentSkin() const
+void AudioPluginAudioProcessorEditor::setGuiScale(juce::Component* _comp, int percent)
{
- if(!m_virusEditor)
- return;
-
- auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get());
-
- if(!editor)
+ if(!_comp)
return;
- const auto res = editor->exportToFolder(synthLib::getModulePath() + "skins/");
+ const auto s = static_cast<float>(percent)/100.0f * m_state.getRootScale();
+ _comp->setTransform(juce::AffineTransform::scale(s,s));
- if(!res.empty())
- {
- juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Export failed", "Failed to export skin:\n\n" + res, "OK", m_virusEditor.get());
- }
- else
- {
- juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::InfoIcon, "Export finished", "Skin successfully exported");
- }
-}
+ setSize(static_cast<int>(m_state.getWidth() * s), static_cast<int>(m_state.getHeight() * s));
-AudioPluginAudioProcessorEditor::Skin AudioPluginAudioProcessorEditor::readSkinFromConfig() const
-{
- const auto* config = processorRef.getController().getConfig();
-
- Skin skin;
- skin.displayName = config->getValue("skinDisplayName", "").toStdString();
- skin.jsonFilename = config->getValue("skinFile", "").toStdString();
- skin.folder = config->getValue("skinFolder", "").toStdString();
- return skin;
-}
-
-void AudioPluginAudioProcessorEditor::writeSkinToConfig(const Skin& _skin) const
-{
auto* config = processorRef.getController().getConfig();
-
- config->setValue("skinDisplayName", _skin.displayName.c_str());
- config->setValue("skinFile", _skin.jsonFilename.c_str());
- config->setValue("skinFolder", _skin.folder.c_str());
-}
-
-void AudioPluginAudioProcessorEditor::setLatencyBlocks(uint32_t _blocks) const
-{
- auto& p = processorRef.getPlugin();
-
- if(p.setLatencyBlocks(_blocks))
- {
- processorRef.updateLatencySamples();
-
- auto* config = processorRef.getController().getConfig();
- config->setValue("latencyBlocks", static_cast<int>(_blocks));
- config->saveIfNeeded();
- }
+ config->setValue("scale", percent);
+ config->saveIfNeeded();
}
-AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor()
+void AudioPluginAudioProcessorEditor::setUiRoot(juce::Component* _component)
{
- m_virusEditor.reset();
-}
+ removeAllChildren();
-//==============================================================================
-void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g)
-{
-}
+ if(!_component)
+ return;
-void AudioPluginAudioProcessorEditor::timerCallback()
-{
- // ugly (polling!) way for refreshing presets names as this is temporary ui
-}
+ const auto& config = processorRef.getController().getConfig();
+ const auto scale = config->getIntValue("scale", 100);
-void AudioPluginAudioProcessorEditor::resized()
-{
- // This is generally where you'll want to lay out the positions of any
- // subcomponents in your editor..
- auto area = getLocalBounds();
- area.removeFromTop(35);
+ setGuiScale(_component, scale);
+ addAndMakeVisible(_component);
}
void AudioPluginAudioProcessorEditor::mouseDown(const juce::MouseEvent& event)
@@ -258,5 +78,8 @@ void AudioPluginAudioProcessorEditor::mouseDown(const juce::MouseEvent& event)
if(event.eventComponent && event.eventComponent->findParentComponentOfClass<juce::FileBrowserComponent>())
return;
- openMenu();
+ if(dynamic_cast<juce::TextEditor*>(event.eventComponent))
+ return;
+
+ m_state.openMenu();
}
diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h
@@ -2,52 +2,28 @@
#include "VirusParameterBinding.h"
-#include "PluginProcessor.h"
+class PluginEditorState;
//==============================================================================
-class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer
+class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor
{
public:
- explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&);
+ explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&, PluginEditorState&);
~AudioPluginAudioProcessorEditor() override;
- void paint (juce::Graphics&) override;
- void resized() override;
void mouseDown(const juce::MouseEvent& event) override;
+ void paint(juce::Graphics& g) override {}
+
private:
- struct Skin
- {
- std::string displayName;
- std::string jsonFilename;
- std::string folder;
-
- bool operator == (const Skin& _other) const
- {
- return displayName == _other.displayName && jsonFilename == _other.jsonFilename && folder == _other.folder;
- }
- };
-
- void timerCallback() override;
- void loadSkin(const Skin& _skin);
- void setGuiScale(int percent);
- void openMenu();
- void exportCurrentSkin() const;
- Skin readSkinFromConfig() const;
- void writeSkinToConfig(const Skin& _skin) const;
- void setLatencyBlocks(uint32_t _blocks) const;
+ 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;
- VirusParameterBinding m_parameterBinding;
-
- std::unique_ptr<juce::Component> m_virusEditor;
- Skin m_currentSkin;
- float m_rootScale = 1.0f;
-
- std::vector<Skin> m_includedSkins;
+ PluginEditorState& m_state;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessorEditor)
};
diff --git a/source/jucePlugin/PluginEditorState.cpp b/source/jucePlugin/PluginEditorState.cpp
@@ -0,0 +1,232 @@
+#include "PluginEditorState.h"
+
+#include "PluginProcessor.h"
+
+#include "ui3/VirusEditor.h"
+
+#include "../synthLib/os.h"
+
+const std::vector<PluginEditorState::Skin> m_includedSkins =
+{
+ {"Hoverland", "VirusC_Hoverland.json", ""},
+ {"Trancy", "VirusC_Trancy.json", ""},
+ {"Galaxpel", "VirusC_Galaxpel.json", ""}
+};
+
+PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor) : m_processor(_processor), m_parameterBinding(_processor.getController())
+{
+ Skin skin = readSkinFromConfig();
+
+ if(skin.jsonFilename.empty())
+ {
+ skin = m_includedSkins[0];
+ }
+
+ loadSkin(skin);
+}
+
+int PluginEditorState::getWidth() const
+{
+ return m_virusEditor ? m_virusEditor->getWidth() : 0;
+}
+
+int PluginEditorState::getHeight() const
+{
+ return m_virusEditor ? m_virusEditor->getHeight() : 0;
+}
+
+const std::vector<PluginEditorState::Skin>& PluginEditorState::getIncludedSkins()
+{
+ return m_includedSkins;
+}
+
+juce::Component* PluginEditorState::getUiRoot() const
+{
+ return m_virusEditor.get();
+}
+
+void PluginEditorState::disableBindings()
+{
+ m_parameterBinding.disableBindings();
+}
+
+void PluginEditorState::enableBindings()
+{
+ m_parameterBinding.enableBindings();
+}
+
+void PluginEditorState::loadSkin(const Skin& _skin)
+{
+ if(m_currentSkin == _skin)
+ return;
+
+ m_currentSkin = _skin;
+ writeSkinToConfig(_skin);
+
+ if (m_virusEditor)
+ {
+ m_parameterBinding.clearBindings();
+
+ auto* parent = m_virusEditor->getParentComponent();
+
+ if(parent && parent->getIndexOfChildComponent(m_virusEditor.get()) > -1)
+ parent->removeChildComponent(m_virusEditor.get());
+ m_virusEditor.reset();
+ }
+
+ m_rootScale = 1.0f;
+
+ try
+ {
+ auto* editor = new genericVirusUI::VirusEditor(m_parameterBinding, m_processor, _skin.jsonFilename, _skin.folder, [this] { openMenu(); });
+ m_virusEditor.reset(editor);
+ m_rootScale = editor->getScale();
+
+ m_virusEditor->setTopLeftPosition(0, 0);
+
+ if(evSkinLoaded)
+ evSkinLoaded(m_virusEditor.get());
+ }
+ catch(const std::runtime_error& _err)
+ {
+ LOG("ERROR: Failed to create editor: " << _err.what());
+
+ juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Skin load failed", _err.what(), "OK");
+ m_virusEditor.reset();
+
+ loadSkin(m_includedSkins[0]);
+ }
+}
+
+void PluginEditorState::setGuiScale(int _scale) const
+{
+ if(evSetGuiScale)
+ evSetGuiScale(_scale);
+}
+
+void PluginEditorState::openMenu()
+{
+ const auto config = m_processor.getController().getConfig();
+ const auto scale = config->getIntValue("scale", 100);
+
+ juce::PopupMenu menu;
+
+ juce::PopupMenu skinMenu;
+
+ auto addSkinEntry = [this, &skinMenu](const PluginEditorState::Skin& _skin)
+ {
+ skinMenu.addItem(_skin.displayName, true, _skin == getCurrentSkin(),[this, _skin] {loadSkin(_skin);});
+ };
+
+ for (const auto & skin : PluginEditorState::getIncludedSkins())
+ addSkinEntry(skin);
+
+ bool haveSkinsOnDisk = false;
+
+ // find more skins on disk
+ const auto modulePath = synthLib::getModulePath();
+
+ std::vector<std::string> entries;
+ synthLib::getDirectoryEntries(entries, modulePath + "skins");
+
+ for (const auto& entry : entries)
+ {
+ std::vector<std::string> files;
+ synthLib::getDirectoryEntries(files, entry);
+
+ for (const auto& file : files)
+ {
+ if(synthLib::hasExtension(file, ".json"))
+ {
+ if(!haveSkinsOnDisk)
+ {
+ haveSkinsOnDisk = true;
+ skinMenu.addSeparator();
+ }
+
+ const auto relativePath = entry.substr(modulePath.size());
+ auto jsonName = file;
+ const auto pathEndPos = jsonName.find_last_of("/\\");
+ if(pathEndPos != std::string::npos)
+ jsonName = file.substr(pathEndPos+1);
+ const Skin skin{jsonName + " (" + relativePath + ")", jsonName, relativePath};
+ addSkinEntry(skin);
+ }
+ }
+ }
+
+ if(m_virusEditor && m_currentSkin.folder.empty())
+ {
+ auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get());
+ if(editor)
+ {
+ skinMenu.addSeparator();
+ skinMenu.addItem("Export current skin to 'skins' folder on disk", true, false, [this]{exportCurrentSkin();});
+ }
+ }
+
+ juce::PopupMenu scaleMenu;
+ scaleMenu.addItem("50%", true, scale == 50, [this] { setGuiScale(50); });
+ scaleMenu.addItem("75%", true, scale == 75, [this] { setGuiScale(75); });
+ scaleMenu.addItem("100%", true, scale == 100, [this] { setGuiScale(100); });
+ scaleMenu.addItem("125%", true, scale == 125, [this] { setGuiScale(125); });
+ scaleMenu.addItem("150%", true, scale == 150, [this] { setGuiScale(150); });
+ scaleMenu.addItem("200%", true, scale == 200, [this] { setGuiScale(200); });
+ scaleMenu.addItem("300%", true, scale == 300, [this] { setGuiScale(300); });
+
+ const auto latency = m_processor.getPlugin().getLatencyBlocks();
+ juce::PopupMenu latencyMenu;
+ latencyMenu.addItem("0 (DAW will report proper CPU usage)", true, latency == 0, [this] { m_processor.setLatencyBlocks(0); });
+ latencyMenu.addItem("1 (default)", true, latency == 1, [this] { m_processor.setLatencyBlocks(1); });
+ latencyMenu.addItem("2", true, latency == 2, [this] { m_processor.setLatencyBlocks(2); });
+ latencyMenu.addItem("4", true, latency == 4, [this] { m_processor.setLatencyBlocks(4); });
+ latencyMenu.addItem("8", true, latency == 8, [this] { m_processor.setLatencyBlocks(8); });
+
+ menu.addSubMenu("GUI Skin", skinMenu);
+ menu.addSubMenu("GUI Scale", scaleMenu);
+ menu.addSubMenu("Latency (blocks)", latencyMenu);
+
+ menu.showMenuAsync(juce::PopupMenu::Options());
+}
+
+void PluginEditorState::exportCurrentSkin() const
+{
+ if(!m_virusEditor)
+ return;
+
+ auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get());
+
+ if(!editor)
+ return;
+
+ const auto res = editor->exportToFolder(synthLib::getModulePath() + "skins/");
+
+ if(!res.empty())
+ {
+ juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Export failed", "Failed to export skin:\n\n" + res, "OK", m_virusEditor.get());
+ }
+ else
+ {
+ juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::InfoIcon, "Export finished", "Skin successfully exported");
+ }
+}
+
+PluginEditorState::Skin PluginEditorState::readSkinFromConfig() const
+{
+ const auto* config = m_processor.getController().getConfig();
+
+ Skin skin;
+ skin.displayName = config->getValue("skinDisplayName", "").toStdString();
+ skin.jsonFilename = config->getValue("skinFile", "").toStdString();
+ skin.folder = config->getValue("skinFolder", "").toStdString();
+ return skin;
+}
+
+void PluginEditorState::writeSkinToConfig(const Skin& _skin) const
+{
+ auto* config = m_processor.getController().getConfig();
+
+ config->setValue("skinDisplayName", _skin.displayName.c_str());
+ config->setValue("skinFile", _skin.jsonFilename.c_str());
+ config->setValue("skinFolder", _skin.folder.c_str());
+}
diff --git a/source/jucePlugin/PluginEditorState.h b/source/jucePlugin/PluginEditorState.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <string>
+
+#include "VirusParameterBinding.h"
+
+class AudioPluginAudioProcessor;
+
+class PluginEditorState
+{
+public:
+ struct Skin
+ {
+ std::string displayName;
+ std::string jsonFilename;
+ std::string folder;
+
+ bool operator == (const Skin& _other) const
+ {
+ return displayName == _other.displayName && jsonFilename == _other.jsonFilename && folder == _other.folder;
+ }
+ };
+
+ explicit PluginEditorState(AudioPluginAudioProcessor& _processor);
+
+ void exportCurrentSkin() const;
+ Skin readSkinFromConfig() const;
+ void writeSkinToConfig(const Skin& _skin) const;
+
+ float getRootScale() const { return m_rootScale; }
+
+ int getWidth() const;
+ int getHeight() const;
+
+ const Skin& getCurrentSkin() { return m_currentSkin; }
+ static const std::vector<Skin>& getIncludedSkins();
+
+ void openMenu();
+
+ std::function<void(int)> evSetGuiScale;
+ std::function<void(juce::Component*)> evSkinLoaded;
+
+ juce::Component* getUiRoot() const;
+
+ void disableBindings();
+ void enableBindings();
+
+private:
+ void loadSkin(const Skin& _skin);
+ void setGuiScale(int _scale) const;
+
+ AudioPluginAudioProcessor& m_processor;
+
+ VirusParameterBinding m_parameterBinding;
+
+ std::unique_ptr<juce::Component> m_virusEditor;
+ Skin m_currentSkin;
+ float m_rootScale = 1.0f;
+};
diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp
@@ -1,5 +1,6 @@
#include "PluginProcessor.h"
#include "PluginEditor.h"
+#include "PluginEditorState.h"
#include "ParameterNames.h"
#include <juce_audio_processors/juce_audio_processors.h>
@@ -24,6 +25,9 @@ AudioPluginAudioProcessor::AudioPluginAudioProcessor() :
{
getController(); // init controller
m_clockTempoParam = getController().getParameterIndexByName(Virus::g_paramClockTempo);
+
+ const auto latencyBlocks = getController().getConfig()->getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks()));
+ setLatencyBlocks(latencyBlocks);
}
AudioPluginAudioProcessor::~AudioPluginAudioProcessor() = default;
@@ -266,7 +270,9 @@ bool AudioPluginAudioProcessor::hasEditor() const
juce::AudioProcessorEditor* AudioPluginAudioProcessor::createEditor()
{
- return new AudioPluginAudioProcessorEditor (*this);
+ if(!m_editorState)
+ m_editorState.reset(new PluginEditorState(*this));
+ return new AudioPluginAudioProcessorEditor (*this, *m_editorState);
}
//==============================================================================
@@ -413,6 +419,20 @@ void AudioPluginAudioProcessor::updateLatencySamples()
setLatencySamples(m_plugin.getLatencyInputToOutput());
}
+void AudioPluginAudioProcessor::setLatencyBlocks(uint32_t _blocks)
+{
+ auto& p = getPlugin();
+
+ if(p.setLatencyBlocks(_blocks))
+ {
+ updateLatencySamples();
+
+ auto* config = getController().getConfig();
+ config->setValue("latencyBlocks", static_cast<int>(_blocks));
+ config->saveIfNeeded();
+ }
+}
+
Virus::Controller &AudioPluginAudioProcessor::getController()
{
if (m_controller == nullptr)
diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h
@@ -2,10 +2,14 @@
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_audio_devices/juce_audio_devices.h>
+
#include "../synthLib/plugin.h"
#include "../virusLib/device.h"
+
#include "VirusController.h"
+class PluginEditorState;
+
//==============================================================================
class AudioPluginAudioProcessor : public juce::AudioProcessor, juce::MidiInputCallback
{
@@ -60,7 +64,8 @@ public:
juce::MidiInput* getMidiInput() const;
void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override;
- std::string getRomName() {
+ std::string getRomName() const
+ {
return juce::File(juce::String(m_romName)).getFileNameWithoutExtension().toStdString();
}
virusLib::ROMFile::Model getModel() const
@@ -73,6 +78,8 @@ public:
}
void updateLatencySamples();
+ void setLatencyBlocks(uint32_t _blocks);
+
// _____________
//
private:
@@ -90,4 +97,5 @@ private:
synthLib::Plugin m_plugin;
std::vector<synthLib::SMidiEvent> m_midiOut;
uint32_t m_clockTempoParam = 0xffffffff;
+ std::unique_ptr<PluginEditorState> m_editorState;
};
diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp
@@ -148,10 +148,10 @@ namespace Virus
}
}
for (const auto& param : globalParams)
- param->setValueFromSynth(value, true);
+ param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::ControlChange);
}
for (const auto& param : partParams)
- param->setValueFromSynth(value, true);
+ param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::ControlChange);
// TODO:
/**
If a
@@ -389,10 +389,10 @@ namespace Virus
for(auto it = _parameterValues.begin(); it != _parameterValues.end(); ++it)
{
auto* p = getParameter(it->first.second, ch);
- p->setValueFromSynth(it->second, true);
+ p->setValueFromSynth(it->second, true, pluginLib::Parameter::ChangedBy::PresetChange);
- for (const auto& linkedParam : p->getLinkedParameters())
- linkedParam->setValueFromSynth(it->second, true);
+ for (const auto& derivedParam : p->getDerivedParameters())
+ derivedParam->setValueFromSynth(it->second, true, pluginLib::Parameter::ChangedBy::PresetChange);
}
if(m_currentPresetSource[ch] != PresetSource::Browser)
@@ -465,7 +465,7 @@ namespace Virus
if(desc.page != virusLib::PAGE_C)
continue;
- param->setValueFromSynth(value);
+ param->setValueFromSynth(value, true, pluginLib::Parameter::ChangedBy::PresetChange);
}
}
}
@@ -487,7 +487,7 @@ namespace Virus
DBG(juce::String::formatted("Set part: %d bank: %s param: %d value: %d", part, page == 0 ? "A" : "B", m.b, m.c));
const auto& params = findSynthParam(part, page, m.b);
for (const auto & p : params)
- p->setValueFromSynth(m.c, true);
+ p->setValueFromSynth(m.c, true, pluginLib::Parameter::ChangedBy::ControlChange);
}
void Controller::printMessage(const SysEx &msg)
diff --git a/source/jucePlugin/VirusParameterBinding.cpp b/source/jucePlugin/VirusParameterBinding.cpp
@@ -9,73 +9,13 @@ VirusParameterBinding::~VirusParameterBinding()
clearBindings();
}
-void VirusParameterBinding::clearBindings()
-{
- for (const auto b : m_bindings)
- {
- auto* slider = dynamic_cast<juce::Slider*>(b.component);
-
- if(slider != nullptr)
- removeMouseListener(*slider);
-
- if(b.onChangeListenerId)
- b.parameter->removeListener(b.onChangeListenerId);
- }
-
- m_bindings.clear();
-}
-
-void VirusParameterBinding::setPart(uint8_t _part)
-{
- m_processor.getController().setCurrentPart(_part);
-
- std::vector<BoundParameter> bindings;
- bindings.swap(m_bindings);
-
- for(size_t i=0; i<bindings.size(); ++i)
- {
- auto& b = bindings[i];
-
- if(b.part != CurrentPart)
- {
- m_bindings.push_back(b);
- continue;
- }
-
- const auto& desc = b.parameter->getDescription();
- const bool isNonPartExclusive = desc.isNonPartSensitive();
-
- if(isNonPartExclusive)
- continue;
-
- auto* slider = dynamic_cast<juce::Slider*>(b.component);
- if(slider)
- {
- bind(*slider, b.type);
- continue;
- }
- auto* button = dynamic_cast<juce::DrawableButton*>(b.component);
- if(button)
- {
- bind(*button, b.type);
- continue;
- }
- auto* comboBox = dynamic_cast<juce::ComboBox*>(b.component);
- if(comboBox)
- {
- bind(*comboBox, b.type);
- continue;
- }
- assert(false && "unknown component type");
- }
-}
void VirusParameterBinding::bind(juce::Slider &_slider, uint32_t _param)
{
bind(_slider, _param, CurrentPart);
}
void VirusParameterBinding::bind(juce::Slider &_slider, uint32_t _param, const uint8_t _part)
{
- const auto v = m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part);
+ const auto v = m_controller.getParameter(_param, _part == CurrentPart ? m_controller.getCurrentPart() : _part);
if (!v)
{
@@ -99,7 +39,7 @@ void VirusParameterBinding::bind(juce::Slider &_slider, uint32_t _param, const u
_slider.getProperties().set("bipolar", true);
}
const BoundParameter p{v, &_slider, _param, _part};
- m_bindings.emplace_back(p);
+ addBinding(p);
}
void VirusParameterBinding::bind(juce::ComboBox& _combo, uint32_t _param)
@@ -109,7 +49,7 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, uint32_t _param)
void VirusParameterBinding::bind(juce::ComboBox& _combo, const uint32_t _param, uint8_t _part)
{
- const auto v = m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part);
+ const auto v = m_controller.getParameter(_param, _part == CurrentPart ? m_controller.getCurrentPart() : _part);
if (!v)
{
assert(false && "Failed to find parameter");
@@ -137,15 +77,26 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, const uint32_t _param,
}
//_combo.addItemList(v->getAllValueStrings(), 1);
_combo.setSelectedId(static_cast<int>(v->getValueObject().getValueSource().getValue()) + 1, juce::dontSendNotification);
+
_combo.onChange = [this, &_combo, v]()
{
+ const auto id = _combo.getSelectedId();
+
+ if(id == 0)
+ return;
+
+ const int current = v->getValueObject().getValueSource().getValue();
+
+ if((id - 1) == current)
+ return;
+
if(v->getDescription().isPublic)
{
v->beginChangeGesture();
- v->setValueNotifyingHost(v->convertTo0to1(static_cast<float>(_combo.getSelectedId() - 1)));
+ v->setValueNotifyingHost(v->convertTo0to1(static_cast<float>(id - 1)));
v->endChangeGesture();
}
- v->getValueObject().getValueSource().setValue((int)_combo.getSelectedId() - 1);
+ v->getValueObject().setValue(id - 1);
};
const auto listenerId = m_nextListenerId++;
@@ -157,12 +108,12 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, const uint32_t _param,
}));
const BoundParameter p{v, &_combo, _param, _part, listenerId};
- m_bindings.emplace_back(p);
+ addBinding(p);
}
void VirusParameterBinding::bind(juce::Button &_btn, const uint32_t _param)
{
- const auto v = m_processor.getController().getParameter(_param, m_processor.getController().getCurrentPart());
+ const auto v = m_controller.getParameter(_param, m_controller.getCurrentPart());
if (!v)
{
assert(false && "Failed to find parameter");
@@ -170,7 +121,15 @@ void VirusParameterBinding::bind(juce::Button &_btn, const uint32_t _param)
}
_btn.getToggleStateValue().referTo(v->getValueObject());
const BoundParameter p{v, &_btn, _param, CurrentPart};
- m_bindings.emplace_back(p);
+ addBinding(p);
+}
+
+juce::Component* VirusParameterBinding::getBoundComponent(const pluginLib::Parameter* _parameter)
+{
+ const auto it = m_boundParameters.find(_parameter);
+ if(it == m_boundParameters.end())
+ return nullptr;
+ return it->second;
}
void VirusParameterBinding::removeMouseListener(juce::Slider& _slider)
@@ -184,3 +143,106 @@ void VirusParameterBinding::removeMouseListener(juce::Slider& _slider)
m_sliderMouseListeners.erase(it);
}
}
+
+void VirusParameterBinding::bind(const std::vector<BoundParameter>& _bindings, const bool _currentPartOnly)
+{
+ for (const auto& b : _bindings)
+ {
+ if(_currentPartOnly && b.part != CurrentPart)
+ {
+ addBinding(b);
+ continue;
+ }
+
+ const auto& desc = b.parameter->getDescription();
+ const bool isNonPartExclusive = desc.isNonPartSensitive();
+
+ if(isNonPartExclusive)
+ continue;
+
+ auto* slider = dynamic_cast<juce::Slider*>(b.component);
+ if(slider)
+ {
+ bind(*slider, b.type);
+ continue;
+ }
+ auto* button = dynamic_cast<juce::DrawableButton*>(b.component);
+ if(button)
+ {
+ bind(*button, b.type);
+ continue;
+ }
+ auto* comboBox = dynamic_cast<juce::ComboBox*>(b.component);
+ if(comboBox)
+ {
+ bind(*comboBox, b.type);
+ continue;
+ }
+ assert(false && "unknown component type");
+ }
+}
+
+void VirusParameterBinding::addBinding(const BoundParameter& _boundParameter)
+{
+ m_bindings.emplace_back(_boundParameter);
+
+ m_boundComponents.erase(_boundParameter.component);
+ m_boundParameters.erase(_boundParameter.parameter);
+
+ m_boundParameters.insert(std::make_pair(_boundParameter.parameter, _boundParameter.component));
+ m_boundComponents.insert(std::make_pair(_boundParameter.component, _boundParameter.parameter));
+}
+
+void VirusParameterBinding::disableBinding(const BoundParameter& _b)
+{
+ m_boundParameters.erase(_b.parameter);
+ m_boundComponents.erase(_b.component);
+
+ auto* slider = dynamic_cast<juce::Slider*>(_b.component);
+
+ if(slider != nullptr)
+ removeMouseListener(*slider);
+
+ auto* combo = dynamic_cast<juce::ComboBox*>(_b.component);
+ if(combo != nullptr)
+ combo->onChange = nullptr;
+
+ if(_b.onChangeListenerId)
+ _b.parameter->removeListener(_b.onChangeListenerId);
+}
+
+void VirusParameterBinding::clearBindings()
+{
+ for (const auto& b : m_bindings)
+ disableBinding(b);
+
+ m_bindings.clear();
+ m_boundParameters.clear();
+ m_boundComponents.clear();
+}
+
+void VirusParameterBinding::setPart(uint8_t _part)
+{
+ const std::vector<BoundParameter> bindings = m_bindings;
+
+ clearBindings();
+
+ m_controller.setCurrentPart(_part);
+
+ bind(bindings, true);
+}
+
+void VirusParameterBinding::disableBindings()
+{
+ m_disabledBindings.clear();
+ std::swap(m_bindings, m_disabledBindings);
+
+ for (const auto& b : m_disabledBindings)
+ disableBinding(b);
+}
+
+void VirusParameterBinding::enableBindings()
+{
+ bind(m_disabledBindings, false);
+ m_disabledBindings.clear();
+}
diff --git a/source/jucePlugin/VirusParameterBinding.h b/source/jucePlugin/VirusParameterBinding.h
@@ -2,7 +2,13 @@
#include "../jucePluginLib/parameter.h"
-namespace juce {
+namespace Virus
+{
+ class Controller;
+}
+
+namespace juce
+{
class Value;
}
@@ -23,36 +29,51 @@ public:
class VirusParameterBinding final : juce::MouseListener
{
public:
- VirusParameterBinding(AudioPluginAudioProcessor &_processor) : m_processor(_processor)
+ static constexpr uint8_t CurrentPart = 0xff;
+
+ struct BoundParameter
{
+ pluginLib::Parameter* parameter = nullptr;
+ juce::Component* component = nullptr;
+ uint32_t type = 0xffffffff;
+ uint8_t part = CurrentPart;
+ uint32_t onChangeListenerId = 0;
+ };
+ VirusParameterBinding(Virus::Controller& _controller) : m_controller(_controller)
+ {
}
~VirusParameterBinding() override;
- void clearBindings();
- void setPart(uint8_t _part);
+
void bind(juce::Slider& _control, uint32_t _param);
void bind(juce::Slider& _control, uint32_t _param, uint8_t _part);
void bind(juce::ComboBox &_control, uint32_t _param);
void bind(juce::ComboBox &_control, uint32_t _param, uint8_t _part);
void bind(juce::Button &_control, uint32_t _param);
+ void clearBindings();
+ void setPart(uint8_t _part);
+
+ void disableBindings();
+ void enableBindings();
+
+ const auto& getBindings() const { return m_bindings; }
+ juce::Component* getBoundComponent(const pluginLib::Parameter* _parameter);
+
private:
void removeMouseListener(juce::Slider& _slider);
- AudioPluginAudioProcessor& m_processor;
+ void addBinding(const BoundParameter& _boundParameter);
+ void disableBinding(const BoundParameter& _b);
- static constexpr uint8_t CurrentPart = 0xff;
+ Virus::Controller& m_controller;
- struct BoundParameter
- {
- pluginLib::Parameter* parameter = nullptr;
- juce::Component* component = nullptr;
- uint32_t type = 0xffffffff;
- uint8_t part = CurrentPart;
- uint32_t onChangeListenerId = 0;
- };
+ void bind(const std::vector<BoundParameter>& _bindings, bool _currentPartOnly);
std::vector<BoundParameter> m_bindings;
+ std::vector<BoundParameter> m_disabledBindings;
+ std::map<const pluginLib::Parameter*, juce::Component*> m_boundParameters;
+ std::map<const juce::Component*, pluginLib::Parameter*> m_boundComponents;
std::map<juce::Slider*, MouseListener*> m_sliderMouseListeners;
- uint32_t m_nextListenerId = 1;
+ uint32_t m_nextListenerId = 100000;
};
diff --git a/source/jucePlugin/parameterDescriptions_C.json b/source/jucePlugin/parameterDescriptions_C.json
@@ -375,6 +375,16 @@
{"page":114, "class":"Global", "index":126, "name":"LCD Contrast", "min":0, "max":127, "isPublic":false, "isDiscrete":false, "isBool":false},
{"page":114, "class":"Global", "index":127, "name":"Master Volume", "min":0, "max":127, "isPublic":false, "isDiscrete":false, "isBool":false}
],
+ "parameterlinks":
+ [
+ {
+ "source": "Cutoff",
+ "dest": "Cutoff2",
+ "conditionParameter": "Filter2 Cutoff Link",
+ "conditionValues": [1],
+ "link": "relative"
+ }
+ ],
"valuelists":
{
"unsignedZero":
diff --git a/source/jucePlugin/skins/Hoverland/VirusC_Hoverland.json b/source/jucePlugin/skins/Hoverland/VirusC_Hoverland.json
@@ -2483,7 +2483,7 @@
}
},
{
- "name" : "EnvVel",
+ "name" : "EnvVel1",
"parameterAttachment" : {
"parameter" : "Flt1 EnvAmt Velocity"
},
@@ -2621,7 +2621,7 @@
}
},
{
- "name" : "EnvVel",
+ "name" : "EnvVel2",
"parameterAttachment" : {
"parameter" : "Flt2 EnvAmt Velocity"
},
@@ -2773,7 +2773,13 @@
"texture" : "link_horizon_48x72_x2",
"tileSizeX" : "24",
"tileSizeY" : "72"
- }
+ },
+ "controllerlinks": [
+ { "source": "VelFlt1Freq", "dest": "VelFlt2Freq", "condition":"LinkEnv"},
+ { "source": "VelFlt2Freq", "dest": "VelFlt1Freq", "condition":"LinkEnv"},
+ { "source": "EnvVel1", "dest": "EnvVel2", "condition":"LinkEnv"},
+ { "source": "EnvVel2", "dest": "EnvVel1", "condition":"LinkEnv"}
+ ]
},
{
"name" : "Attack",
@@ -3230,7 +3236,11 @@
"texture" : "link_vert_72x48_x2",
"tileSizeX" : "72",
"tileSizeY" : "24"
- }
+ },
+ "controllerlinks": [
+ { "source": "LfoOsc1Pitch", "dest": "LfoOsc2Pitch", "condition":"Lfo1Link"},
+ { "source": "LfoOsc2Pitch", "dest": "LfoOsc1Pitch", "condition":"Lfo1Link"}
+ ]
},
{
"name" : "LfoShape",
@@ -3373,7 +3383,7 @@
}
},
{
- "name" : "LfoOsc1Pitch",
+ "name" : "Cutoff1Lfo2Amount",
"parameterAttachment" : {
"parameter" : "Cutoff1 Lfo2 Amount"
},
@@ -3390,7 +3400,7 @@
}
},
{
- "name" : "LfoOsc2Pitch",
+ "name" : "Cutoff2Lfo2Amount",
"parameterAttachment" : {
"parameter" : "Cutoff2 Lfo2 Amount"
},
@@ -3491,7 +3501,7 @@
}
},
{
- "name" : "Lfo1Link",
+ "name" : "Lfo2Link",
"button" : {
"isToggle" : "1",
"normalImage" : "0",
@@ -3505,7 +3515,11 @@
"texture" : "link_vert_72x48_x2",
"tileSizeX" : "72",
"tileSizeY" : "24"
- }
+ },
+ "controllerlinks": [
+ { "source": "Cutoff1Lfo2Amount", "dest": "Cutoff2Lfo2Amount", "condition":"Lfo2Link"},
+ { "source": "Cutoff2Lfo2Amount", "dest": "Cutoff1Lfo2Amount", "condition":"Lfo2Link"}
+ ]
},
{
"name" : "LfoShape",
diff --git a/source/jucePlugin/ui3/ControllerLinks.cpp b/source/jucePlugin/ui3/ControllerLinks.cpp
@@ -0,0 +1,62 @@
+#include "ControllerLinks.h"
+
+#include "VirusEditor.h"
+
+namespace genericVirusUI
+{
+ // The only purpose of this class is to provide backwards-compatibility with old skins that do not have their links expressed in the json description file
+ ControllerLinks::ControllerLinks(const VirusEditor& _editor)
+ {
+ std::vector<juce::Slider*> lfoSliderL, lfoSliderR, envVel;
+ std::vector<juce::Button*> lfoToggles;
+
+ _editor.findComponents(lfoSliderL, "LfoOsc1Pitch");
+ _editor.findComponents(lfoSliderR, "LfoOsc2Pitch");
+
+ _editor.findComponents(lfoToggles, "Lfo1Link");
+ if(lfoToggles.empty())
+ _editor.findComponents(lfoToggles, "LfoOscPitchLink");
+
+ _editor.findComponents(envVel, "EnvVel");
+ if(envVel.empty())
+ _editor.findComponents(envVel, "EnvVelo");
+
+ auto* linkEnv = _editor.findComponentT<juce::Button>("LinkEnv", false);
+
+ auto* flt1Vel = _editor.findComponentT<juce::Slider>("VelFlt1Freq", false);
+ auto* flt2Vel = _editor.findComponentT<juce::Slider>("VelFlt2Freq", false);
+
+ if(lfoSliderL.size() == 2 && lfoSliderR.size() == 2 && lfoToggles.size() == 2)
+ {
+ for(size_t i=0; i<2; ++i)
+ createLink(lfoSliderL[i], lfoSliderR[i], lfoToggles[i]);
+ }
+
+ if(linkEnv)
+ {
+ if(envVel.size() == 2)
+ createLink(envVel[0], envVel[1], linkEnv);
+
+ if(flt1Vel && flt2Vel)
+ createLink(flt1Vel, flt2Vel, linkEnv);
+ }
+
+ auto* vocModQ = _editor.findComponentT<juce::Slider>("VocModQ", false);
+ auto* vocCarrSpread = _editor.findComponentT<juce::Slider>("VocCarrSpread", false);
+ auto* vocLink = _editor.findComponentT<juce::Button>("VocoderLink", false);
+
+ if(vocModQ && vocCarrSpread && vocLink)
+ createLink(vocModQ, vocCarrSpread, vocLink);
+ }
+
+ void ControllerLinks::createLink(juce::Slider* _a, juce::Slider* _b, juce::Button* _cond)
+ {
+ auto* link = new genericUI::ControllerLink();
+ link->create(_a, _b, _cond);
+ m_links.emplace_back(link);
+
+ link = new genericUI::ControllerLink();
+ link->create(_b, _a, _cond);
+ m_links.emplace_back(link);
+ }
+}
diff --git a/source/jucePlugin/ui3/ControllerLinks.h b/source/jucePlugin/ui3/ControllerLinks.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+
+namespace juce
+{
+ class Button;
+ class Slider;
+}
+
+namespace genericUI
+{
+ class ControllerLink;
+}
+
+namespace genericVirusUI
+{
+ class VirusEditor;
+
+ class ControllerLinks
+ {
+ public:
+ ControllerLinks(const VirusEditor& _editor);
+
+ private:
+ void createLink(juce::Slider* _a, juce::Slider* _b, juce::Button* _cond);
+
+ std::vector<std::unique_ptr<genericUI::ControllerLink>> m_links;
+ };
+}
diff --git a/source/jucePlugin/ui3/Tabs.h b/source/jucePlugin/ui3/Tabs.h
@@ -1,7 +1,5 @@
#pragma once
-#include <vector>
-
#include "../../juceUiLib/tabgroup.h"
namespace juce
diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp
@@ -28,6 +28,10 @@ namespace genericVirusUI
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 MidiPorts(*this));
// be backwards compatible with old skins
@@ -119,10 +123,30 @@ namespace genericVirusUI
updatePresetName();
updatePlayModeButtons();
updateControlLabel(nullptr);
+
+ for (auto& params : getController().getExposedParameters())
+ {
+ for (const auto& param : params.second)
+ {
+ m_boundParameters.push_back(param);
+
+ param->onValueChanged.emplace_back(1, [this, param]()
+ {
+ if(param->getChangeOrigin() == pluginLib::Parameter::ChangedBy::PresetChange)
+ return;
+ auto* comp = m_parameterBinding.getBoundComponent(param);
+ if(comp)
+ updateControlLabel(comp);
+ });
+ }
+ }
}
VirusEditor::~VirusEditor()
{
+ for (auto* p : m_boundParameters)
+ p->removeListener(1);
+
m_parameterBinding.clearBindings();
getController().onProgramChange = nullptr;
@@ -257,33 +281,21 @@ namespace genericVirusUI
updatePresetName();
}
- void VirusEditor::mouseDrag(const juce::MouseEvent & event)
- {
- updateControlLabel(event.eventComponent);
- }
-
void VirusEditor::mouseEnter(const juce::MouseEvent& event)
{
- if (event.mouseWasDraggedSinceMouseDown())
- return;
- updateControlLabel(event.eventComponent);
- }
- void VirusEditor::mouseExit(const juce::MouseEvent& event)
- {
- if (event.mouseWasDraggedSinceMouseDown())
- return;
- updateControlLabel(nullptr);
+ if(event.eventComponent && event.eventComponent->getProperties().contains("parameter"))
+ updateControlLabel(event.eventComponent);
}
- void VirusEditor::mouseUp(const juce::MouseEvent& event)
+ void VirusEditor::timerCallback()
{
- if (event.mouseWasDraggedSinceMouseDown())
- return;
- updateControlLabel(event.eventComponent);
+ updateControlLabel(nullptr);
}
- void VirusEditor::updateControlLabel(juce::Component* _component) const
+ void VirusEditor::updateControlLabel(juce::Component* _component)
{
+ stopTimer();
+
if(_component)
{
// combo boxes report the child label as event source, try the parent in this case
@@ -326,7 +338,7 @@ namespace genericVirusUI
m_focusedParameterName->setVisible(true);
m_focusedParameterValue->setVisible(true);
- if(m_focusedParameterTooltip && dynamic_cast<juce::Slider*>(_component))
+ if(m_focusedParameterTooltip && dynamic_cast<juce::Slider*>(_component) && _component->isShowing())
{
int x = _component->getX();
int y = _component->getY();
@@ -362,6 +374,12 @@ namespace genericVirusUI
m_focusedParameterTooltip->setVisible(true);
m_focusedParameterTooltip->toFront(false);
}
+ else if(m_focusedParameterTooltip)
+ {
+ m_focusedParameterTooltip->setVisible(false);
+ }
+
+ startTimer(3000);
}
void VirusEditor::updatePresetName() const
@@ -433,14 +451,14 @@ namespace genericVirusUI
}
juce::PopupMenu banksMenu;
- for(auto b=0; b<getController().getBankCount(); ++b)
+ for(uint8_t b=0; b<static_cast<uint8_t>(getController().getBankCount()); ++b)
{
std::stringstream bankName;
bankName << "Bank " << static_cast<char>('A' + b);
addEntry(banksMenu, bankName.str(), [this, b](const FileType _type)
{
- savePresets(SaveType::Bank, _type, static_cast<uint8_t>(b));
+ savePresets(SaveType::Bank, _type, b);
});
}
@@ -501,7 +519,7 @@ namespace genericVirusUI
{
const auto playMode = getController().getParameterIndexByName(Virus::g_paramPlayMode);
- getController().getParameter(playMode)->setValue(_playMode);
+ getController().getParameter(playMode)->setValue(_playMode, pluginLib::Parameter::ChangedBy::Ui);
if (_playMode == virusLib::PlayModeSingle && getController().getCurrentPart() != 0)
m_parameterBinding.setPart(0);
diff --git a/source/jucePlugin/ui3/VirusEditor.h b/source/jucePlugin/ui3/VirusEditor.h
@@ -7,13 +7,19 @@
#include "FxPage.h"
#include "MidiPorts.h"
#include "PatchBrowser.h"
+#include "ControllerLinks.h"
+
+namespace pluginLib
+{
+ class Parameter;
+}
class VirusParameterBinding;
class AudioPluginAudioProcessor;
namespace genericVirusUI
{
- class VirusEditor : public genericUI::EditorInterface, public genericUI::Editor
+ class VirusEditor : public genericUI::EditorInterface, public genericUI::Editor, juce::Timer
{
public:
enum class FileType
@@ -56,12 +62,10 @@ namespace genericVirusUI
void onPlayModeChanged();
void onCurrentPartChanged();
- void mouseDrag(const juce::MouseEvent& event) override;
void mouseEnter(const juce::MouseEvent& event) override;
- void mouseExit(const juce::MouseEvent& event) override;
- void mouseUp(const juce::MouseEvent& event) override;
+ void timerCallback() override;
- void updateControlLabel(juce::Component* _component) const;
+ void updateControlLabel(juce::Component* _component);
void updatePresetName() const;
void updatePlayModeButtons() const;
@@ -85,6 +89,7 @@ namespace genericVirusUI
std::unique_ptr<MidiPorts> m_midiPorts;
std::unique_ptr<FxPage> m_fxPage;
std::unique_ptr<PatchBrowser> m_patchBrowser;
+ std::unique_ptr<ControllerLinks> m_controllerLinks;
juce::Label* m_presetName = nullptr;
juce::Label* m_focusedParameterName = nullptr;
@@ -103,6 +108,7 @@ namespace genericVirusUI
std::unique_ptr<juce::FileChooser> m_fileChooser;
juce::String m_previousPath;
std::function<void()> m_openMenuCallback;
+ std::vector<pluginLib::Parameter*> m_boundParameters;
std::map<std::string, std::vector<char>> m_fileCache;
};
diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt
@@ -7,6 +7,7 @@ set(SOURCES
parameter.cpp parameter.h
parameterdescription.cpp parameterdescription.h
parameterdescriptions.cpp parameterdescriptions.h
+ parameterlink.cpp parameterlink.h
)
add_library(jucePluginLib STATIC)
diff --git a/source/jucePluginLib/controller.cpp b/source/jucePluginLib/controller.cpp
@@ -44,7 +44,7 @@ namespace pluginLib
const auto& existingParams = findSynthParam(idx);
for (auto& existingParam : existingParams)
- existingParam->addLinkedParameter(p.get());
+ existingParam->addDerivedParameter(p.get());
}
const bool isNonPartExclusive = desc.isNonPartSensitive();
diff --git a/source/jucePluginLib/controller.h b/source/jucePluginLib/controller.h
@@ -30,6 +30,8 @@ namespace pluginLib
bool parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const;
bool parseMidiPacket(std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const;
+ const auto& getExposedParameters() { return m_synthParams; }
+
protected:
virtual Parameter* createParameter(Controller& _controller, const Description& _desc, uint8_t _part, int _uid);
void registerParams(juce::AudioProcessor& _processor);
diff --git a/source/jucePluginLib/parameter.cpp b/source/jucePluginLib/parameter.cpp
@@ -30,7 +30,7 @@ namespace pluginLib
func.second();
}
- void Parameter::setLinkedValue(const int _value)
+ void Parameter::setDerivedValue(const int _value, ChangedBy _origin)
{
const int newValue = juce::roundToInt(m_range.getRange().clipValue(static_cast<float>(_value)));
@@ -38,11 +38,14 @@ namespace pluginLib
return;
m_lastValue = newValue;
+ m_lastValueOrigin = _origin;
if(getDescription().isPublic)
{
beginChangeGesture();
- setValueNotifyingHost(convertTo0to1(static_cast<float>(newValue)));
+ const float v = convertTo0to1(static_cast<float>(newValue));
+ setValue(v, _origin);
+ sendValueChangedMessageToListeners(v);
endChangeGesture();
}
else
@@ -53,28 +56,34 @@ namespace pluginLib
bool Parameter::isMetaParameter() const
{
- return !m_linkedParameters.empty();
+ return !m_derivedParameters.empty();
}
- void Parameter::setValue(float newValue)
+ void Parameter::setValue(const float _newValue)
{
- if (m_changingLinkedValues)
+ setValue(_newValue, ChangedBy::HostAutomation);
+ }
+
+ void Parameter::setValue(const float _newValue, const ChangedBy _origin)
+ {
+ if (m_changingDerivedValues)
return;
- m_value.setValue(convertFrom0to1(newValue));
+ m_lastValueOrigin = _origin;
+ m_value.setValue(convertFrom0to1(_newValue));
- m_changingLinkedValues = true;
+ m_changingDerivedValues = true;
- for (const auto& parameter : m_linkedParameters)
+ for (const auto& parameter : m_derivedParameters)
{
- if(!parameter->m_changingLinkedValues)
- parameter->setLinkedValue(m_value.getValue());
+ if(!parameter->m_changingDerivedValues)
+ parameter->setDerivedValue(m_value.getValue(), _origin);
}
- m_changingLinkedValues = false;
- }
+ m_changingDerivedValues = false;
+ }
- void Parameter::setValueFromSynth(int newValue, const bool notifyHost)
+ void Parameter::setValueFromSynth(int newValue, const bool notifyHost, ChangedBy _origin)
{
const auto clampedValue = juce::roundToInt(m_range.getRange().clipValue(static_cast<float>(newValue)));
@@ -82,11 +91,14 @@ namespace pluginLib
return;
m_lastValue = clampedValue;
+ m_lastValueOrigin = _origin;
if (notifyHost && getDescription().isPublic)
{
beginChangeGesture();
- setValueNotifyingHost(convertTo0to1(static_cast<float>(clampedValue)));
+ const auto v = convertTo0to1(static_cast<float>(clampedValue));
+ setValue(v, _origin);
+ sendValueChangedMessageToListeners(v);
endChangeGesture();
}
else
@@ -94,15 +106,15 @@ namespace pluginLib
m_value.setValue(clampedValue);
}
- if (m_changingLinkedValues)
+ if (m_changingDerivedValues)
return;
- m_changingLinkedValues = true;
+ m_changingDerivedValues = true;
- for (const auto& p : m_linkedParameters)
- p->setLinkedValue(newValue);
+ for (const auto& p : m_derivedParameters)
+ p->setDerivedValue(newValue, _origin);
- m_changingLinkedValues = false;
+ m_changingDerivedValues = false;
}
bool Parameter::removeListener(const uint32_t _id)
@@ -137,18 +149,18 @@ namespace pluginLib
return 0;
}
- void Parameter::addLinkedParameter(Parameter* _param)
+ void Parameter::addDerivedParameter(Parameter* _param)
{
if (_param == this)
return;
- for (auto* p : m_linkedParameters)
+ for (auto* p : m_derivedParameters)
{
- _param->m_linkedParameters.insert(p);
- p->m_linkedParameters.insert(_param);
+ _param->m_derivedParameters.insert(p);
+ p->m_derivedParameters.insert(_param);
}
- m_linkedParameters.insert(_param);
- _param->m_linkedParameters.insert(this);
+ m_derivedParameters.insert(_param);
+ _param->m_derivedParameters.insert(this);
}
}
diff --git a/source/jucePluginLib/parameter.h b/source/jucePluginLib/parameter.h
@@ -15,6 +15,15 @@ namespace pluginLib
class Parameter : juce::Value::Listener, public juce::RangedAudioParameter
{
public:
+ enum class ChangedBy
+ {
+ Unknown,
+ PresetChange,
+ ControlChange,
+ HostAutomation,
+ Ui,
+ };
+
Parameter(Controller& _controller, const Description& _desc, uint8_t _partNum, int uniqueId);
juce::Value &getValueObject() { return m_value; };
@@ -29,8 +38,9 @@ namespace pluginLib
bool isMetaParameter() const override;
float getValue() const override { return convertTo0to1(m_value.getValue()); }
- void setValue(float newValue) override;
- void setValueFromSynth(int newValue, bool notifyHost = true);
+ void setValue(float _newValue) override;
+ void setValue(float _newValue, ChangedBy _origin);
+ void setValueFromSynth(int newValue, bool notifyHost, ChangedBy _origin);
bool isDiscrete() const override { return m_desc.isDiscrete; }
bool isBoolean() const override { return m_desc.isBool; }
@@ -59,18 +69,20 @@ namespace pluginLib
// eg. multi/single value change requires triggering more logic.
std::list<std::pair<uint32_t, std::function<void()>>> onValueChanged;
- void addLinkedParameter(Parameter* _param);
+ void addDerivedParameter(Parameter* _param);
int getUniqueId() const { return m_uniqueId; }
- const std::set<Parameter*>& getLinkedParameters() { return m_linkedParameters; }
+ const std::set<Parameter*>& getDerivedParameters() { return m_derivedParameters; }
bool removeListener(uint32_t _id);
+ ChangedBy getChangeOrigin() const { return m_lastValueOrigin; }
+
private:
static juce::String genId(const Description &d, int part, int uniqueId);
void valueChanged(juce::Value &) override;
- void setLinkedValue(int _value);
+ void setDerivedValue(int _value, ChangedBy _origin);
Controller &m_ctrl;
const Description m_desc;
@@ -78,8 +90,9 @@ namespace pluginLib
const uint8_t m_partNum;
const int m_uniqueId; // 0 for all unique parameters, > 0 if multiple Parameter instances reference a single synth parameter
int m_lastValue{-1};
+ ChangedBy m_lastValueOrigin = ChangedBy::Unknown;
juce::Value m_value;
- std::set<Parameter*> m_linkedParameters;
- bool m_changingLinkedValues = false;
+ std::set<Parameter*> m_derivedParameters;
+ bool m_changingDerivedValues = false;
};
}
diff --git a/source/jucePluginLib/parameterdescriptions.cpp b/source/jucePluginLib/parameterdescriptions.cpp
@@ -317,9 +317,11 @@ namespace pluginLib
}
const auto midipackets = json["midipackets"].getDynamicObject();
-
parseMidiPackets(errors, midipackets);
+ const auto parameterLinks = json["parameterlinks"].getArray();
+ parseParameterLinks(errors, parameterLinks);
+
auto res = errors.str();
if(!res.empty())
@@ -515,4 +517,91 @@ namespace pluginLib
if(!hasErrors)
m_midiPackets.insert(std::make_pair(_key, packet));
}
+
+ void ParameterDescriptions::parseParameterLinks(std::stringstream& _errors, juce::Array<juce::var>* _links)
+ {
+ if(!_links)
+ return;
+
+ for(int i=0; i<_links->size(); ++i)
+ {
+ const auto& value = (*_links)[i];
+ parseParameterLink(_errors, value);
+ }
+ }
+
+ void ParameterDescriptions::parseParameterLink(std::stringstream& _errors, const juce::var& _value)
+ {
+ const auto source = _value["source"].toString().toStdString();
+ const auto dest = _value["dest"].toString().toStdString();
+ const auto conditionParam = _value["conditionParameter"].toString().toStdString();
+ const auto conditionValues = _value["conditionValues"].getArray();
+ const auto linkType = _value["link"].toString().toStdString();
+
+ ParameterLink link;
+
+ if(!getIndexByName(link.source, source))
+ {
+ _errors << "Source parameter " << source << " not found for parameter link" << std::endl;
+ return;
+ }
+
+ if(!getIndexByName(link.dest, dest))
+ {
+ _errors << "Destination parameter " << dest << " not found for parameter link" << std::endl;
+ return;
+ }
+
+ if(link.source == link.dest)
+ {
+ _errors << "Cannot link source parameter " << source << " to itself" << std::endl;
+ return;
+ }
+
+ if(linkType == "relative")
+ {
+ link.mode = ParameterLink::LinkMode::Relative;
+ }
+ else if(linkType == "absolute")
+ {
+ link.mode = ParameterLink::LinkMode::Absolute;
+ }
+ else
+ {
+ _errors << "Parameter link mode " << linkType << " is not a value value, must be either relative or absolute" << std::endl;
+ return;
+ }
+
+ if(conditionParam.empty())
+ {
+ if(conditionValues && conditionValues->size() > 1)
+ {
+ _errors << "Condition parameter not specified for conditional parameter link of " << source << " to " << dest << ". Either specify no condition parameter and no values, or specify both" << std::endl;
+ return;
+ }
+ }
+
+ if(!conditionParam.empty())
+ {
+ if(conditionValues == nullptr || conditionValues->isEmpty())
+ {
+ _errors << "Condition parameter list is empty or invalid although the condition parameter " << conditionParam << " has been specified for parameter link of " << source << " to " << dest << ". Either specify no condition parameter and no values, or specify both" << std::endl;
+ return;
+ }
+
+ if(!getIndexByName(link.conditionParameter, conditionParam))
+ {
+ _errors << "Link condition parameter " << conditionParam << " not found for parameter link" << std::endl;
+ return;
+ }
+
+ for(auto i=0; i<conditionValues->size(); ++i)
+ {
+ const int v = (*conditionValues)[i];
+ link.conditionValues.insert(static_cast<uint8_t>(v));
+ }
+ }
+
+ m_parameterLinks.push_back(link);
+ }
}
diff --git a/source/jucePluginLib/parameterdescriptions.h b/source/jucePluginLib/parameterdescriptions.h
@@ -6,6 +6,7 @@
#include "midipacket.h"
#include "parameterdescription.h"
+#include "parameterlink.h"
namespace pluginLib
{
@@ -32,9 +33,13 @@ namespace pluginLib
void parseMidiPackets(std::stringstream& _errors, juce::DynamicObject* _packets);
void parseMidiPacket(std::stringstream& _errors, const std::string& _key, const juce::var& _value);
+ void parseParameterLinks(std::stringstream& _errors, juce::Array<juce::var>* _links);
+ void parseParameterLink(std::stringstream& _errors, const juce::var& _value);
+
std::map<std::string, ValueList> m_valueLists;
std::vector<Description> m_descriptions;
std::map<std::string, uint32_t> m_nameToIndex;
std::map<std::string, MidiPacket> m_midiPackets;
+ std::vector<ParameterLink> m_parameterLinks;
};
}
diff --git a/source/jucePluginLib/parameterlink.cpp b/source/jucePluginLib/parameterlink.cpp
diff --git a/source/jucePluginLib/parameterlink.h b/source/jucePluginLib/parameterlink.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <set>
+#include <vector>
+
+namespace pluginLib
+{
+ struct ParameterLink
+ {
+ enum class LinkMode
+ {
+ Absolute,
+ Relative
+ };
+
+ bool isValid() const
+ {
+ return source != dest;
+ }
+
+ bool hasConditions() const
+ {
+ return !conditionValues.empty();
+ }
+
+ uint32_t source = 0;
+ uint32_t dest = 0;
+
+ uint32_t conditionParameter;
+ std::set<uint8_t> conditionValues;
+
+ LinkMode mode = LinkMode::Absolute;
+ };
+}
diff --git a/source/juceUiLib/CMakeLists.txt b/source/juceUiLib/CMakeLists.txt
@@ -5,6 +5,7 @@ set(SOURCES
editor.cpp editor.h
editorInterface.h
condition.cpp condition.h
+ controllerlink.cpp controllerlink.h
image.cpp image.h
rotaryStyle.cpp rotaryStyle.h
comboboxStyle.cpp comboboxStyle.h
diff --git a/source/juceUiLib/controllerlink.cpp b/source/juceUiLib/controllerlink.cpp
@@ -0,0 +1,82 @@
+#include "controllerlink.h"
+
+#include "editor.h"
+
+namespace genericUI
+{
+ ControllerLink::ControllerLink(std::string _source, std::string _dest, std::string _conditionParam)
+ : m_sourceName(std::move(_source))
+ , m_destName(std::move(_dest))
+ , m_conditionParam(std::move(_conditionParam))
+ {
+ }
+
+ ControllerLink::~ControllerLink()
+ {
+ if(m_source)
+ {
+ m_source->removeListener(this);
+ m_source->removeComponentListener(this);
+ }
+ }
+
+ void ControllerLink::create(const Editor& _editor)
+ {
+ create(
+ _editor.findComponentT<juce::Slider>(m_sourceName),
+ _editor.findComponentT<juce::Slider>(m_destName),
+ _editor.findComponentT<juce::Button>(m_conditionParam)
+ );
+ }
+
+ void ControllerLink::create(juce::Slider* _source, juce::Slider* _dest, juce::Button* _condition)
+ {
+ m_source = _source;
+ m_dest = _dest;
+ m_condition = _condition;
+
+ m_lastSourceValue = m_source->getValue();
+ m_source->addListener(this);
+ m_source->addComponentListener(this);
+ }
+
+ void ControllerLink::sliderValueChanged(juce::Slider* _slider)
+ {
+ const auto current = m_source->getValue();
+ const auto delta = current - m_lastSourceValue;
+ m_lastSourceValue = current;
+
+ if(!m_sourceIsBeingDragged)
+ return;
+
+ if(std::abs(delta) < 0.0001)
+ return;
+
+ if(m_condition && !m_condition->getToggleState())
+ return;
+
+ const auto destValue = m_dest->getValue();
+ const auto newDestValue = destValue + delta;
+
+ if(std::abs(newDestValue - destValue) < 0.0001)
+ return;
+
+ m_dest->setValue(newDestValue);
+ }
+
+ void ControllerLink::sliderDragStarted(juce::Slider*)
+ {
+ m_lastSourceValue = m_source->getValue();
+ m_sourceIsBeingDragged = true;
+ }
+
+ void ControllerLink::sliderDragEnded(juce::Slider*)
+ {
+ m_sourceIsBeingDragged = false;
+ }
+
+ void ControllerLink::componentBeingDeleted(juce::Component& component)
+ {
+ m_source = nullptr;
+ }
+}
diff --git a/source/juceUiLib/controllerlink.h b/source/juceUiLib/controllerlink.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <string>
+
+#include <juce_audio_processors/juce_audio_processors.h>
+
+namespace genericUI
+{
+ class Editor;
+
+ class ControllerLink : juce::Slider::Listener, juce::ComponentListener
+ {
+ public:
+ ControllerLink(ControllerLink&&) = delete;
+ ControllerLink(const ControllerLink&) = delete;
+
+ ControllerLink(std::string _source = std::string(), std::string _dest = std::string(), std::string _conditionParam = std::string());
+ ~ControllerLink() override;
+
+ void create(const Editor& _editor);
+
+ void create(juce::Slider* _source, juce::Slider* _dest, juce::Button* _condition);
+
+ void operator = (const ControllerLink&) = delete;
+ void operator = (ControllerLink&&) = delete;
+
+ private:
+ void sliderValueChanged(juce::Slider* _slider) override;
+ void sliderDragStarted(juce::Slider*) override;
+ void sliderDragEnded(juce::Slider*) override;
+
+ void componentBeingDeleted(juce::Component& component) override;
+
+ const std::string m_sourceName;
+ const std::string m_destName;
+ const std::string m_conditionParam;
+
+ juce::Slider* m_source = nullptr;
+ juce::Slider* m_dest = nullptr;
+ juce::Button* m_condition = nullptr;
+ double m_lastSourceValue = 0;
+ bool m_sourceIsBeingDragged = false;
+ };
+}
diff --git a/source/juceUiLib/editor.cpp b/source/juceUiLib/editor.cpp
@@ -57,6 +57,7 @@ namespace genericUI
m_rootObject->createJuceTree(*this);
m_rootObject->createTabGroups(*this);
+ m_rootObject->createControllerLinks(*this);
m_scale = m_rootObject->getPropertyFloat("scale", 1.0f);
}
@@ -207,6 +208,11 @@ namespace genericUI
return m_rootObject ? m_rootObject->getConditionCountRecursive() : 0;
}
+ size_t Editor::getControllerLinkCountRecursive() const
+ {
+ return m_rootObject ? m_rootObject->getControllerLinkCountRecursive() : 0;
+ }
+
void Editor::setEnabled(juce::Component& _component, const bool _enable)
{
if(_component.getProperties().contains("disabledAlpha"))
diff --git a/source/juceUiLib/editor.h b/source/juceUiLib/editor.h
@@ -73,6 +73,7 @@ namespace genericUI
}
size_t getConditionCountRecursive() const;
+ size_t getControllerLinkCountRecursive() const;
static void setEnabled(juce::Component& _component, bool _enable);
diff --git a/source/juceUiLib/uiObject.cpp b/source/juceUiLib/uiObject.cpp
@@ -64,6 +64,17 @@ namespace genericUI
}
}
+ void UiObject::createControllerLinks(Editor& _editor)
+ {
+ for (auto& link : m_controllerLinks)
+ link->create(_editor);
+
+ for (auto& ch : m_children)
+ {
+ ch->createControllerLinks(_editor);
+ }
+ }
+
void UiObject::apply(Editor& _editor, juce::Component& _target)
{
const auto x = getPropertyInt("x");
@@ -252,6 +263,16 @@ namespace genericUI
return count;
}
+ size_t UiObject::getControllerLinkCountRecursive() const
+ {
+ size_t count = m_controllerLinks.size();
+
+ for (const auto & c : m_children)
+ count += c->getControllerLinkCountRecursive();
+
+ return count;
+ }
+
void UiObject::createCondition(Editor& _editor, juce::Component& _target)
{
if(!hasComponent("condition"))
@@ -359,6 +380,30 @@ namespace genericUI
m_tabGroup = TabGroup(name, pagesVec, buttonVec);
}
+ else if(key == "controllerlinks")
+ {
+ auto* entries = value.getArray();
+
+ if(entries && !entries->isEmpty())
+ {
+ for(auto j=0; j<entries->size(); ++j)
+ {
+ const auto& e = (*entries)[j];
+ const auto source = e["source"].toString().toStdString();
+ const auto dest = e["dest"].toString().toStdString();
+ const auto condition = e["condition"].toString().toStdString();
+
+ if(source.empty())
+ throw std::runtime_error("source for controller link needs to have a name");
+ if(dest.empty())
+ throw std::runtime_error("destination for controller link needs to have a name");
+ if(condition.empty())
+ throw std::runtime_error("condition for controller link needs to have a name");
+
+ m_controllerLinks.emplace_back(new ControllerLink(source, dest, condition));
+ }
+ }
+ }
else
{
auto* componentDesc = value.getDynamicObject();
diff --git a/source/juceUiLib/uiObject.h b/source/juceUiLib/uiObject.h
@@ -7,6 +7,7 @@
#include <vector>
#include "condition.h"
+#include "controllerlink.h"
#include "tabgroup.h"
namespace juce
@@ -38,6 +39,7 @@ namespace genericUI
void createJuceTree(Editor& _editor);
void createChildObjects(Editor& _editor, juce::Component& _parent) const;
void createTabGroups(Editor& _editor);
+ void createControllerLinks(Editor& _editor);
void apply(Editor& _editor, juce::Component& _target);
void apply(Editor& _editor, juce::Slider& _target);
@@ -56,6 +58,7 @@ namespace genericUI
std::string getProperty(const std::string& _key, const std::string& _default = std::string()) const;
size_t getConditionCountRecursive() const;
+ size_t getControllerLinkCountRecursive() const;
private:
bool hasComponent(const std::string& _component) const;
@@ -82,6 +85,7 @@ namespace genericUI
std::unique_ptr<Condition> m_condition;
TabGroup m_tabGroup;
+ std::vector<std::unique_ptr<ControllerLink>> m_controllerLinks;
};
inline bool UiObject::hasComponent(const std::string& _component) const
diff --git a/source/juceUiLib/uiObjectStyle.cpp b/source/juceUiLib/uiObjectStyle.cpp
@@ -122,15 +122,9 @@ namespace genericUI
juce::Font UiObjectStyle::getPopupMenuFont()
{
- if(!m_fontFile.empty())
- {
- auto font = juce::Font(m_editor.getFont(m_fontFile).getTypeface());
- applyFontProperties(font);
- return font;
- }
- auto font = LookAndFeel_V4::getPopupMenuFont();
- applyFontProperties(font);
- return font;
+ auto f = LookAndFeel_V4::getPopupMenuFont();
+ f.setHeight(f.getHeight() * 2.0f);
+ return f;
}
juce::Font UiObjectStyle::getTextButtonFont(juce::TextButton& _textButton, int buttonHeight)
@@ -146,6 +140,16 @@ namespace genericUI
return font;
}
+ bool UiObjectStyle::shouldPopupMenuScaleWithTargetComponent(const juce::PopupMenu::Options& _options)
+ {
+ return true;
+ }
+
+ juce::PopupMenu::Options UiObjectStyle::getOptionsForComboBoxPopupMenu(juce::ComboBox& _comboBox, juce::Label& _label)
+ {
+ return LookAndFeel_V4::getOptionsForComboBoxPopupMenu(_comboBox, _label).withStandardItemHeight(0);
+ }
+
void UiObjectStyle::applyFontProperties(juce::Font& _font) const
{
if(m_textHeight)
diff --git a/source/juceUiLib/uiObjectStyle.h b/source/juceUiLib/uiObjectStyle.h
@@ -23,6 +23,8 @@ namespace genericUI
juce::Font getLabelFont(juce::Label&) override;
juce::Font getPopupMenuFont() override;
juce::Font getTextButtonFont(juce::TextButton&, int buttonHeight) override;
+ bool shouldPopupMenuScaleWithTargetComponent (const juce::PopupMenu::Options&) override;
+ juce::PopupMenu::Options getOptionsForComboBoxPopupMenu(juce::ComboBox&, juce::Label&) override;
void applyFontProperties(juce::Font& _font) const;
diff --git a/source/virusLib/hdi08TxParser.cpp b/source/virusLib/hdi08TxParser.cpp
@@ -13,7 +13,7 @@ namespace virusLib
{
const std::vector<dsp56k::TWord> g_knownPatterns[] =
{
- {0xf40000, 0x7f0000, 0x000000, 0xf70000} // sent after DSP has booted
+ {0xf40000, 0x7f0000} // sent after DSP has booted
};
bool Hdi08TxParser::append(const dsp56k::TWord _data)