commit 0a305af6395bb17a44908b243d7cfc1bbcd98fc8 parent faf1e32fcc4567bff6eed09a737f064a4cfe6020 Author: dsp56300 <87139854+dsp56300@users.noreply.github.com> Date: Sat, 8 Jan 2022 13:30:37 +0100 Merge pull request #31 from 790/ui Wire up new ui Diffstat:
27 files changed, 1183 insertions(+), 425 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") -project(gearmulator VERSION 1.2.1) +project(gearmulator VERSION 1.2.2) include(base.cmake) diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -40,6 +40,7 @@ PRIVATE ui/Virus_FxEditor.cpp ui/Virus_LfoEditor.cpp ui/Virus_OscEditor.cpp + ui/Virus_PatchBrowser.cpp ui/Virus_Buttons.h ui/Virus_LookAndFeel.h ui/VirusEditor.h @@ -47,6 +48,7 @@ PRIVATE ui/Virus_FxEditor.h ui/Virus_LfoEditor.h ui/Virus_OscEditor.h + ui/Virus_PatchBrowser.h ui/Ui_Utils.h VirusController.cpp VirusController.h @@ -70,6 +72,7 @@ juce_add_binary_data(jucePlugin_BinaryData "assets/buttons/GLOBAL_btn_effects_141x26.png" "assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png" "assets/buttons/GLOBAL_btn_osc_filter_141x26.png" + "assets/buttons/GLOBAL_btn_patch_browser_141x26.png" "assets/buttons/env_pol_50x34.png" "assets/buttons/lfo_btn_23_19.png" "assets/buttons/link_vert_12x36.png" @@ -77,6 +80,8 @@ juce_add_binary_data(jucePlugin_BinaryData "assets/buttons/presets_btn_43_15.png" "assets/buttons/Handle_18x47.png" "assets/buttons/sync2_54x25.png" + "assets/buttons/part_select_btn_39x72.png" + "assets/buttons/arphold_btn_28_22.png" "assets/knobs/Gen_70x70_100.png" "assets/knobs/Gen_pol_70x70_100.png" "assets/knobs/GenBlue_70x70_100.png" diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp @@ -1,225 +1,19 @@ #include "PluginProcessor.h" #include "PluginEditor.h" - +#include "VirusController.h" #include "version.h" #include "ui/VirusEditor.h" //============================================================================== AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) : - AudioProcessorEditor(&p), processorRef(p), m_btSingleMode("Single Mode"), m_btMultiMode("Multi Mode"), - m_parameterBinding(p), - m_btLoadFile("Load bank"), m_cmbMidiInput("Midi Input"), m_cmbMidiOutput("Midi Output"), - m_tempEditor(p) + AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p), m_virusEditor(new VirusEditor(m_parameterBinding, processorRef)) { ignoreUnused (processorRef); - juce::PropertiesFile::Options opts; - opts.applicationName = "DSP56300 Emulator"; - opts.filenameSuffix = ".settings"; - opts.folderName = "DSP56300 Emulator"; - opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; - m_properties = new juce::PropertiesFile(opts); - - // Make sure that before the constructor has finished, you've set the - // editor's size to whatever you need it to be. - setSize(800, 800); - - // Resizable UI - setResizable(true, true); - setResizeLimits(800,400,800,1600); - - m_btSingleMode.setRadioGroupId(0x3cf); - m_btMultiMode.setRadioGroupId(0x3cf); - addAndMakeVisible(m_btSingleMode); - addAndMakeVisible(m_btMultiMode); - m_btSingleMode.setTopLeftPosition(0,0); - m_btSingleMode.setSize(120,30); - m_btMultiMode.getToggleStateValue().referTo(*processorRef.getController().getParamValue(Virus::Param_PlayMode)); - const auto isMulti = processorRef.getController().isMultiMode(); - m_btSingleMode.setToggleState(!isMulti, juce::dontSendNotification); - m_btMultiMode.setToggleState(isMulti, juce::dontSendNotification); - m_btSingleMode.setClickingTogglesState(true); - m_btMultiMode.setClickingTogglesState(true); - m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, m_btSingleMode.getY()); - m_btMultiMode.setSize(120,30); - - addAndMakeVisible(m_btLoadFile); - m_btLoadFile.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + m_btMultiMode.getWidth() + 10, m_btSingleMode.getY()); - m_btLoadFile.setSize(120, 30); - m_btLoadFile.onClick = [this]() { - loadFile(); - }; - - for (uint8_t pt = 0; pt < 16; pt++) - { - m_partSelectors[pt].onClick = [this, pt]() { - - juce::PopupMenu selector; - - for(uint8_t b=0; b<processorRef.getController().getBankCount(); ++b) - { - const auto bank = virusLib::fromArrayIndex(b); - auto presetNames = processorRef.getController().getSinglePresetNames(bank); - juce::PopupMenu p; - for (uint8_t i = 0; i < 128; i++) - { - p.addItem(presetNames[i], [this, bank, i, pt] { processorRef.getController().setCurrentPartPreset(pt, bank, i); }); - } - std::stringstream bankName; - bankName << "Bank " << static_cast<char>('A' + b); - selector.addSubMenu(std::string(bankName.str()), p); - } - selector.showMenu(juce::PopupMenu::Options()); - }; - m_partSelectors[pt].setSize(m_partSelectors[pt].getWidth() - 48, m_partSelectors[pt].getHeight()); - m_partSelectors[pt].setTopLeftPosition(m_partSelectors[pt].getPosition() + juce::Point(24, 0)); - addAndMakeVisible(m_partSelectors[pt]); - - m_prevPatch[pt].setSize(24, m_partSelectors[pt].getHeight()); - m_nextPatch[pt].setSize(24, m_partSelectors[pt].getHeight()); - m_prevPatch[pt].setTopLeftPosition(m_partSelectors[pt].getPosition() - juce::Point(24, 0)); - m_nextPatch[pt].setTopLeftPosition(m_partSelectors[pt].getPosition() + juce::Point(m_partSelectors[pt].getWidth(), 0)); - m_prevPatch[pt].setButtonText("<"); - m_nextPatch[pt].setButtonText(">"); - m_prevPatch[pt].onClick = [this, pt]() { - processorRef.getController().setCurrentPartPreset(pt, processorRef.getController().getCurrentPartBank(pt), - std::max(0, processorRef.getController().getCurrentPartProgram(pt) - 1)); - }; - m_nextPatch[pt].onClick = [this, pt]() { - processorRef.getController().setCurrentPartPreset(pt, processorRef.getController().getCurrentPartBank(pt), - std::min(127, processorRef.getController().getCurrentPartProgram(pt) + 1)); - }; - addAndMakeVisible(m_prevPatch[pt]); - addAndMakeVisible(m_nextPatch[pt]); - } - - auto midiIn = m_properties->getValue("midi_input", ""); - auto midiOut = m_properties->getValue("midi_output", ""); - if (midiIn != "") - { - processorRef.setMidiInput(midiIn); - } - if (midiOut != "") - { - processorRef.setMidiOutput(midiOut); - } - - m_cmbMidiInput.setSize(160, 30); - m_cmbMidiInput.setTopLeftPosition(0, 400); - m_cmbMidiOutput.setSize(160, 30); - m_cmbMidiOutput.setTopLeftPosition(164, 400); - addAndMakeVisible(m_cmbMidiInput); - addAndMakeVisible(m_cmbMidiOutput); - m_cmbMidiInput.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled"); - auto midiInputs = juce::MidiInput::getAvailableDevices(); - juce::StringArray midiInputNames; - midiInputNames.add(" - Midi In - "); - auto inIndex = 0; - for (int i = 0; i < midiInputs.size(); i++) - { - const auto input = midiInputs[i]; - if (processorRef.getMidiInput() != nullptr && input.identifier == processorRef.getMidiInput()->getIdentifier()) - { - inIndex = i + 1; - } - midiInputNames.add(input.name); - } - m_cmbMidiInput.addItemList(midiInputNames, 1); - m_cmbMidiInput.setSelectedItemIndex(inIndex, juce::dontSendNotification); - m_cmbMidiOutput.setTextWhenNoChoicesAvailable("No MIDI Outputs Enabled"); - auto midiOutputs = juce::MidiOutput::getAvailableDevices(); - juce::StringArray midiOutputNames; - midiOutputNames.add(" - Midi Out - "); - auto outIndex = 0; - for (int i = 0; i < midiOutputs.size(); i++) - { - const auto output = midiOutputs[i]; - if (processorRef.getMidiOutput() != nullptr && output.identifier == processorRef.getMidiOutput()->getIdentifier()) - { - outIndex = i+1; - } - midiOutputNames.add(output.name); - } - m_cmbMidiOutput.addItemList(midiOutputNames, 1); - m_cmbMidiOutput.setSelectedItemIndex(outIndex, juce::dontSendNotification); - m_cmbMidiInput.onChange = [this]() { updateMidiInput(m_cmbMidiInput.getSelectedItemIndex()); }; - m_cmbMidiOutput.onChange = [this]() { updateMidiOutput(m_cmbMidiOutput.getSelectedItemIndex()); }; - - addAndMakeVisible(m_tempEditor); - - startTimerHz(5); - - m_openEditor.setButtonText("Show Editor"); - m_openEditor.setTopLeftPosition(0, 500); - m_openEditor.onClick = [this]() - { - m_virusEditor.reset(new juce::ResizableWindow("VirusEditor", true)); - m_virusEditor->setTopLeftPosition(0, 0); - m_virusEditor->setUsingNativeTitleBar(true); - m_virusEditor->setVisible(true); - m_virusEditor->setResizable(true, false); - m_virusEditor->setContentOwned(new VirusEditor(m_parameterBinding), true); - }; - addAndMakeVisible(m_openEditor); -} -void AudioPluginAudioProcessorEditor::updateMidiInput(int index) -{ - auto list = juce::MidiInput::getAvailableDevices(); - - if (index == 0) - { - m_properties->setValue("midi_input", ""); - m_properties->save(); - m_lastInputIndex = index; - m_cmbMidiInput.setSelectedItemIndex(index, juce::dontSendNotification); - return; - } - index--; - auto newInput = list[index]; - - if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier)) - deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true); - - if (!processorRef.setMidiInput(newInput.identifier)) - { - m_cmbMidiInput.setSelectedItemIndex(0, juce::dontSendNotification); - m_lastInputIndex = 0; - return; - } - - m_properties->setValue("midi_input", newInput.identifier); - m_properties->save(); - - m_cmbMidiInput.setSelectedItemIndex(index+1, juce::dontSendNotification); - m_lastInputIndex = index; -} -void AudioPluginAudioProcessorEditor::updateMidiOutput(int index) -{ - auto list = juce::MidiOutput::getAvailableDevices(); - - if (index == 0) - { - m_properties->setValue("midi_output", ""); - m_properties->save(); - m_cmbMidiOutput.setSelectedItemIndex(index, juce::dontSendNotification); - m_lastOutputIndex = index; - processorRef.setMidiOutput(""); - return; - } - index--; - auto newOutput = list[index]; - if(!processorRef.setMidiOutput(newOutput.identifier)) - { - m_cmbMidiOutput.setSelectedItemIndex(0, juce::dontSendNotification); - m_lastOutputIndex = 0; - return; - } - m_properties->setValue("midi_output", newOutput.identifier); - m_properties->save(); - - m_cmbMidiOutput.setSelectedItemIndex(index+1, juce::dontSendNotification); - m_lastOutputIndex = index; + setSize(1377, 800); + m_virusEditor->setTopLeftPosition(0, 0); + addAndMakeVisible(m_virusEditor); } AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() @@ -229,36 +23,11 @@ AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() //============================================================================== void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g) { - // (Our component is opaque, so we must completely fill the background with a solid colour) - g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); - - g.setColour (juce::Colours::white); - g.setFont (15.0f); - - std::string message = "DSP 56300 Emulator\nVersion " + std::string(g_pluginVersionString) + "\n" __DATE__ " " __TIME__; - - if(!processorRef.isPluginValid()) - message += "\n\nNo ROM, no sound!\nCopy ROM next to plugin, must end with .bin"; - - g.drawFittedText(message, getLocalBounds().removeFromLeft(400).removeFromBottom(45), juce::Justification::centred, - 2); - g.drawFittedText("To donate: paypal.me/dsp56300", getLocalBounds().removeFromRight(400).removeFromTop(35), - juce::Justification::centred, 2); } void AudioPluginAudioProcessorEditor::timerCallback() { // ugly (polling!) way for refreshing presets names as this is temporary ui - const auto multiMode = processorRef.getController().isMultiMode(); - for (auto pt = 0; pt < 16; pt++) - { - bool singlePartOrInMulti = pt == 0 || multiMode; - m_partSelectors[pt].setVisible(singlePartOrInMulti); - m_prevPatch[pt].setVisible(singlePartOrInMulti); - m_nextPatch[pt].setVisible(singlePartOrInMulti); - if (singlePartOrInMulti) - m_partSelectors[pt].setButtonText(processorRef.getController().getCurrentPartPresetName(pt)); - } } void AudioPluginAudioProcessorEditor::resized() @@ -267,84 +36,4 @@ void AudioPluginAudioProcessorEditor::resized() // subcomponents in your editor.. auto area = getLocalBounds(); area.removeFromTop(35); - m_tempEditor.setBounds(area.removeFromRight(400)); - for (auto pt = 0; pt < 16; pt++) - { - m_partSelectors[pt].setBounds(area.removeFromTop(20)); - } - - m_openEditor.setBounds(0, 0, 80, 20); -} - -void AudioPluginAudioProcessorEditor::loadFile() { - juce::FileChooser chooser("Choose syx/midi banks to import", - m_previousPath.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : m_previousPath, - "*.syx,*.mid,*.midi", - true); - - if (!chooser.browseForFileToOpen()) - return; - bool sentData = false; - const auto result = chooser.getResult(); - m_previousPath = result.getParentDirectory().getFullPathName(); - const auto ext = result.getFileExtension().toLowerCase(); - if (ext == ".syx") - { - juce::MemoryBlock data; - result.loadFileAsData(data); - for (auto it = data.begin(); it != data.end(); it += 267) - { - if ((it + 267) < data.end()) - { - processorRef.getController().sendSysEx(Virus::SysEx(it, it + 267)); - sentData = true; - } - } - m_btLoadFile.setButtonText("Loaded"); - } - else if (ext == ".mid" || ext == ".midi") - { - juce::MemoryBlock data; - if (!result.loadFileAsData(data)) - { - return; - } - const uint8_t *ptr = (uint8_t *)data.getData(); - const auto end = ptr + data.getSize(); - - for (auto it = ptr; it < end; it += 1) - { - if ((uint8_t)*it == (uint8_t)0xf0 && (it+267) < end) - { - if ((uint8_t) *(it + 1) == (uint8_t)0x00) - { - auto syx = Virus::SysEx(it, it + 267); - syx[7] = 0x01; // force to bank a - syx[266] = 0xf7; - - processorRef.getController().sendSysEx(syx); - - it += 266; - } - else // some midi files have two bytes after the 0xf0 - { - auto syx = Virus::SysEx(); - syx.push_back(0xf0); - for (auto i = it + 3; i < it + 3 + 266; i++) - { - syx.push_back((uint8_t)*i); - } - syx[7] = 0x01; // force to bank a - syx[266] = 0xf7; - processorRef.getController().sendSysEx(syx); - it += 266; - } - - sentData = true; - } - } - } - - if (sentData) - processorRef.getController().onStateLoaded(); } diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h @@ -1,6 +1,7 @@ #pragma once #include "VirusParameterBinding.h" +#include "ui/VirusEditor.h" #include "PluginProcessor.h" #include <juce_audio_devices/juce_audio_devices.h> //============================================================================== @@ -16,34 +17,15 @@ public: void resized() override; private: - void updateMidiInput(int index); - void updateMidiOutput(int index); void timerCallback() override; - void loadFile(); // This reference is provided as a quick way for your editor to // access the processor object that created it. AudioPluginAudioProcessor& processorRef; VirusParameterBinding m_parameterBinding; - juce::GenericAudioProcessorEditor m_tempEditor; - juce::TextButton m_partSelectors[16]; - juce::TextButton m_prevPatch[16]; - juce::TextButton m_nextPatch[16]; - juce::TextButton m_btSingleMode; - juce::TextButton m_btMultiMode; - juce::TextButton m_btLoadFile; - juce::String m_previousPath; - juce::ComboBox m_cmbMidiInput; - juce::ComboBox m_cmbMidiOutput; - - juce::AudioDeviceManager deviceManager; - juce::PropertiesFile *m_properties; - int m_lastInputIndex = 0; - int m_lastOutputIndex = 0; // New "real" editor - juce::TextButton m_openEditor; // temporary until integrated - will be rebased! - std::unique_ptr<juce::ResizableWindow> m_virusEditor; + VirusEditor *m_virusEditor; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -181,6 +181,18 @@ namespace Virus return findSynthParam(index); } + Parameter *Controller::getParameter(const ParameterType _param, const uint8_t _part) + { + const auto it = m_paramTypeToParamIndex.find(_param); + if (it == m_paramTypeToParamIndex.end()) + return nullptr; + + const auto &index = it->second; + + ParamIndex paramIndex{index.page, _part, index.paramNum}; + return findSynthParam(paramIndex); + } + void Controller::parseParamChange(const SysEx &msg) { const auto pos = kHeaderWithMsgCodeLen - 1; @@ -510,6 +522,19 @@ namespace Virus } } + juce::String numToFilter2Mode(float idx, Parameter::Description) + { + const auto ridx = juce::roundToInt(idx); + switch (ridx) + { + case 0: return "LowPass"; + case 1: return "HighPass"; + case 2: return "BandPass"; + case 3: return "BandStop"; + default: return juce::String(idx); + } + } + juce::String numToFilterRouting(float idx, Parameter::Description) { const auto ridx = juce::roundToInt(idx); @@ -1343,7 +1368,7 @@ namespace Virus {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 48, "Filter Balance", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 49, "Saturation Curve", {0,14}, numToSatCurv, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 51, "Filter1 Mode", {0,7}, numToFilterMode, {}, true, true, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 52, "Filter2 Mode", {0,7}, numToFilterMode, {}, true, true, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 52, "Filter2 Mode", {0,3}, numToFilter2Mode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 53, "Filter Routing", {0,3}, numToFilterRouting, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 54, "Filter Env Attack", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 55, "Filter Env Decay", {0,127}, {},{}, true, false, false}, diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -120,7 +120,7 @@ namespace Virus Param_ChorusDelay, Param_ChorusFeedback, Param_ChorusLfoShape, - Param_DelayReverMode, + Param_DelayReverbMode, Param_EffectSend, Param_DelayTime, Param_DelayFeedback, @@ -381,6 +381,11 @@ namespace Virus Param_Count }; + enum EnvelopeType + { + Env_Amp, + Env_Filter + }; using SysEx = std::vector<uint8_t>; class Controller : private juce::Timer { @@ -402,6 +407,7 @@ namespace Virus juce::Value *getParamValue(uint8_t ch, uint8_t bank, uint8_t paramIndex); juce::Value *getParamValue(ParameterType _param); Parameter* getParameter(ParameterType _param); + Parameter *getParameter(ParameterType _param, uint8_t _part); // bank - 0-1 (AB) juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; diff --git a/source/jucePlugin/VirusParameterBinding.cpp b/source/jucePlugin/VirusParameterBinding.cpp @@ -1,10 +1,20 @@ #include "VirusParameterBinding.h" - +#include "VirusParameter.h" #include "PluginProcessor.h" -void VirusParameterBinding::bind(juce::Slider& _slider, Virus::ParameterType _param) const +void VirusParameterBinding::setPart(uint8_t _part) { + m_part = _part; + + for (const auto b : m_bindings) + { + b->onValueChanged = nullptr; + } + m_bindings.clear(); + +} +void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param) { - const auto v = m_processor.getController().getParameter(_param); + const auto v = m_processor.getController().getParameter(_param, m_part); if (!v) { assert(false && "Failed to find parameter"); @@ -15,3 +25,45 @@ void VirusParameterBinding::bind(juce::Slider& _slider, Virus::ParameterType _pa _slider.setDoubleClickReturnValue(true, v->getDefaultValue()); _slider.getValueObject().referTo(v->getValueObject()); } + +void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param) +{ + const auto v = m_processor.getController().getParameter(_param, m_part); + if (!v) + { + assert(false && "Failed to find parameter"); + return; + } + _combo.setTextWhenNothingSelected("--"); + _combo.addItemList(v->getAllValueStrings(), 1); + _combo.setSelectedItemIndex(v->getValueObject().getValueSource().getValue()); + _combo.onChange = [this, &_combo, v]() { + //v->setValue(_combo.getSelectedId() - 1); + v->getValueObject().getValueSource().setValue(_combo.getSelectedItemIndex()); + }; + + v->onValueChanged = [this, &_combo, v]() { _combo.setSelectedItemIndex(v->getValueObject().getValueSource().getValue(), juce::NotificationType::dontSendNotification); }; + m_bindings.add(v); +} + +void VirusParameterBinding::bind(juce::DrawableButton &_btn, Virus::ParameterType _param) +{ + const auto v = m_processor.getController().getParameter(_param, m_part); + if (!v) + { + assert(false && "Failed to find parameter"); + return; + } + _btn.getToggleStateValue().referTo(v->getValueObject()); +} + +void VirusParameterBinding::bind(juce::Component &_btn, Virus::ParameterType _param) +{ + const auto v = m_processor.getController().getParameter(_param, m_part); + if (!v) + { + assert(false && "Failed to find parameter"); + return; + } + +} diff --git a/source/jucePlugin/VirusParameterBinding.h b/source/jucePlugin/VirusParameterBinding.h @@ -1,6 +1,6 @@ #pragma once #include "VirusController.h" - +#include "VirusParameter.h" namespace juce { class Value; } @@ -10,11 +10,16 @@ class AudioPluginAudioProcessor; class VirusParameterBinding { public: - VirusParameterBinding(AudioPluginAudioProcessor& _processor) : m_processor(_processor) + VirusParameterBinding(AudioPluginAudioProcessor& _processor, uint8_t _part = 0) : m_processor(_processor) { + m_part = _part; } - - void bind(juce::Slider& _control, Virus::ParameterType _param) const; - + void setPart(uint8_t _part); + void bind(juce::Slider& _control, Virus::ParameterType _param); + void bind(juce::ComboBox &_control, Virus::ParameterType _param); + void bind(juce::DrawableButton &_control, Virus::ParameterType _param); + void bind(juce::Component &_control, Virus::ParameterType _param); AudioPluginAudioProcessor& m_processor; + uint8_t m_part; + juce::Array<Virus::Parameter*> m_bindings; }; diff --git a/source/jucePlugin/assets/buttons/GLOBAL_btn_patch_browser_141x26.png b/source/jucePlugin/assets/buttons/GLOBAL_btn_patch_browser_141x26.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/arphold_btn_28_22.png b/source/jucePlugin/assets/buttons/arphold_btn_28_22.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/part_select_btn_39x72.png b/source/jucePlugin/assets/buttons/part_select_btn_39x72.png Binary files differ. diff --git a/source/jucePlugin/ui/VirusEditor.cpp b/source/jucePlugin/ui/VirusEditor.cpp @@ -1,17 +1,21 @@ #include "VirusEditor.h" #include "BinaryData.h" - +#include "../version.h" #include "Virus_ArpEditor.h" #include "Virus_FxEditor.h" #include "Virus_LfoEditor.h" #include "Virus_OscEditor.h" +#include "Virus_PatchBrowser.h" +#include "../VirusParameterBinding.h" +#include "../VirusController.h" using namespace juce; constexpr auto kPanelWidth = 1377; constexpr auto kPanelHeight = 800; -VirusEditor::VirusEditor(VirusParameterBinding& _parameterBinding) : m_parameterBinding(_parameterBinding) +VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAudioProcessor &_processorRef) : + m_parameterBinding(_parameterBinding), processorRef(_processorRef), m_controller(processorRef.getController()), m_btSingleMode("Single Mode"), m_btMultiMode("Multi Mode") { setLookAndFeel(&m_lookAndFeel); @@ -25,6 +29,7 @@ VirusEditor::VirusEditor(VirusParameterBinding& _parameterBinding) : m_parameter m_fxEditor = std::make_unique<FxEditor>(_parameterBinding); m_lfoEditor = std::make_unique<LfoEditor>(_parameterBinding); m_oscEditor = std::make_unique<OscEditor>(_parameterBinding); + m_patchBrowser = std::make_unique<PatchBrowser>(_parameterBinding, m_controller); applyToSections([this](Component *s) { addChildComponent(s); }); @@ -34,19 +39,281 @@ VirusEditor::VirusEditor(VirusParameterBinding& _parameterBinding) : m_parameter m_fxEditor->setVisible(m_mainButtons.m_effects.getToggleState()); m_lfoEditor->setVisible(m_mainButtons.m_lfoMatrix.getToggleState()); m_oscEditor->setVisible(m_mainButtons.m_oscFilter.getToggleState()); + m_patchBrowser->setVisible(m_mainButtons.m_patches.getToggleState()); }; - + m_oscEditor->setVisible(true); + m_mainButtons.m_oscFilter.setToggleState(true, NotificationType::dontSendNotification); addAndMakeVisible(m_presetButtons); + m_presetButtons.m_load.onClick = [this]() { loadFile(); }; + m_presetButtons.m_save.onClick = [this]() { saveFile(); }; + + for (auto pt = 0; pt < 16; pt++) + { + m_partLabels[pt].setBounds(34, 161 + pt * (36), 24, 36); + m_partLabels[pt].setText(juce::String(pt + 1), juce::dontSendNotification); + m_partLabels[pt].setColour(0, juce::Colours::white); + m_partLabels[pt].setColour(1, juce::Colour(45, 24, 24)); + m_partLabels[pt].setJustificationType(Justification::centred); + addAndMakeVisible(m_partLabels[pt]); + + m_partSelect[pt].setBounds(34, 161 + pt*(36), 39, 36); + m_partSelect[pt].setButtonText(juce::String(pt)); + m_partSelect[pt].setRadioGroupId(kPartGroupId); + m_partSelect[pt].setClickingTogglesState(true); + m_partSelect[pt].onClick = [this, pt]() { + this->changePart(pt); + }; + addAndMakeVisible(m_partSelect[pt]); + + m_presetNames[pt].setBounds(80, 171 + pt * (36) - 2, 136, 16 + 4); + m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); + m_presetNames[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255,113,128)); + m_presetNames[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); + //m_presetNames[pt].setColour(0, juce::Colours::white); + + m_presetNames[pt].onClick = [this, pt]() { + juce::PopupMenu selector; + + for (uint8_t b = 0; b < m_controller.getBankCount(); ++b) + { + const auto bank = virusLib::fromArrayIndex(b); + auto presetNames = m_controller.getSinglePresetNames(bank); + juce::PopupMenu p; + for (uint8_t j = 0; j < 128; j++) + { + const auto presetName = presetNames[j]; + p.addItem(presetNames[j], [this, bank, j, pt, presetName] { + m_controller.setCurrentPartPreset(pt, bank, j); + m_presetNames[pt].setButtonText(presetName); + }); + } + std::stringstream bankName; + bankName << "Bank " << static_cast<char>('A' + b); + selector.addSubMenu(std::string(bankName.str()), p); + } + selector.showMenu(juce::PopupMenu::Options()); + }; + addAndMakeVisible(m_presetNames[pt]); + + m_prevPatch[pt].setBounds(228, 173 + 36*pt - 2, 16, 14); + m_nextPatch[pt].setBounds(247, 173 + 36*pt - 2, 16, 14); + m_prevPatch[pt].setButtonText("<"); + m_nextPatch[pt].setButtonText(">"); + m_prevPatch[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255, 113, 128)); + m_nextPatch[pt].setColour(juce::TextButton::ColourIds::textColourOnId, juce::Colour(255, 113, 128)); + m_prevPatch[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); + m_nextPatch[pt].setColour(juce::TextButton::ColourIds::textColourOffId, juce::Colour(255, 113, 128)); + m_prevPatch[pt].onClick = [this, pt]() { + m_controller.setCurrentPartPreset( + pt, m_controller.getCurrentPartBank(pt), + std::max(0, m_controller.getCurrentPartProgram(pt) - 1)); + }; + m_nextPatch[pt].onClick = [this, pt]() { + m_controller.setCurrentPartPreset( + pt, m_controller.getCurrentPartBank(pt), + std::min(127, m_controller.getCurrentPartProgram(pt) + 1)); + }; + addAndMakeVisible(m_prevPatch[pt]); + addAndMakeVisible(m_nextPatch[pt]); + } + m_partSelect[0].setToggleState(true, NotificationType::sendNotification); + + m_btSingleMode.setRadioGroupId(0x3cf); + m_btMultiMode.setRadioGroupId(0x3cf); + addAndMakeVisible(m_btSingleMode); + addAndMakeVisible(m_btMultiMode); + m_btSingleMode.setTopLeftPosition(102, 756); + m_btSingleMode.setSize(100, 30); + //m_btMultiMode.getToggleStateValue().referTo(*m_controller.getParamValue(Virus::Param_PlayMode)); + const auto isMulti = m_controller.isMultiMode(); + m_btSingleMode.setToggleState(!isMulti, juce::dontSendNotification); + m_btMultiMode.setToggleState(isMulti, juce::dontSendNotification); + m_btSingleMode.setClickingTogglesState(true); + m_btMultiMode.setClickingTogglesState(true); + m_btSingleMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); + m_btSingleMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); + m_btMultiMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); + m_btMultiMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); + m_btSingleMode.onClick = [this]() { setPlayMode(0); }; + m_btMultiMode.onClick = [this]() { setPlayMode(1); }; + + m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, + m_btSingleMode.getY()); + m_btMultiMode.setSize(100, 30); + + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300 Emulator"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300 Emulator"; + opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; + m_properties = new juce::PropertiesFile(opts); + auto midiIn = m_properties->getValue("midi_input", ""); + auto midiOut = m_properties->getValue("midi_output", ""); + if (midiIn != "") + { + processorRef.setMidiInput(midiIn); + } + if (midiOut != "") + { + processorRef.setMidiOutput(midiOut); + } + + m_cmbMidiInput.setSize(160, 30); + m_cmbMidiInput.setTopLeftPosition(350, 760); + m_cmbMidiOutput.setSize(160, 30); + m_cmbMidiOutput.setTopLeftPosition(350+164, 760); + addAndMakeVisible(m_cmbMidiInput); + addAndMakeVisible(m_cmbMidiOutput); + m_cmbMidiInput.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled"); + auto midiInputs = juce::MidiInput::getAvailableDevices(); + juce::StringArray midiInputNames; + midiInputNames.add(" - Midi In - "); + auto inIndex = 0; + for (int i = 0; i < midiInputs.size(); i++) + { + const auto input = midiInputs[i]; + if (processorRef.getMidiInput() != nullptr && input.identifier == processorRef.getMidiInput()->getIdentifier()) + { + inIndex = i + 1; + } + midiInputNames.add(input.name); + } + m_cmbMidiInput.addItemList(midiInputNames, 1); + m_cmbMidiInput.setSelectedItemIndex(inIndex, juce::dontSendNotification); + m_cmbMidiOutput.setTextWhenNoChoicesAvailable("No MIDI Outputs Enabled"); + auto midiOutputs = juce::MidiOutput::getAvailableDevices(); + juce::StringArray midiOutputNames; + midiOutputNames.add(" - Midi Out - "); + auto outIndex = 0; + for (int i = 0; i < midiOutputs.size(); i++) + { + const auto output = midiOutputs[i]; + if (processorRef.getMidiOutput() != nullptr && + output.identifier == processorRef.getMidiOutput()->getIdentifier()) + { + outIndex = i + 1; + } + midiOutputNames.add(output.name); + } + m_cmbMidiOutput.addItemList(midiOutputNames, 1); + m_cmbMidiOutput.setSelectedItemIndex(outIndex, juce::dontSendNotification); + m_cmbMidiInput.onChange = [this]() { updateMidiInput(m_cmbMidiInput.getSelectedItemIndex()); }; + m_cmbMidiOutput.onChange = [this]() { updateMidiOutput(m_cmbMidiOutput.getSelectedItemIndex()); }; + + std::string message = + "DSP 56300 Emulator\nVersion " + std::string(g_pluginVersionString) + "\n" __DATE__ " " __TIME__; + if (!processorRef.isPluginValid()) + message += "\n\nNo ROM, no sound!\nCopy ROM next to plugin, must end with .bin"; + message += "\n\nTo donate: paypal.me/dsp56300"; + m_version.setText(message, NotificationType::dontSendNotification); + m_version.setBounds(94, 2, 220, 150); + m_version.setColour(juce::Label::textColourId, juce::Colours::white); + m_version.setJustificationType(Justification::centred); + addAndMakeVisible(m_version); + + m_patchName.setBounds(410, 48, 362, 36); + m_patchName.setJustificationType(Justification::left); + m_patchName.setFont(juce::Font(32.0f, juce::Font::bold)); + m_patchName.setColour(juce::Label::textColourId, juce::Colour(255, 113, 128)); + addAndMakeVisible(m_patchName); + startTimerHz(5); setSize (kPanelWidth, kPanelHeight); } VirusEditor::~VirusEditor() { setLookAndFeel(nullptr); } +void VirusEditor::timerCallback() +{ + // ugly (polling!) way for refreshing presets names as this is temporary ui + const auto multiMode = m_controller.isMultiMode(); + for (auto pt = 0; pt < 16; pt++) + { + bool singlePartOrInMulti = pt == 0 || multiMode; + m_presetNames[pt].setVisible(singlePartOrInMulti); + m_prevPatch[pt].setVisible(singlePartOrInMulti); + m_nextPatch[pt].setVisible(singlePartOrInMulti); + if (singlePartOrInMulti) + m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); + if (pt == m_parameterBinding.m_part) + { + m_patchName.setText(m_controller.getCurrentPartPresetName(pt), + NotificationType::dontSendNotification); + } + } + +} + +void VirusEditor::updateMidiInput(int index) +{ + auto list = juce::MidiInput::getAvailableDevices(); + + if (index == 0) + { + m_properties->setValue("midi_input", ""); + m_properties->save(); + m_lastInputIndex = index; + m_cmbMidiInput.setSelectedItemIndex(index, juce::dontSendNotification); + return; + } + index--; + auto newInput = list[index]; + + if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier)) + deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true); + + if (!processorRef.setMidiInput(newInput.identifier)) + { + m_cmbMidiInput.setSelectedItemIndex(0, juce::dontSendNotification); + m_lastInputIndex = 0; + return; + } + + m_properties->setValue("midi_input", newInput.identifier); + m_properties->save(); + + m_cmbMidiInput.setSelectedItemIndex(index + 1, juce::dontSendNotification); + m_lastInputIndex = index; +} +void VirusEditor::updateMidiOutput(int index) +{ + auto list = juce::MidiOutput::getAvailableDevices(); + + if (index == 0) + { + m_properties->setValue("midi_output", ""); + m_properties->save(); + m_cmbMidiOutput.setSelectedItemIndex(index, juce::dontSendNotification); + m_lastOutputIndex = index; + processorRef.setMidiOutput(""); + return; + } + index--; + auto newOutput = list[index]; + if (!processorRef.setMidiOutput(newOutput.identifier)) + { + m_cmbMidiOutput.setSelectedItemIndex(0, juce::dontSendNotification); + m_lastOutputIndex = 0; + return; + } + m_properties->setValue("midi_output", newOutput.identifier); + m_properties->save(); + + m_cmbMidiOutput.setSelectedItemIndex(index + 1, juce::dontSendNotification); + m_lastOutputIndex = index; +} +void VirusEditor::updatePartsPresetNames() +{ + for (auto i = 0; i < 16; i++) + { + m_presetNames[i].setButtonText(m_controller.getCurrentPartPresetName(i)); + } +} void VirusEditor::applyToSections(std::function<void(Component *)> action) { for (auto *section : {static_cast<Component *>(m_arpEditor.get()), static_cast<Component *>(m_fxEditor.get()), - static_cast<Component *>(m_lfoEditor.get()), static_cast<Component *>(m_oscEditor.get())}) + static_cast<Component *>(m_lfoEditor.get()), static_cast<Component *>(m_oscEditor.get()), + static_cast<Component *>(m_patchBrowser.get())}) { action(section); } @@ -61,20 +328,58 @@ void VirusEditor::resized() applyToSections([this](Component *s) { s->setTopLeftPosition(338, 133); }); } +void VirusEditor::setPlayMode(uint8_t _mode) { + m_controller.getParameter(Virus::Param_PlayMode)->setValue(_mode); + changePart(0); +} + +void VirusEditor::changePart(uint8_t _part) +{ + for (auto &p : m_partSelect) + { + p.setToggleState(false, juce::dontSendNotification); + } + m_partSelect[_part].setToggleState(true, juce::dontSendNotification); + m_parameterBinding.setPart(_part); + + removeChildComponent(m_oscEditor.get()); + removeChildComponent(m_lfoEditor.get()); + removeChildComponent(m_fxEditor.get()); + removeChildComponent(m_arpEditor.get()); + + m_oscEditor = std::make_unique<OscEditor>(m_parameterBinding); + addChildComponent(m_oscEditor.get()); + + m_lfoEditor = std::make_unique<LfoEditor>(m_parameterBinding); + addChildComponent(m_lfoEditor.get()); + + m_fxEditor = std::make_unique<FxEditor>(m_parameterBinding); + addChildComponent(m_fxEditor.get()); + + m_arpEditor = std::make_unique<ArpEditor>(m_parameterBinding); + addChildComponent(m_arpEditor.get()); + + m_mainButtons.updateSection(); + resized(); +} + VirusEditor::MainButtons::MainButtons() : m_oscFilter ("OSC|FILTER", DrawableButton::ImageRaw) , m_lfoMatrix ("LFO|MATRIX", DrawableButton::ImageRaw) , m_effects ("EFFECTS", DrawableButton::ImageRaw) , m_arpSettings ("ARP|SETTINGS", DrawableButton::ImageRaw) +, m_patches("PATCHES", DrawableButton::ImageRaw) { - constexpr auto numOfMainButtons = 4; + constexpr auto numOfMainButtons = 5; setupButton (0, Drawable::createFromImageData (BinaryData::GLOBAL_btn_osc_filter_141x26_png, BinaryData::GLOBAL_btn_osc_filter_141x26_pngSize), m_oscFilter); setupButton (1, Drawable::createFromImageData (BinaryData::GLOBAL_btn_lfo_matrix_141x26_png, BinaryData::GLOBAL_btn_lfo_matrix_141x26_pngSize), m_lfoMatrix); setupButton (2, Drawable::createFromImageData (BinaryData::GLOBAL_btn_effects_141x26_png, BinaryData::GLOBAL_btn_effects_141x26_pngSize), m_effects); setupButton (3, Drawable::createFromImageData (BinaryData::GLOBAL_btn_arp_settings_141x26_png, BinaryData::GLOBAL_btn_arp_settings_141x26_pngSize), m_arpSettings); + setupButton (4, Drawable::createFromImageData(BinaryData::GLOBAL_btn_patch_browser_141x26_png, BinaryData::GLOBAL_btn_patch_browser_141x26_pngSize), m_patches); setSize ((kButtonWidth + kMargin) * numOfMainButtons, kButtonHeight); } + void VirusEditor::MainButtons::valueChanged(juce::Value &) { updateSection(); } void VirusEditor::MainButtons::setupButton (int i, std::unique_ptr<Drawable>&& btnImage, juce::DrawableButton& btn) @@ -99,3 +404,119 @@ VirusEditor::PresetButtons::PresetButtons() m_load.setBounds(36 + w, y, w, Buttons::PresetButton::kHeight); m_presets.setBounds(43 + w * 2, y, w, Buttons::PresetButton::kHeight); } + + + +VirusEditor::PartButtons::PartButtons() +{ +} + +void VirusEditor::loadFile() +{ + juce::FileChooser chooser( + "Choose syx/midi banks to import", + m_previousPath.isEmpty() + ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() + : m_previousPath, + "*.syx,*.mid,*.midi", true); + + if (!chooser.browseForFileToOpen()) + return; + bool sentData = false; + const auto result = chooser.getResult(); + m_previousPath = result.getParentDirectory().getFullPathName(); + const auto ext = result.getFileExtension().toLowerCase(); + if (ext == ".syx") + { + juce::MemoryBlock data; + result.loadFileAsData(data); + + for (auto it = data.begin(); it != data.end(); it += 267) + { + if ((it + 267) <= data.end()) + { + m_controller.sendSysEx(Virus::SysEx(it, it + 267)); + sentData = true; + } + } + } + else if (ext == ".mid" || ext == ".midi") + { + juce::MemoryBlock data; + if (!result.loadFileAsData(data)) + { + return; + } + const uint8_t *ptr = (uint8_t *)data.getData(); + const auto end = ptr + data.getSize(); + + for (auto it = ptr; it < end; it += 1) + { + if ((uint8_t)*it == (uint8_t)0xf0 && (it + 267) < end) + { + if ((uint8_t) * (it + 1) == (uint8_t)0x00) + { + auto syx = Virus::SysEx(it, it + 267); + syx[7] = 0x01; // force to bank a + syx[266] = 0xf7; + + m_controller.sendSysEx(syx); + + it += 266; + } + else // some midi files have two bytes after the 0xf0 + { + auto syx = Virus::SysEx(); + syx.push_back(0xf0); + for (auto i = it + 3; i < it + 3 + 266; i++) + { + syx.push_back((uint8_t)*i); + } + syx[7] = 0x01; // force to bank a + syx[266] = 0xf7; + m_controller.sendSysEx(syx); + it += 266; + } + + sentData = true; + } + } + } + + if (sentData) + m_controller.onStateLoaded(); +} + +void VirusEditor::saveFile() { + juce::FileChooser chooser( + "Save preset as syx", + m_previousPath.isEmpty() + ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() + : m_previousPath, + "*.syx", true); + + if (!chooser.browseForFileToSave(true)) + return; + bool sentData = false; + const auto result = chooser.getResult(); + m_previousPath = result.getParentDirectory().getFullPathName(); + const auto ext = result.getFileExtension().toLowerCase(); + + const uint8_t syxHeader[9] = {0xF0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x10, 0x01, 0x00}; + const uint8_t syxEof[1] = {0xF7}; + uint8_t cs = syxHeader[5] + syxHeader[6] + syxHeader[7] + syxHeader[8]; + uint8_t data[256]; + for (int i = 0; i < 256; i++) + { + auto param = m_controller.getParamValue(m_parameterBinding.m_part, i < 128 ? 0 : 1, i % 128); + + data[i] = param ? (int)param->getValue() : 0; + cs += data[i]; + } + cs = cs & 0x7f; + + result.appendData(syxHeader, 9); + result.appendData(data, 256); + result.appendData(&cs, 1); + result.appendData(syxEof, 1); +} +\ No newline at end of file diff --git a/source/jucePlugin/ui/VirusEditor.h b/source/jucePlugin/ui/VirusEditor.h @@ -1,23 +1,52 @@ #pragma once #include <juce_gui_extra/juce_gui_extra.h> +#include <juce_audio_devices/juce_audio_devices.h> #include "Virus_Buttons.h" #include "Virus_LookAndFeel.h" +#include "../VirusController.h" class VirusParameterBinding; class OscEditor; class LfoEditor; class FxEditor; class ArpEditor; +class PatchBrowser; -class VirusEditor : public juce::Component +class VirusEditor : public juce::Component, private juce::Timer { public: - VirusEditor(VirusParameterBinding& _parameterBinding); + VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAudioProcessor &_processorRef); ~VirusEditor() override; void resized() override; + void changePart(uint8_t _part); + void updatePartsPresetNames(); + void loadFile(); + void saveFile(); + void setPlayMode(uint8_t _mode); private: + void timerCallback() override; + void updateMidiInput(int index); + void updateMidiOutput(int index); + + juce::Label m_version; + juce::Label m_patchName; + Buttons::PartSelectButton m_partSelect[16]; + juce::Label m_partLabels[16]; + juce::TextButton m_presetNames[16]; + juce::TextButton m_nextPatch[16]; + juce::TextButton m_prevPatch[16]; + juce::TextButton m_btSingleMode; + juce::TextButton m_btMultiMode; + juce::ComboBox m_cmbMidiInput; + juce::ComboBox m_cmbMidiOutput; + juce::AudioDeviceManager deviceManager; + juce::PropertiesFile *m_properties; + int m_lastInputIndex = 0; + int m_lastOutputIndex = 0; + + static constexpr auto kPartGroupId = 0x3FBBC; struct MainButtons : juce::Component, juce::Value::Listener { MainButtons(); @@ -25,7 +54,7 @@ private: void valueChanged(juce::Value &) override; std::function<void()> updateSection; - juce::DrawableButton m_oscFilter, m_lfoMatrix, m_effects, m_arpSettings; + juce::DrawableButton m_oscFilter, m_lfoMatrix, m_effects, m_arpSettings, m_patches; static constexpr auto kMargin = 5; static constexpr auto kButtonWidth = 141; static constexpr auto kButtonHeight = 26; @@ -38,15 +67,23 @@ private: Buttons::PresetButton m_save, m_load, m_presets; } m_presetButtons; + struct PartButtons : juce::Component { + PartButtons(); + }; void applyToSections(std::function<void(juce::Component *)>); - VirusParameterBinding& m_parameterBinding; + VirusParameterBinding& m_parameterBinding; + AudioPluginAudioProcessor &processorRef; + Virus::Controller& m_controller; std::unique_ptr<OscEditor> m_oscEditor; std::unique_ptr<LfoEditor> m_lfoEditor; std::unique_ptr<FxEditor> m_fxEditor; std::unique_ptr<ArpEditor> m_arpEditor; + std::unique_ptr<PatchBrowser> m_patchBrowser; std::unique_ptr<juce::Drawable> m_background; Virus::LookAndFeel m_lookAndFeel; + + juce::String m_previousPath; }; diff --git a/source/jucePlugin/ui/Virus_ArpEditor.cpp b/source/jucePlugin/ui/Virus_ArpEditor.cpp @@ -1,12 +1,14 @@ #include "Virus_ArpEditor.h" #include "BinaryData.h" #include "Ui_Utils.h" - +#include "../VirusParameterBinding.h" constexpr auto comboBoxWidth = 84; using namespace juce; -ArpEditor::ArpEditor(VirusParameterBinding& _parameterBinding) +ArpEditor::ArpEditor(VirusParameterBinding &_parameterBinding) : + m_velocityAmount(_parameterBinding), m_inputs(_parameterBinding), m_arp(_parameterBinding), + m_softKnobs(_parameterBinding), m_patchSettings(_parameterBinding) { setupBackground(*this, m_background, BinaryData::bg_arp_1018x620_png, BinaryData::bg_arp_1018x620_pngSize); setBounds(m_background->getDrawableBounds().toNearestIntEdges()); @@ -23,7 +25,7 @@ ArpEditor::ArpEditor(VirusParameterBinding& _parameterBinding) addAndMakeVisible(m_patchSettings); } -ArpEditor::VelocityAmount::VelocityAmount() +ArpEditor::VelocityAmount::VelocityAmount(VirusParameterBinding &_parameterBinding) { constexpr auto y = 19; for (auto *s : {&m_osc1Shape, &m_filter1Freq, &m_filter1Res, &m_pulseWidth, &m_volume, &m_panorama, &m_osc2Shape, @@ -41,17 +43,31 @@ ArpEditor::VelocityAmount::VelocityAmount() m_filter2Freq.setBounds(m_osc1Shape.getRight() - 7, y2, knobSize, knobSize); m_filter2Res.setBounds(m_filter1Freq.getRight() - 8, y2, knobSize, knobSize); m_fmAmount.setBounds(m_filter1Res.getRight() - 7, y2, knobSize, knobSize); + + _parameterBinding.bind(m_osc1Shape, Virus::Param_Osc1ShapeVelocity); + _parameterBinding.bind(m_filter1Freq, Virus::Param_Filter1EnvAmtVelocity); + _parameterBinding.bind(m_filter1Res, Virus::Param_Resonance1Velocity); + _parameterBinding.bind(m_pulseWidth, Virus::Param_PulseWidthVelocity); + _parameterBinding.bind(m_volume, Virus::Param_AmpVelocity); + _parameterBinding.bind(m_panorama, Virus::Param_PanoramaVelocity); + _parameterBinding.bind(m_osc2Shape, Virus::Param_Osc2ShapeVelocity); + _parameterBinding.bind(m_filter2Freq, Virus::Param_Filter2EnvAmtVelocity); + _parameterBinding.bind(m_filter2Res, Virus::Param_Resonance2Velocity); + _parameterBinding.bind(m_fmAmount, Virus::Param_FmAmountVelocity); } -ArpEditor::Inputs::Inputs() +ArpEditor::Inputs::Inputs(VirusParameterBinding &_parameterBinding) { addAndMakeVisible(m_inputMode); m_inputMode.setBounds(43, 38, comboBoxWidth, comboBoxHeight); addAndMakeVisible(m_inputSelect); m_inputSelect.setBounds(145, 38, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_inputMode, Virus::Param_InputMode); + _parameterBinding.bind(m_inputSelect, Virus::Param_InputSelect); } -ArpEditor::Arpeggiator::Arpeggiator() +ArpEditor::Arpeggiator::Arpeggiator(VirusParameterBinding &_parameterBinding) { constexpr auto y = 18; for (auto *s : {&m_globalTempo, &m_noteLength, &m_noteSwing}) @@ -67,17 +83,25 @@ ArpEditor::Arpeggiator::Arpeggiator() constexpr auto comboBoxHeight = 15; constexpr auto comboTopY = 35; - m_mode.setBounds(39, 40, 52, 38); + m_mode.setBounds(35, 40, 100, 18); m_pattern.setBounds(114, comboTopY, comboBoxWidth, comboBoxHeight); m_resolution.setBounds(220, comboTopY, comboBoxWidth, comboBoxHeight); m_octaveRange.setBounds(m_pattern.getBounds().translated(0, comboBoxHeight + 18)); - m_arpHold.setButtonText("On"); addAndMakeVisible(m_arpHold); - m_arpHold.setBounds(220, m_octaveRange.getY(), 32, comboBoxHeight); + m_arpHold.setBounds(222, m_octaveRange.getY()+2, 28, 11); + + _parameterBinding.bind(m_globalTempo, Virus::Param_ClockTempo); + _parameterBinding.bind(m_noteLength, Virus::Param_ArpNoteLength); + _parameterBinding.bind(m_noteSwing, Virus::Param_ArpSwing); + _parameterBinding.bind(m_mode, Virus::Param_ArpMode); + _parameterBinding.bind(m_pattern, Virus::Param_ArpPatternSelect); + _parameterBinding.bind(m_octaveRange, Virus::Param_ArpOctaveRange); + _parameterBinding.bind(m_resolution, Virus::Param_ArpClock); + _parameterBinding.bind(m_arpHold, Virus::Param_ArpHoldEnable); } -ArpEditor::SoftKnobs::SoftKnobs() +ArpEditor::SoftKnobs::SoftKnobs(VirusParameterBinding &_parameterBinding) { auto distance = 105; for (auto i = 0; i < 2; i++) @@ -89,7 +113,7 @@ ArpEditor::SoftKnobs::SoftKnobs() } } -ArpEditor::PatchSettings::PatchSettings() +ArpEditor::PatchSettings::PatchSettings(VirusParameterBinding &_parameterBinding) { constexpr auto y = 18; for (auto *s : {&m_patchVolume, &m_panning, &m_outputBalance, &m_transpose}) @@ -116,4 +140,15 @@ ArpEditor::PatchSettings::PatchSettings() m_bendDown.setBounds(x2, 42, comboBoxWidth, comboBoxHeight); m_smoothMode.setBounds(x2, m_bendScale.getY(), comboBoxWidth, comboBoxHeight); m_cat2.setBounds(x2, m_cat1.getY(), comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_patchVolume, Virus::Param_PatchVolume); + _parameterBinding.bind(m_panning, Virus::Param_Panorama); + //_parameterBinding.bind(m_outputBalance, Virus::Param_SecondOutputBalance); + _parameterBinding.bind(m_transpose, Virus::Param_Transpose); + _parameterBinding.bind(m_keyMode, Virus::Param_KeyMode); + //_parameterBinding.bind(m_secondaryOutput, Virus::Param_KeyMode); + _parameterBinding.bind(m_bendUp, Virus::Param_BenderRangeUp); + _parameterBinding.bind(m_bendDown, Virus::Param_BenderRangeDown); + _parameterBinding.bind(m_bendScale, Virus::Param_BenderScale); + _parameterBinding.bind(m_smoothMode, Virus::Param_ControlSmoothMode); } diff --git a/source/jucePlugin/ui/Virus_ArpEditor.h b/source/jucePlugin/ui/Virus_ArpEditor.h @@ -1,6 +1,7 @@ #pragma once #include "../PluginProcessor.h" +#include "Virus_Buttons.h" class VirusParameterBinding; @@ -12,7 +13,7 @@ public: private: struct VelocityAmount : juce::Component { - VelocityAmount(); + VelocityAmount(VirusParameterBinding &_parameterBinding); juce::Slider m_osc1Shape; juce::Slider m_filter1Freq; juce::Slider m_filter1Res; @@ -27,29 +28,29 @@ private: struct Inputs : juce::Component { - Inputs(); + Inputs(VirusParameterBinding &_parameterBinding); juce::ComboBox m_inputMode, m_inputSelect; } m_inputs; struct Arpeggiator : juce::Component { - Arpeggiator(); + Arpeggiator(VirusParameterBinding &_parameterBinding); juce::Slider m_globalTempo; juce::Slider m_noteLength; juce::Slider m_noteSwing; juce::ComboBox m_mode, m_pattern, m_octaveRange, m_resolution; - juce::TextButton m_arpHold; + Buttons::ArpHoldButton m_arpHold; } m_arp; struct SoftKnobs : juce::Component { - SoftKnobs(); + SoftKnobs(VirusParameterBinding &_parameterBinding); juce::ComboBox m_funcAs[2], m_name[2]; } m_softKnobs; struct PatchSettings : juce::Component { - PatchSettings(); + PatchSettings(VirusParameterBinding &_parameterBinding); juce::Slider m_patchVolume; juce::Slider m_panning; juce::Slider m_outputBalance; diff --git a/source/jucePlugin/ui/Virus_Buttons.cpp b/source/jucePlugin/ui/Virus_Buttons.cpp @@ -27,6 +27,17 @@ namespace Buttons setImages(off.get(), nullptr, on.get(), nullptr, on.get()); } + Buttons::ArpHoldButton::ArpHoldButton() : DrawableButton("ArpHoldButton", DrawableButton::ImageRaw) + { + auto off = Drawable::createFromImageData(BinaryData::arphold_btn_28_22_png, BinaryData::arphold_btn_28_22_pngSize); + auto on = off->createCopy(); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setClickingTogglesState(true); + on->setOriginWithOriginalSize({0, -11}); + setImages(off.get(), nullptr, on.get(), nullptr, on.get()); + } + Buttons::EnvPol::EnvPol() : m_pos("Positive", DrawableButton::ImageRaw), m_neg("Negative", DrawableButton::ImageRaw) { static int radioGroup = 0x4bc3f; @@ -92,4 +103,15 @@ namespace Buttons setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); setImages(normal.get(), nullptr, pressed.get(), nullptr, pressed.get(), nullptr, normal.get()); } + + Buttons::PartSelectButton::PartSelectButton() : DrawableButton("PartSelectButton", DrawableButton::ButtonStyle::ImageRaw) + { + auto normal = + Drawable::createFromImageData(BinaryData::part_select_btn_39x72_png, BinaryData::part_select_btn_39x72_pngSize); + auto pressed = normal->createCopy(); + pressed->setOriginWithOriginalSize({0, -36}); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setImages(normal.get(), nullptr, pressed.get(), nullptr, pressed.get(), nullptr, normal.get()); + } }; // namespace Buttons diff --git a/source/jucePlugin/ui/Virus_Buttons.h b/source/jucePlugin/ui/Virus_Buttons.h @@ -19,15 +19,21 @@ namespace Buttons static constexpr auto kWidth = 23; static constexpr auto kHeight = 19; }; - + class ArpHoldButton : public juce::DrawableButton + { + public: + ArpHoldButton(); + static constexpr auto kWidth = 28; + static constexpr auto kHeight = 11; + }; class EnvPol : public juce::Component { public: EnvPol(); - - private: juce::DrawableButton m_pos; juce::DrawableButton m_neg; + private: + }; class LinkButton : public juce::DrawableButton @@ -51,4 +57,12 @@ namespace Buttons static constexpr auto kHeight = 15; PresetButton(); }; + + class PartSelectButton : public juce::DrawableButton + { + public: + static constexpr auto kWidth = 39; + static constexpr auto kHeight = 36; + PartSelectButton(); + }; } // namespace Buttons diff --git a/source/jucePlugin/ui/Virus_FxEditor.cpp b/source/jucePlugin/ui/Virus_FxEditor.cpp @@ -1,12 +1,15 @@ #include "Virus_FxEditor.h" #include "BinaryData.h" #include "Ui_Utils.h" - +#include "../VirusParameterBinding.h" using namespace juce; constexpr auto comboBoxWidth = 84; -FxEditor::FxEditor(VirusParameterBinding& _parameterBinding) +FxEditor::FxEditor(VirusParameterBinding &_parameterBinding) : + m_dist(_parameterBinding), m_analogBoost(_parameterBinding), m_phaser(_parameterBinding), + m_chorus(_parameterBinding), m_eq(_parameterBinding), m_envFollow(_parameterBinding), m_punch(_parameterBinding), + m_delayReverb(_parameterBinding), m_vocoder(_parameterBinding) { setupBackground(*this, m_background, BinaryData::bg_fx_1018x620_png, BinaryData::bg_fx_1018x620_pngSize); setBounds(m_background->getDrawableBounds().toNearestIntEdges()); @@ -31,23 +34,29 @@ FxEditor::FxEditor(VirusParameterBinding& _parameterBinding) addAndMakeVisible(m_vocoder); } -FxEditor::Distortion::Distortion() +FxEditor::Distortion::Distortion(VirusParameterBinding &_parameterBinding) { setupRotary(*this, m_intensity); m_intensity.setBounds(101, 18, knobSize, knobSize); addAndMakeVisible(m_curve); m_curve.setBounds(17, 42, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_intensity, Virus::Param_DistortionIntensity); + _parameterBinding.bind(m_curve, Virus::Param_DistortionCurve); } -FxEditor::AnalogBoost::AnalogBoost() +FxEditor::AnalogBoost::AnalogBoost(VirusParameterBinding &_parameterBinding) { for (auto *s : {&m_boost, &m_tune}) setupRotary(*this, *s); m_boost.setBounds(16, 18, knobSize, knobSize); m_tune.setBounds(m_boost.getBounds().withX(m_boost.getRight() - 4)); + + _parameterBinding.bind(m_boost, Virus::Param_BassIntensity); + _parameterBinding.bind(m_tune, Virus::Param_BassTune); } -FxEditor::Phaser::Phaser() +FxEditor::Phaser::Phaser(VirusParameterBinding &_parameterBinding) { constexpr auto y = 16; for (auto *s : {&m_rate, &m_freq, &m_depth, &m_feedback, &m_spread, &m_mix}) @@ -61,9 +70,17 @@ FxEditor::Phaser::Phaser() m_mix.setBounds(m_spread.getRight() - 7, y, knobSize, knobSize); addAndMakeVisible(m_stages); m_stages.setBounds(17, 41, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_rate, Virus::Param_PhaserRate); + _parameterBinding.bind(m_freq, Virus::Param_PhaserFreq); + _parameterBinding.bind(m_depth, Virus::Param_PhaserDepth); + _parameterBinding.bind(m_feedback, Virus::Param_PhaserFeedback); + _parameterBinding.bind(m_spread, Virus::Param_PhaserSpread); + _parameterBinding.bind(m_mix, Virus::Param_PhaserMix); + _parameterBinding.bind(m_stages, Virus::Param_PhaserMode); } -FxEditor::Chorus::Chorus() +FxEditor::Chorus::Chorus(VirusParameterBinding &_parameterBinding) { constexpr auto y = 18; for (auto *s : {&m_rate, &m_depth, &m_feedback, &m_delay, &m_mix}) @@ -76,9 +93,16 @@ FxEditor::Chorus::Chorus() m_mix.setBounds(m_delay.getRight() - 4, y, knobSize, knobSize); addAndMakeVisible(m_lfoShape); m_lfoShape.setBounds(17, 42, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_rate, Virus::Param_ChorusRate); + _parameterBinding.bind(m_depth, Virus::Param_ChorusDepth); + _parameterBinding.bind(m_feedback, Virus::Param_ChorusFeedback); + _parameterBinding.bind(m_delay, Virus::Param_ChorusDelay); + _parameterBinding.bind(m_mix, Virus::Param_ChorusMix); + _parameterBinding.bind(m_lfoShape, Virus::Param_ChorusLfoShape); } -FxEditor::Equalizer::Equalizer() +FxEditor::Equalizer::Equalizer(VirusParameterBinding &_parameterBinding) { constexpr auto y = 18; for (auto *s : {&m_low_gain, &m_low_freq, &m_mid_gain, &m_mid_freq, &m_mid_q, &m_high_gain, &m_high_freq}) @@ -90,9 +114,17 @@ FxEditor::Equalizer::Equalizer() m_mid_q.setBounds(m_mid_freq.getRight() - 6, y, knobSize, knobSize); m_high_gain.setBounds(m_mid_q.getRight() - 6, y, knobSize, knobSize); m_high_freq.setBounds(m_high_gain.getRight() - 6, y, knobSize, knobSize); + + _parameterBinding.bind(m_low_gain, Virus::Param_LowEqGain); + _parameterBinding.bind(m_low_freq, Virus::Param_LowEqFreq); + _parameterBinding.bind(m_mid_gain, Virus::Param_MidEqGain); + _parameterBinding.bind(m_mid_freq, Virus::Param_MidEqFreq); + _parameterBinding.bind(m_mid_q, Virus::Param_MidEqQFactor); + _parameterBinding.bind(m_high_gain, Virus::Param_HighEqGain); + _parameterBinding.bind(m_high_freq, Virus::Param_HighEqFreq); } -FxEditor::EnvelopeFollower::EnvelopeFollower() +FxEditor::EnvelopeFollower::EnvelopeFollower(VirusParameterBinding &_parameterBinding) { constexpr auto y = 12; for (auto *s : {&m_gain, &m_attack, &m_release}) @@ -102,15 +134,18 @@ FxEditor::EnvelopeFollower::EnvelopeFollower() m_release.setBounds(m_attack.getRight() - 7, y, knobSize, knobSize); addAndMakeVisible(m_input); m_input.setBounds(17, 37, comboBoxWidth, comboBoxHeight); + } -FxEditor::Punch::Punch() +FxEditor::Punch::Punch(VirusParameterBinding &_parameterBinding) { setupRotary(*this, m_amount); m_amount.setBounds(19, 12, knobSize, knobSize); + + _parameterBinding.bind(m_amount, Virus::Param_PunchIntensity); } -FxEditor::DelayAndReverb::DelayAndReverb() +FxEditor::DelayAndReverb::DelayAndReverb(VirusParameterBinding &_parameterBinding) : m_sync(_parameterBinding) { constexpr auto y = 18; for (auto *s : {&m_time, &m_rate, &m_depth, &m_color, &m_feedback}) @@ -126,9 +161,16 @@ FxEditor::DelayAndReverb::DelayAndReverb() m_sync.setBounds(0, 116 + 2, 481, 116); addAndMakeVisible(m_sync); + + _parameterBinding.bind(m_time, Virus::Param_DelayTime); + _parameterBinding.bind(m_rate, Virus::Param_DelayRateReverbDecayTime); + _parameterBinding.bind(m_depth, Virus::Param_DelayDepthReverbRoomSize); + _parameterBinding.bind(m_color, Virus::Param_DelayColor); + _parameterBinding.bind(m_feedback, Virus::Param_DelayFeedback); + _parameterBinding.bind(m_fxMode, Virus::Param_DelayReverbMode); } -FxEditor::DelayAndReverb::Sync::Sync() +FxEditor::DelayAndReverb::Sync::Sync(VirusParameterBinding &_parameterBinding) { setupRotary(*this, m_mix); m_mix.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); @@ -138,9 +180,14 @@ FxEditor::DelayAndReverb::Sync::Sync() m_clock.setBounds(18, 22, comboBoxWidth, comboBoxHeight); addAndMakeVisible(m_lfoShape); m_lfoShape.setBounds(m_clock.getBounds().getRight() + 26, 22, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_mix, Virus::Param_EffectSend); + _parameterBinding.bind(m_clock, Virus::Param_DelayClock); + _parameterBinding.bind(m_lfoShape, Virus::Param_DelayLfoShape); } -FxEditor::Vocoder::Vocoder() : m_link(true) +FxEditor::Vocoder::Vocoder(VirusParameterBinding &_parameterBinding) : + m_link(true), m_carrier(_parameterBinding), m_modulator(_parameterBinding) { constexpr auto y = 17; for (auto *s : {&m_sourceBalance, &m_spectralBalance, &m_bands, &m_attack, &m_release}) @@ -160,9 +207,17 @@ FxEditor::Vocoder::Vocoder() : m_link(true) m_link.setBounds(445, 195, 12, 36); addAndMakeVisible(m_mode); m_mode.setBounds(16, 43, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_mode, Virus::Param_VocoderMode); + _parameterBinding.bind(m_bands, Virus::Param_FilterEnvRelease); + _parameterBinding.bind(m_sourceBalance, Virus::Param_FilterBalance); + _parameterBinding.bind(m_spectralBalance, Virus::Param_FilterEnvSustainTime); + _parameterBinding.bind(m_attack, Virus::Param_FilterEnvAttack); + _parameterBinding.bind(m_release, Virus::Param_FilterEnvDecay); + _parameterBinding.bind(m_link, Virus::Param_Filter2CutoffLink); } -FxEditor::Vocoder::Carrier::Carrier() +FxEditor::Vocoder::Carrier::Carrier(VirusParameterBinding &_parameterBinding) { constexpr auto y = -4; for (auto *s : {&m_center_freq, &m_q_factor, &m_spread}) @@ -170,9 +225,13 @@ FxEditor::Vocoder::Carrier::Carrier() m_center_freq.setBounds(12, -4, knobSize, knobSize); m_q_factor.setBounds(m_center_freq.getRight() - 8, y, knobSize, knobSize); m_spread.setBounds(m_q_factor.getRight() - 7, y, knobSize, knobSize); + + _parameterBinding.bind(m_center_freq, Virus::Param_FilterCutA); + _parameterBinding.bind(m_q_factor, Virus::Param_FilterResA); + _parameterBinding.bind(m_spread, Virus::Param_FilterKeyFollowA); } -FxEditor::Vocoder::Modulator::Modulator() +FxEditor::Vocoder::Modulator::Modulator(VirusParameterBinding &_parameterBinding) { constexpr auto y = -4; for (auto *s : {&m_freq_offset, &m_q_factor, &m_spread}) @@ -182,4 +241,9 @@ FxEditor::Vocoder::Modulator::Modulator() m_spread.setBounds(m_q_factor.getRight() - 7, y, knobSize, knobSize); addAndMakeVisible(m_modInput); m_modInput.setBounds(8, 23, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_freq_offset, Virus::Param_FilterCutB); + _parameterBinding.bind(m_q_factor, Virus::Param_FilterResB); + _parameterBinding.bind(m_spread, Virus::Param_FilterKeyFollowB); + _parameterBinding.bind(m_modInput, Virus::Param_InputSelect); } diff --git a/source/jucePlugin/ui/Virus_FxEditor.h b/source/jucePlugin/ui/Virus_FxEditor.h @@ -13,21 +13,21 @@ public: private: struct Distortion : juce::Component { - Distortion(); + Distortion(VirusParameterBinding &_parameterBinding); juce::Slider m_intensity; juce::ComboBox m_curve; } m_dist; struct AnalogBoost : juce::Component { - AnalogBoost(); + AnalogBoost(VirusParameterBinding &_parameterBinding); juce::Slider m_boost; juce::Slider m_tune; } m_analogBoost; struct Phaser : juce::Component { - Phaser(); + Phaser(VirusParameterBinding &_parameterBinding); juce::Slider m_rate; juce::Slider m_freq; juce::Slider m_depth; @@ -39,7 +39,7 @@ private: struct Chorus : juce::Component { - Chorus(); + Chorus(VirusParameterBinding &_parameterBinding); juce::Slider m_rate; juce::Slider m_depth; juce::Slider m_feedback; @@ -50,7 +50,7 @@ private: struct Equalizer : juce::Component { - Equalizer(); + Equalizer(VirusParameterBinding &_parameterBinding); juce::Slider m_low_gain; juce::Slider m_low_freq; juce::Slider m_mid_gain; @@ -62,7 +62,7 @@ private: struct EnvelopeFollower : juce::Component { - EnvelopeFollower(); + EnvelopeFollower(VirusParameterBinding &_parameterBinding); juce::Slider m_gain; juce::Slider m_attack; juce::Slider m_release; @@ -71,13 +71,13 @@ private: struct Punch : juce::Component { - Punch(); + Punch(VirusParameterBinding &_parameterBinding); juce::Slider m_amount; } m_punch; struct DelayAndReverb : juce::Component { - DelayAndReverb(); + DelayAndReverb(VirusParameterBinding &_parameterBinding); juce::Slider m_time; juce::Slider m_rate; juce::Slider m_depth; @@ -87,7 +87,7 @@ private: struct Sync : juce::Component { - Sync(); + Sync(VirusParameterBinding &_parameterBinding); juce::Slider m_mix; juce::ComboBox m_clock, m_lfoShape; } m_sync; @@ -95,7 +95,7 @@ private: struct Vocoder : juce::Component { - Vocoder(); + Vocoder(VirusParameterBinding &_parameterBinding); juce::Slider m_sourceBalance; juce::Slider m_spectralBalance; juce::Slider m_bands; @@ -106,7 +106,7 @@ private: struct Carrier : juce::Component { - Carrier(); + Carrier(VirusParameterBinding &_parameterBinding); juce::Slider m_center_freq; juce::Slider m_q_factor; juce::Slider m_spread; @@ -114,7 +114,7 @@ private: struct Modulator : juce::Component { - Modulator(); + Modulator(VirusParameterBinding &_parameterBinding); juce::Slider m_freq_offset; juce::Slider m_q_factor; juce::Slider m_spread; diff --git a/source/jucePlugin/ui/Virus_LfoEditor.cpp b/source/jucePlugin/ui/Virus_LfoEditor.cpp @@ -1,6 +1,7 @@ #include "Virus_LfoEditor.h" #include "BinaryData.h" #include "Ui_Utils.h" +#include "../VirusParameterBinding.h" using namespace juce; constexpr auto comboBoxWidth = 98; @@ -22,21 +23,39 @@ LfoEditor::LfoEditor(VirusParameterBinding& _parameterBinding) : m_lfoOne(_param addAndMakeVisible(m_modMatrix); } -LfoEditor::LfoBase::LfoBase(VirusParameterBinding& _parameterBinding) +LfoEditor::LfoBase::LfoBase(VirusParameterBinding& _parameterBinding, uint8_t _lfoIndex) { for (auto *s : {&m_rate, &m_keytrack, &m_amount}) setupRotary(*this, *s); - addAndMakeVisible(m_subWaveform); - m_subWaveform.setBounds(8, 123, Buttons::HandleButton::kWidth, Buttons::HandleButton::kHeight); + addAndMakeVisible(m_mode); + m_mode.setBounds(8, 123, Buttons::HandleButton::kWidth, Buttons::HandleButton::kHeight); addAndMakeVisible(m_shape); m_shape.setBounds(10, 37, 84, comboBoxHeight); addAndMakeVisible(m_clock); m_clock.setBounds(10, 80, 84, comboBoxHeight); addAndMakeVisible(m_assignDest); + const Virus::ParameterType rate[] = {Virus::Param_Lfo1Rate, Virus::Param_Lfo2Rate, Virus::Param_Lfo3Rate}; + const Virus::ParameterType keytrack[] = {Virus::Param_Lfo1Keyfollow, Virus::Param_Lfo2Keyfollow, + Virus::Param_Lfo3Keyfollow}; + const Virus::ParameterType amount[] = {Virus::Param_Lfo1AssignAmount, Virus::Param_Lfo2AssignAmount, + Virus::Param_OscLfo3Amount}; + const Virus::ParameterType shapes[] = {Virus::Param_Lfo1Shape, Virus::Param_Lfo2Shape, Virus::Param_Lfo3Shape}; + const Virus::ParameterType clock[] = {Virus::Param_Lfo1Clock, Virus::Param_Lfo2Clock, Virus::Param_Lfo3Clock}; + const Virus::ParameterType assignDest[] = {Virus::Param_Lfo1AssignDest, Virus::Param_Lfo2AssignDest, + Virus::Param_Lfo3Destination}; + const Virus::ParameterType lfoModes[] = {Virus::Param_Lfo1Mode, Virus::Param_Lfo2Mode, Virus::Param_Lfo3Mode}; + + _parameterBinding.bind(m_rate, rate[_lfoIndex]); + _parameterBinding.bind(m_keytrack, keytrack[_lfoIndex]); + _parameterBinding.bind(m_amount, amount[_lfoIndex]); + _parameterBinding.bind(m_shape, shapes[_lfoIndex]); + _parameterBinding.bind(m_clock, clock[_lfoIndex]); + _parameterBinding.bind(m_assignDest, assignDest[_lfoIndex]); + _parameterBinding.bind(m_mode, lfoModes[_lfoIndex]); } -LfoEditor::LfoTwoOneShared::LfoTwoOneShared(VirusParameterBinding& _parameterBinding) : LfoBase(_parameterBinding), m_link(false) +LfoEditor::LfoTwoOneShared::LfoTwoOneShared(VirusParameterBinding& _parameterBinding, uint8_t _lfoIndex) : LfoBase(_parameterBinding, _lfoIndex), m_link(false) { for (auto *s : {&m_contour, &m_phase}) setupRotary(*this, *s); @@ -50,9 +69,18 @@ LfoEditor::LfoTwoOneShared::LfoTwoOneShared(VirusParameterBinding& _parameterBin m_link.setBounds(293, 8, 36, 12); m_assignDest.setBounds(393, 122, comboBoxWidth, comboBoxHeight); + + //_parameterBinding.bind(m_rate, Virus::Param_Lfo1Rate); + //_parameterBinding.bind(m_keytrack, Virus::Param_Lfo1Keyfollow); + _parameterBinding.bind(m_contour, _lfoIndex == 0 ? Virus::Param_Lfo1Symmetry : Virus::Param_Lfo2Symmetry); + _parameterBinding.bind(m_phase, _lfoIndex == 0 ? Virus::Param_Lfo1KeyTrigger : Virus::Param_Lfo2Keytrigger); + //parameterBinding.bind(m_amount, Virus::Param_Lfo1AssignAmount); + _parameterBinding.bind(m_envMode, _lfoIndex == 0 ? Virus::Param_Lfo1EnvMode : Virus::Param_Lfo2EnvMode); + //_parameterBinding.bind(m_link, _lfoIndex == 0 ? Virus::Param_Lfo1Mode : Virus::Param_Lfo2Mode); + //_parameterBinding.bind(m_assignDest, Virus::Param_Lfo1AssignDest); } -LfoEditor::LfoOne::LfoOne(VirusParameterBinding& _parameterBinding) : LfoTwoOneShared(_parameterBinding) +LfoEditor::LfoOne::LfoOne(VirusParameterBinding& _parameterBinding) : LfoTwoOneShared(_parameterBinding, 0) { for (auto *s : {&m_osc1Pitch, &m_osc2Pitch, &m_filterGain, &m_pw12, &m_reso12}) setupRotary(*this, *s); @@ -62,9 +90,15 @@ LfoEditor::LfoOne::LfoOne(VirusParameterBinding& _parameterBinding) : LfoTwoOneS m_pw12.setBounds(374, 14, knobSize, knobSize); m_reso12.setBounds(m_pw12.getBounds().translated(knobSize - 4, 0)); addAndMakeVisible(m_link); // add last to allow clicking over knobs area... + + _parameterBinding.bind(m_osc1Pitch, Virus::Param_Osc1Lfo1Amount); + _parameterBinding.bind(m_osc2Pitch, Virus::Param_Osc2Lfo1Amount); + _parameterBinding.bind(m_filterGain, Virus::Param_FltGainLfo1Amount); + _parameterBinding.bind(m_pw12, Virus::Param_PWLfo1Amount); + _parameterBinding.bind(m_reso12, Virus::Param_ResoLfo1Amount); } -LfoEditor::LfoTwo::LfoTwo(VirusParameterBinding& _parameterBinding) : LfoTwoOneShared(_parameterBinding) +LfoEditor::LfoTwo::LfoTwo(VirusParameterBinding& _parameterBinding) : LfoTwoOneShared(_parameterBinding, 1) { for (auto *s : {&m_f1cutoff, &m_f2cutoff, &m_panning, &m_shape12, &m_fmAmount}) setupRotary(*this, *s); @@ -74,9 +108,15 @@ LfoEditor::LfoTwo::LfoTwo(VirusParameterBinding& _parameterBinding) : LfoTwoOneS m_shape12.setBounds(374, 14, knobSize, knobSize); m_fmAmount.setBounds(m_shape12.getBounds().translated(knobSize - 4, 0)); addAndMakeVisible(m_link); // add last to allow clicking over knobs area... + + _parameterBinding.bind(m_f1cutoff, Virus::Param_Cutoff1Lfo2Amount); + _parameterBinding.bind(m_f2cutoff, Virus::Param_Cutoff2Lfo2Amount); + _parameterBinding.bind(m_panning, Virus::Param_PanoramaLfo2Amount); + _parameterBinding.bind(m_shape12, Virus::Param_OscShapeLfo2Amount); + _parameterBinding.bind(m_fmAmount, Virus::Param_FmAmountLfo2Amount); } -LfoEditor::LfoThree::LfoThree(VirusParameterBinding& _parameterBinding) : LfoBase(_parameterBinding) +LfoEditor::LfoThree::LfoThree(VirusParameterBinding& _parameterBinding) : LfoBase(_parameterBinding, 2) { setupRotary(*this, m_fadeIn); m_rate.setBounds(107, 22, knobSize, knobSize); @@ -84,6 +124,8 @@ LfoEditor::LfoThree::LfoThree(VirusParameterBinding& _parameterBinding) : LfoBas m_keytrack.setBounds(m_rate.getBounds().translated(0, knobSize + 6)); m_amount.setBounds(307, 22, knobSize, knobSize); m_assignDest.setBounds(393, 45, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_fadeIn, Virus::Param_Lfo3FadeInTime); } LfoEditor::ModMatrix::ModMatrix(VirusParameterBinding& _parameterBinding) @@ -97,6 +139,38 @@ LfoEditor::ModMatrix::ModMatrix(VirusParameterBinding& _parameterBinding) setupSlot(3, {{255, 214}}, {320, 190}); setupSlot(4, {{255, 338}}, {320, 314}); setupSlot(5, {{255, 462}}, {320, 439}); + + // slot 0 is assign2, slot 1 is assign3, then 1,4,5,6 + _parameterBinding.bind(m_modMatrix[0]->m_source, Virus::Param_Assign2Source); + _parameterBinding.bind(m_modMatrix[1]->m_source, Virus::Param_Assign3Source); + _parameterBinding.bind(m_modMatrix[2]->m_source, Virus::Param_Assign1Source); + _parameterBinding.bind(m_modMatrix[3]->m_source, Virus::Param_Assign4Source); + _parameterBinding.bind(m_modMatrix[4]->m_source, Virus::Param_Assign5Source); + _parameterBinding.bind(m_modMatrix[5]->m_source, Virus::Param_Assign6Source); + + _parameterBinding.bind(m_modMatrix[0]->m_destinations[0]->m_amount, Virus::Param_Assign2Amount1); + _parameterBinding.bind(m_modMatrix[0]->m_destinations[1]->m_amount, Virus::Param_Assign2Amount2); + _parameterBinding.bind(m_modMatrix[0]->m_destinations[2]->m_amount, Virus::Param_Assign2Amount3); + + _parameterBinding.bind(m_modMatrix[1]->m_destinations[0]->m_amount, Virus::Param_Assign3Amount1); + _parameterBinding.bind(m_modMatrix[1]->m_destinations[1]->m_amount, Virus::Param_Assign3Amount2); + + _parameterBinding.bind(m_modMatrix[2]->m_destinations[0]->m_amount, Virus::Param_Assign1Amount); + + _parameterBinding.bind(m_modMatrix[3]->m_destinations[0]->m_amount, Virus::Param_Assign4Amount); + _parameterBinding.bind(m_modMatrix[4]->m_destinations[0]->m_amount, Virus::Param_Assign5Amount); + _parameterBinding.bind(m_modMatrix[5]->m_destinations[0]->m_amount, Virus::Param_Assign6Amount); + + _parameterBinding.bind(m_modMatrix[0]->m_destinations[0]->m_dest, Virus::Param_Assign2Destination1); + _parameterBinding.bind(m_modMatrix[0]->m_destinations[1]->m_dest, Virus::Param_Assign2Destination2); + _parameterBinding.bind(m_modMatrix[0]->m_destinations[2]->m_dest, Virus::Param_Assign2Destination3); + _parameterBinding.bind(m_modMatrix[1]->m_destinations[0]->m_dest, Virus::Param_Assign3Destination1); + _parameterBinding.bind(m_modMatrix[1]->m_destinations[1]->m_dest, Virus::Param_Assign3Destination2); + + _parameterBinding.bind(m_modMatrix[2]->m_destinations[0]->m_dest, Virus::Param_Assign1Destination); + _parameterBinding.bind(m_modMatrix[3]->m_destinations[0]->m_dest, Virus::Param_Assign4Destination); + _parameterBinding.bind(m_modMatrix[4]->m_destinations[0]->m_dest, Virus::Param_Assign5Destination); + _parameterBinding.bind(m_modMatrix[5]->m_destinations[0]->m_dest, Virus::Param_Assign6Destination); } void LfoEditor::ModMatrix::setupSlot(int slotNum, std::initializer_list<juce::Point<int>> destsPos, diff --git a/source/jucePlugin/ui/Virus_LfoEditor.h b/source/jucePlugin/ui/Virus_LfoEditor.h @@ -13,18 +13,18 @@ public: private: struct LfoBase : juce::Component { - LfoBase(VirusParameterBinding& _parameterBinding); + LfoBase(VirusParameterBinding& _parameterBinding, uint8_t _lfoIndex); juce::Slider m_rate; juce::Slider m_keytrack; juce::Slider m_amount; - Buttons::HandleButton m_subWaveform; + Buttons::HandleButton m_mode; juce::ComboBox m_shape, m_clock; juce::ComboBox m_assignDest; }; struct LfoTwoOneShared : LfoBase { - LfoTwoOneShared(VirusParameterBinding& _parameterBinding); + LfoTwoOneShared(VirusParameterBinding& _parameterBinding, uint8_t _lfoIndex); juce::Slider m_contour; juce::Slider m_phase; Buttons::LfoButton m_envMode; diff --git a/source/jucePlugin/ui/Virus_LookAndFeel.cpp b/source/jucePlugin/ui/Virus_LookAndFeel.cpp @@ -61,5 +61,9 @@ void LookAndFeel::drawComboBox (Graphics& g, int width, int height, bool isButto { // panels draws combo box... so it's invisible :) } +void LookAndFeel::drawButtonBackground(juce::Graphics &, juce::Button &, const juce::Colour &backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) +{ + +} } // namespace Virus diff --git a/source/jucePlugin/ui/Virus_LookAndFeel.h b/source/jucePlugin/ui/Virus_LookAndFeel.h @@ -27,6 +27,8 @@ namespace Virus void drawComboBox (juce::Graphics&, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox&) override; + void drawButtonBackground(juce::Graphics &, juce::Button &, const juce::Colour &backgroundColour, bool shouldDrawButtonAsHighlighted, + bool shouldDrawButtonAsDown) override; private: std::unique_ptr<juce::Drawable> m_genKnob, m_genPol, m_genBlue, m_genRed, m_multi; juce::NormalisableRange<float> m_knobImageSteps; diff --git a/source/jucePlugin/ui/Virus_OscEditor.cpp b/source/jucePlugin/ui/Virus_OscEditor.cpp @@ -57,11 +57,11 @@ OscEditor::OscOne::OscOne(VirusParameterBinding& _parameterBinding, uint32_t _os addAndMakeVisible(m_waveSelect); m_waveSelect.setBounds (18, 42, comboBoxWidth, comboBoxHeight); - _parameterBinding.bind(m_semitone, _oscIndex == 0 ? Virus::Param_Osc1Semitone : Virus::Param_Osc2Semitone); - _parameterBinding.bind(m_shape, _oscIndex == 0 ? Virus::Param_Osc1Shape : Virus::Param_Osc2Shape); - _parameterBinding.bind(m_pulseWidth, _oscIndex == 0 ? Virus::Param_Osc1PW : Virus::Param_Osc2PW); - _parameterBinding.bind(m_keyFollow, _oscIndex == 0 ? Virus::Param_Osc1Keyfollow : Virus::Param_Osc2Keyfollow); -// _parameterBinding.bind(m_waveSelect, _oscIndex == 0 ? Virus::Param_Osc1Wave : Virus::Param_Osc2Wave); + _parameterBinding.bind(m_semitone, _oscIndex == 0 ? Virus::Param_Osc1Semitone : Virus::Param_Osc2Semitone); + _parameterBinding.bind(m_shape, _oscIndex == 0 ? Virus::Param_Osc1Shape : Virus::Param_Osc2Shape); + _parameterBinding.bind(m_pulseWidth, _oscIndex == 0 ? Virus::Param_Osc1PW : Virus::Param_Osc2PW); + _parameterBinding.bind(m_keyFollow, _oscIndex == 0 ? Virus::Param_Osc1Keyfollow : Virus::Param_Osc2Keyfollow); + _parameterBinding.bind(m_waveSelect, _oscIndex == 0 ? Virus::Param_Osc1Wave : Virus::Param_Osc2Wave); } OscEditor::OscTwo::OscTwo(VirusParameterBinding& _parameterBinding) : OscOne(_parameterBinding, 1) @@ -74,6 +74,12 @@ OscEditor::OscTwo::OscTwo(VirusParameterBinding& _parameterBinding) : OscOne(_pa m_envOsc2.setBounds(m_envFm.getBounds().translated(66, 0)); addAndMakeVisible (m_fmMode); m_fmMode.setBounds (18, 140, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_fmAmount, Virus::Param_Osc2FMAmount); + _parameterBinding.bind(m_detune, Virus::Param_Osc2Detune); + _parameterBinding.bind(m_envFm, Virus::Param_FMFiltEnvAmt); + _parameterBinding.bind(m_envOsc2, Virus::Param_Osc2FltEnvAmt); + _parameterBinding.bind(m_fmMode, Virus::Param_OscFMMode); } OscEditor::OscThree::OscThree(VirusParameterBinding& _parameterBinding) @@ -86,6 +92,11 @@ OscEditor::OscThree::OscThree(VirusParameterBinding& _parameterBinding) m_level.setBounds(m_detune.getBounds().translated(63, 0)); addAndMakeVisible (m_oscThreeMode); m_oscThreeMode.setBounds (18, 43, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_semitone, Virus::Param_Osc3Semitone); + _parameterBinding.bind(m_detune, Virus::Param_Osc3Detune); + _parameterBinding.bind(m_level, Virus::Param_Osc3Volume); + _parameterBinding.bind(m_oscThreeMode, Virus::Param_Osc3Mode); } OscEditor::Unison::Unison(VirusParameterBinding& _parameterBinding) @@ -98,6 +109,12 @@ OscEditor::Unison::Unison(VirusParameterBinding& _parameterBinding) m_phaseInit.setBounds(m_lfoPhase.getBounds().translated(66, 0)); addAndMakeVisible (m_unisonVoices); m_unisonVoices.setBounds (18, 42, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_detune, Virus::Param_UnisonDetune); + _parameterBinding.bind(m_panSpread, Virus::Param_UnisonPanSpread); + _parameterBinding.bind(m_lfoPhase, Virus::Param_UnisonLfoPhase); + _parameterBinding.bind(m_phaseInit, Virus::Param_OscInitPhase); + _parameterBinding.bind(m_unisonVoices, Virus::Param_UnisonMode); } OscEditor::Mixer::Mixer(VirusParameterBinding& _parameterBinding) @@ -107,6 +124,9 @@ OscEditor::Mixer::Mixer(VirusParameterBinding& _parameterBinding) m_oscBalance.setBounds(14, 18, knobSize, knobSize); m_oscLevel.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); m_oscLevel.setBounds(m_oscBalance.getBounds().translated(knobSize, 0)); + + _parameterBinding.bind(m_oscBalance, Virus::Param_OscBalance); + _parameterBinding.bind(m_oscLevel, Virus::Param_OscMainVolume); } OscEditor::RingMod::RingMod(VirusParameterBinding& _parameterBinding) @@ -118,6 +138,10 @@ OscEditor::RingMod::RingMod(VirusParameterBinding& _parameterBinding) m_ringModLevel.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); m_ringModLevel.setBounds(m_noiseLevel.getBounds().translated(knobSize, 0)); m_noiseColor.setBounds(m_noiseLevel.getBounds().translated(0, 95)); + + _parameterBinding.bind(m_noiseLevel, Virus::Param_NoiseVolume); + _parameterBinding.bind(m_ringModLevel, Virus::Param_RingModMVolume); + _parameterBinding.bind(m_noiseColor, Virus::Param_NoiseColor); } OscEditor::Sub::Sub(VirusParameterBinding& _parameterBinding) @@ -127,15 +151,20 @@ OscEditor::Sub::Sub(VirusParameterBinding& _parameterBinding) m_level.setBounds(14 + knobSize, 20, knobSize, knobSize); m_subWaveform.setBounds(30, 34, Buttons::HandleButton::kWidth, Buttons::HandleButton::kHeight); addAndMakeVisible(m_subWaveform); + + _parameterBinding.bind(m_level, Virus::Param_SubOscVolume); + _parameterBinding.bind(m_subWaveform, Virus::Param_SubOscShape); } OscEditor::Portamento::Portamento(VirusParameterBinding& _parameterBinding) { setupRotary(*this, m_portamento); m_portamento.setBounds(12, 18, knobSize, knobSize); + + _parameterBinding.bind(m_portamento, Virus::Param_PortamentoTime); } -OscEditor::Filters::Filters(VirusParameterBinding& _parameterBinding) : m_filter{_parameterBinding,_parameterBinding}, m_link1(true), m_link2(true) +OscEditor::Filters::Filters(VirusParameterBinding &_parameterBinding) : m_filter{Filter(_parameterBinding,0),Filter(_parameterBinding,1)}, m_link1(true), m_link2(true) { addAndMakeVisible(m_filter[0]); addAndMakeVisible(m_filter[1]); @@ -162,9 +191,19 @@ OscEditor::Filters::Filters(VirusParameterBinding& _parameterBinding) : m_filter m_filterRouting.setBounds (m_filterMode[0].getBounds().translated (184, 0)); m_saturationCurve.setBounds (m_filterMode[1].getBounds().translated (184, 0)); m_keyFollowBase.setBounds (m_filterMode[0].getBounds().translated (286, 0)); + + _parameterBinding.bind(m_filterBalance, Virus::Param_FilterBalance); + _parameterBinding.bind(m_filterMode[0], Virus::Param_FilterModeA); + _parameterBinding.bind(m_filterMode[1], Virus::Param_FilterModeB); + _parameterBinding.bind(m_filterRouting, Virus::Param_FilterRouting); + _parameterBinding.bind(m_saturationCurve, Virus::Param_SaturationCurve); + _parameterBinding.bind(m_keyFollowBase, Virus::Param_FilterKeyFollowA); // maybe not right + _parameterBinding.bind(m_link1, Virus::Param_Filter2CutoffLink); + _parameterBinding.bind(m_envPol[0].m_pos, Virus::Param_Filter1EnvPolarity); + _parameterBinding.bind(m_envPol[1].m_pos, Virus::Param_Filter2EnvPolarity); } -OscEditor::Filters::Filter::Filter(VirusParameterBinding& _parameterBinding) +OscEditor::Filters::Filter::Filter(VirusParameterBinding& _parameterBinding, const uint8_t _fltIndex) { for (auto *s : {&m_cutoff, &m_res, &m_envAmount, &m_keyTrack, &m_resVel, &m_envVel}) setupRotary(*this, *s); @@ -174,9 +213,16 @@ OscEditor::Filters::Filter::Filter(VirusParameterBinding& _parameterBinding) m_keyTrack.setBounds(m_envAmount.getBounds().translated(knobSize - 5, 0)); m_resVel.setBounds(m_keyTrack.getBounds().translated(knobSize - 5, 0)); m_envVel.setBounds(m_resVel.getBounds().translated(knobSize - 5, 0)); + + _parameterBinding.bind(m_cutoff, _fltIndex == 0 ? Virus::Param_FilterCutA : Virus::Param_FilterCutB); + _parameterBinding.bind(m_res, _fltIndex == 0 ? Virus::Param_FilterResA: Virus::Param_FilterResB); + _parameterBinding.bind(m_envAmount, _fltIndex == 0 ? Virus::Param_FilterEnvAmtA : Virus::Param_FilterEnvAmtB); + _parameterBinding.bind(m_keyTrack, _fltIndex == 0 ? Virus::Param_FilterKeyFollowA : Virus::Param_FilterKeyFollowB); + _parameterBinding.bind(m_resVel, _fltIndex == 0 ? Virus::Param_Resonance1Velocity : Virus::Param_Resonance2Velocity); + _parameterBinding.bind(m_envVel, _fltIndex == 0 ? Virus::Param_Filter1EnvAmtVelocity : Virus::Param_Filter2EnvAmtVelocity); } -OscEditor::Envelope::Envelope(VirusParameterBinding& _parameterBinding) +OscEditor::Envelope::Envelope(VirusParameterBinding& _parameterBinding, Virus::EnvelopeType _envIndex) { for (auto *s : {&m_attack, &m_decay, &m_sustain, &m_time, &m_release}) setupRotary(*this, *s); @@ -185,4 +231,15 @@ OscEditor::Envelope::Envelope(VirusParameterBinding& _parameterBinding) m_sustain.setBounds(m_decay.getBounds().translated(knobSize + 9, 0)); m_time.setBounds(m_sustain.getBounds().translated(knobSize + 9, 0)); m_release.setBounds(m_time.getBounds().translated(knobSize + 12, 0)); + + _parameterBinding.bind(m_attack, + _envIndex == Virus::Env_Amp ? Virus::Param_AmpEnvAttack : Virus::Param_FilterEnvAttack); + _parameterBinding.bind(m_decay, + _envIndex == Virus::Env_Amp ? Virus::Param_AmpEnvDecay : Virus::Param_FilterEnvDecay); + _parameterBinding.bind(m_sustain, + _envIndex == Virus::Env_Amp ? Virus::Param_AmpEnvSustain : Virus::Param_FilterEnvSustain); + _parameterBinding.bind(m_release, + _envIndex == Virus::Env_Amp ? Virus::Param_AmpEnvRelease : Virus::Param_FilterEnvRelease); + _parameterBinding.bind(m_time, + _envIndex == Virus::Env_Amp ? Virus::Param_AmpEnvSustainTime : Virus::Param_FilterEnvSustainTime); } diff --git a/source/jucePlugin/ui/Virus_OscEditor.h b/source/jucePlugin/ui/Virus_OscEditor.h @@ -82,7 +82,7 @@ private: Filters(VirusParameterBinding& _parameterBinding); struct Filter : juce::Component { - Filter(VirusParameterBinding& _parameterBinding); + Filter(VirusParameterBinding& _parameterBinding, uint8_t fltIndex); juce::Slider m_cutoff; juce::Slider m_res; juce::Slider m_envAmount; @@ -90,7 +90,7 @@ private: juce::Slider m_resVel; juce::Slider m_envVel; }; - std::array<Filter, 2> m_filter; + std::array<Filter, 2> m_filter; juce::Slider m_filterBalance; Buttons::EnvPol m_envPol[2]; Buttons::LinkButton m_link1, m_link2; @@ -100,7 +100,7 @@ private: struct Envelope : juce::Component { - Envelope(VirusParameterBinding& _parameterBinding); + Envelope(VirusParameterBinding &_parameterBinding, Virus::EnvelopeType _envIndex); juce::Slider m_attack; juce::Slider m_decay; juce::Slider m_sustain; @@ -110,13 +110,13 @@ private: struct FilterEnv : Envelope { - FilterEnv(VirusParameterBinding& _parameterBinding) : Envelope(_parameterBinding) {} + FilterEnv(VirusParameterBinding& _parameterBinding) : Envelope(_parameterBinding, Virus::EnvelopeType::Env_Filter) {} } m_filterEnv; struct AmpEnv : Envelope { - AmpEnv(VirusParameterBinding& _parameterBinding) : Envelope(_parameterBinding) {} - } m_ampEnv; + AmpEnv(VirusParameterBinding &_parameterBinding) : Envelope(_parameterBinding, Virus::EnvelopeType::Env_Amp) {} + } m_ampEnv; Buttons::SyncButton m_oscSync; std::unique_ptr<juce::Drawable> m_background; diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.cpp b/source/jucePlugin/ui/Virus_PatchBrowser.cpp @@ -0,0 +1,207 @@ +#include "../VirusParameterBinding.h" +#include "BinaryData.h" +#include "Ui_Utils.h" +#include "Virus_PatchBrowser.h" +#include <juce_gui_extra/juce_gui_extra.h> +using namespace juce; +constexpr auto comboBoxWidth = 98; +const juce::Array<juce::String> categories = {"", "Lead", "Bass", "Pad", "Decay", "Pluck", + "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion", + "Input", "Vocoder", "Favourite 1", "Favourite 2", "Favourite 3"}; + +PatchBrowser::PatchBrowser(VirusParameterBinding & _parameterBinding, Virus::Controller& _controller) : + m_parameterBinding(_parameterBinding), + m_controller(_controller), + m_patchList("Patch browser"), + m_fileFilter("*.syx;*.mid;*.midi", "*", "virus patch dumps"), + m_bankList(FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, NULL) +{ + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300 Emulator"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300 Emulator"; + opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; + m_properties = new juce::PropertiesFile(opts); + + auto bankDir = m_properties->getValue("virus_bank_dir", ""); + if (bankDir != "" && juce::File(bankDir).isDirectory()) + { + m_bankList.setRoot(bankDir); + } + + setBounds(22, 30, 1000, 570); + m_bankList.setBounds(16, 28, 480, 540); + + m_patchList.setBounds(m_bankList.getBounds().translated(m_bankList.getWidth(), 0)); + + m_patchList.getHeader().addColumn("#", 0, 32); + m_patchList.getHeader().addColumn("Name", 1, 150); + m_patchList.getHeader().addColumn("Category1", 2, 100); + m_patchList.getHeader().addColumn("Category2", 3, 100); + m_patchList.getHeader().addColumn("Arp", 4, 32); + addAndMakeVisible(m_bankList); + addAndMakeVisible(m_patchList); + + m_bankList.addListener(this); + m_patchList.setModel(this); +} + +void PatchBrowser::selectionChanged() {} + +void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e) +{ + auto ext = file.getFileExtension().toLowerCase(); + auto path = file.getParentDirectory().getFullPathName(); + m_properties->setValue("virus_bank_dir", path); + if (ext == ".syx") + { + m_patches.clear(); + + juce::MemoryBlock data; + file.loadFileAsData(data); + uint8_t index = 0; + for (auto it = data.begin(); it != data.end(); it += 267) + { + if ((it + 267) <= data.end()) + { + Patch patch; + patch.progNumber = index; + data.copyTo(patch.data, 267*index + 9, 256); + patch.name = parseAsciiText(patch.data, 128 + 112); + patch.category1 = patch.data[251]; + patch.category2 = patch.data[252]; + m_patches.add(patch); + index++; + } + } + m_patchList.updateContent(); + m_patchList.repaint(); // force repaint since row number doesn't often change + } + else if (ext == ".mid" || ext == ".midi") + { + m_patches.clear(); + juce::MemoryBlock data; + if (!file.loadFileAsData(data)) + { + return; + } + uint8_t index = 0; + + const uint8_t *ptr = (uint8_t *)data.getData(); + const auto end = ptr + data.getSize(); + + for (auto it = ptr; it < end; it += 1) + { + if ((uint8_t)*it == (uint8_t)0xf0 && (it + 267) < end) + { + if ((uint8_t) * (it + 1) == (uint8_t)0x00) + { + auto syx = Virus::SysEx(it, it + 267); + syx[7] = 0x01; // force to bank a + syx[266] = 0xf7; + + Patch patch; + patch.progNumber = index; + std::copy(syx.begin() + 9, syx.end() - 2, patch.data); + patch.name = parseAsciiText(patch.data, 128 + 112); + patch.category1 = patch.data[251]; + patch.category2 = patch.data[252]; + m_patches.add(patch); + index++; + it += 266; + } + else // some midi files have two bytes after the 0xf0 + { + auto syx = Virus::SysEx(); + syx.push_back(0xf0); + for (auto i = it + 3; i < it + 3 + 266; i++) + { + syx.push_back((uint8_t)*i); + } + syx[7] = 0x01; // force to bank a + syx[266] = 0xf7; + + Patch patch; + std::memcpy(patch.data, syx.data()+9, 256); + patch.progNumber = index; + patch.name = parseAsciiText(patch.data, 128 + 112); + patch.category1 = patch.data[251]; + patch.category2 = patch.data[252]; + m_patches.add(patch); + index++; + + it += 266; + } + + } + } + m_patchList.updateContent(); + m_patchList.repaint(); + } +} + +void PatchBrowser::fileDoubleClicked(const juce::File &file) {} + +void PatchBrowser::browserRootChanged(const File &newRoot) {} + +int PatchBrowser::getNumRows() { return m_patches.size(); } + +void PatchBrowser::paintRowBackground(Graphics &g, int rowNumber, int width, int height, bool rowIsSelected) { + auto alternateColour = getLookAndFeel() + .findColour(juce::ListBox::backgroundColourId) + .interpolatedWith(getLookAndFeel().findColour(juce::ListBox::textColourId), 0.03f); + if (rowIsSelected) + g.fillAll(juce::Colours::lightblue); + else if (rowNumber % 2) + g.fillAll(alternateColour); +} + +void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width, int height, bool rowIsSelected) { + g.setColour(rowIsSelected ? juce::Colours::darkblue + : getLookAndFeel().findColour(juce::ListBox::textColourId)); // [5] + + auto rowElement = m_patches[rowNumber]; + //auto text = rowElement.name; + juce::String text = ""; + if (columnId == 0) + text = juce::String(rowElement.progNumber); + else if (columnId == 1) + text = rowElement.name; + else if (columnId == 2) + text = categories[rowElement.category1]; + else if (columnId == 3) + text = categories[rowElement.category2]; + else if (columnId == 4) + text = rowElement.data[129] != 0 ? "Y" : " "; + g.drawText(text, 2, 0, width - 4, height, juce::Justification::centredLeft, true); // [6] + g.setColour(getLookAndFeel().findColour(juce::ListBox::backgroundColourId)); + g.fillRect(width - 1, 0, 1, height); // [7] +} + +void PatchBrowser::selectedRowsChanged(int lastRowSelected) { + auto idx = m_patchList.getSelectedRow(); + uint8_t syxHeader[9] = {0xF0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x10, 0x00, 0x00}; + syxHeader[8] = m_controller.isMultiMode() ? m_parameterBinding.m_part : virusLib::ProgramType::SINGLE; // set edit buffer + const uint8_t syxEof = 0xF7; + uint8_t cs = syxHeader[5] + syxHeader[6] + syxHeader[7] + syxHeader[8]; + uint8_t data[256]; + for (int i = 0; i < 256; i++) + { + data[i] = m_patches[idx].data[i]; + cs += data[i]; + } + cs = cs & 0x7f; + Virus::SysEx syx; + for (auto i : syxHeader) + { + syx.push_back(i); + } + for (auto i : data) + { + syx.push_back(i); + } + syx.push_back(cs); + syx.push_back(syxEof); + m_controller.sendSysEx(syx); // send to edit buffer + m_controller.parseMessage(syx); // update ui +} diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.h b/source/jucePlugin/ui/Virus_PatchBrowser.h @@ -0,0 +1,55 @@ +#pragma once + +#include "../PluginProcessor.h" +#include "Virus_Buttons.h" +#include <juce_gui_extra/juce_gui_extra.h> +#include "../VirusController.h" +class VirusParameterBinding; + +class PatchBrowser : public juce::Component, juce::FileBrowserListener, juce::TableListBoxModel +{ + +public: + PatchBrowser(VirusParameterBinding &_parameterBinding, Virus::Controller& _controller); + +private: + VirusParameterBinding &m_parameterBinding; + Virus::Controller& m_controller; + struct Patch + { + uint8_t progNumber; + juce::String name; + uint8_t category1; + uint8_t category2; + uint8_t data[256]; + + }; + template <typename T> juce::String parseAsciiText(const T &msg, const int start) const + { + char text[Virus::Controller::kNameLength + 1]; + text[Virus::Controller::kNameLength] = 0; // termination + for (auto pos = 0; pos < Virus::Controller::kNameLength; ++pos) + text[pos] = msg[start + pos]; + return juce::String(text); + } + juce::WildcardFileFilter m_fileFilter; + juce::FileBrowserComponent m_bankList; + juce::TableListBox m_patchList; + juce::Array<Patch> m_patches; + juce::PropertiesFile *m_properties; + + // Inherited via FileBrowserListener + void selectionChanged() override; + void fileClicked(const juce::File &file, const juce::MouseEvent &e) override; + void fileDoubleClicked(const juce::File &file) override; + void browserRootChanged(const juce::File &newRoot) override; + + // Inherited via TableListBoxModel + virtual int getNumRows() override; + virtual void paintRowBackground(juce::Graphics &, int rowNumber, int width, int height, + bool rowIsSelected) override; + virtual void paintCell(juce::Graphics &, int rowNumber, int columnId, int width, int height, + bool rowIsSelected) override; + + virtual void selectedRowsChanged(int lastRowSelected) override; +};