commit a98176b35343291d0178d8e9b01985582f12b349 parent a5be363b8dd886bc9dd437ddf7d03fa4af1ec73c Author: dsp56300 <87139854+dsp56300@users.noreply.github.com> Date: Mon, 17 Jan 2022 22:43:20 +0100 Merge pull request #33 from 790/ui more ui improvements + version bump Diffstat:
24 files changed, 443 insertions(+), 185 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.3) +project(gearmulator VERSION 1.2.4) include(base.cmake) diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -70,6 +70,8 @@ juce_add_binary_data(jucePlugin_BinaryData "assets/panels/bg_fx_1018x620.png" "assets/panels/bg_lfo_1018x620.png" "assets/panels/bg_osc_1018x620.png" + "assets/panels/bg_fxreverb_481x234.png" + "assets/panels/bg_fxdelay_481x234.png" "assets/buttons/GLOBAL_btn_arp_settings_141x26.png" "assets/buttons/GLOBAL_btn_effects_141x26.png" "assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png" @@ -103,6 +105,7 @@ target_link_libraries(jucePlugin PRIVATE jucePlugin_BinaryData juce::juce_audio_utils + juce::juce_cryptography PUBLIC virusLib #juce::juce_recommended_config_flags diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp @@ -7,13 +7,33 @@ //============================================================================== AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) : - AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p), m_virusEditor(new VirusEditor(m_parameterBinding, processorRef)) + AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p), m_virusEditor(new VirusEditor(m_parameterBinding, processorRef)), m_scale("Scale") { ignoreUnused (processorRef); setSize(1377, 800); + const auto config = processorRef.getController().getConfig(); + auto scale = config->getIntValue("scale", 100); m_virusEditor->setTopLeftPosition(0, 0); + m_scale.setBounds(0,0,74,24); + m_scale.addItem("50%", 50); + m_scale.addItem("75%", 75); + m_scale.addItem("100%", 100); + m_scale.addItem("125%", 125); + m_scale.addItem("150%", 150); + m_scale.addItem("200%", 200); + + m_scale.setSelectedId(scale, juce::dontSendNotification); + m_scale.setColour(juce::ComboBox::textColourId, juce::Colours::white); + m_scale.onChange = [this, config]() { + float value = m_scale.getSelectedIdAsValue().getValue(); + setScaleFactor(value/100.0f); + config->setValue("scale", (int)value); + config->saveIfNeeded(); + }; + setScaleFactor(scale/100.0f); addAndMakeVisible(m_virusEditor); + addAndMakeVisible(m_scale); } AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h @@ -26,6 +26,7 @@ private: // New "real" editor VirusEditor *m_virusEditor; + juce::ComboBox m_scale; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -14,7 +14,13 @@ namespace Virus Controller::Controller(AudioPluginAudioProcessor &p, unsigned char deviceId) : m_processor(p), m_deviceId(deviceId) { assert(sizeof(m_paramsDescription) / sizeof(Parameter::Description) == Param_Count && "size of enum must match size of parameter descriptions"); - + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300 Emulator"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300 Emulator"; + opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; + m_config = new juce::PropertiesFile(opts); + registerParams(); // add lambda to enforce updating patches when virus switch from/to multi/single. (findSynthParam(0, 0x72, 0x7a))->onValueChanged = [this] { @@ -342,8 +348,15 @@ namespace Virus const auto ch = patch.progNumber == 0x40 ? 0 : patch.progNumber; for (auto i = 0; i < kDataSizeInBytes; i++) { - if (auto *p = findSynthParam(ch, i > bankSize ? 0x71 : 0x70, i % bankSize)) - p->setValueFromSynth(patch.data[i], true); + if (auto *p = findSynthParam(ch, i > bankSize ? 0x71 : 0x70, i % bankSize)) { + if((p->getDescription().classFlags & Parameter::MULTI_OR_SINGLE) && isMultiMode()) + continue; + else + p->setValueFromSynth(patch.data[i], true); + } + } + if (onProgramChange) { + onProgramChange(); } } else @@ -382,9 +395,11 @@ namespace Virus if (auto* p = findSynthParam(pt, virusLib::PAGE_B, virusLib::CLOCK_TEMPO)) { p->setValueFromSynth(patch.data[virusLib::MD_CLOCK_TEMPO], true); } - if (auto* p = findSynthParam(pt, virusLib::PAGE_A, virusLib::EFFECT_SEND)) { +/* if (auto* p = findSynthParam(pt, virusLib::PAGE_A, virusLib::EFFECT_SEND)) { p->setValueFromSynth(patch.data[virusLib::MD_PART_EFFECT_SEND], true); - } + }*/ + m_currentBank[pt] = (virusLib::BankNumber)(patch.data[virusLib::MD_PART_BANK_NUMBER + pt]+1); + m_currentProgram[pt] = patch.data[virusLib::MD_PART_PROGRAM_NUMBER + pt]; } } if (hasChecksum) @@ -1375,7 +1390,7 @@ namespace Virus const auto ridx = juce::roundToInt(v); switch (ridx) { -// case 0: return "Off"; + case 0: return ""; case 1: return "1 Stage"; case 2: return "2 Stages"; case 3: return "3 Stages"; @@ -1545,7 +1560,7 @@ namespace Virus {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 94, "Key Mode", {0,5}, numToKeyMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 97, "Unison Mode", {0,15}, numToUnisonMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 98, "Unison Detune", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 99, "Unison Panorama Spread", {0,127}, {},{}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 99, "Unison Pan Spread", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 100, "Unison Lfo Phase", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 101, "Input Mode", {0,3}, numToInputMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 102, "Input Select", {0,8}, numToInputSelect,{}, true, true, false}, @@ -1555,12 +1570,12 @@ namespace Virus {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 108, "Chorus Delay", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 109, "Chorus Feedback", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 110, "Chorus Lfo Shape", {0,67}, numToLfoShape, {}, true, true, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A, 112, "Delay/Reverb Mode", {0,26}, numToDelayReverbMode, {}, true, true, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE, 112, "Delay/Reverb Mode", {0,26}, numToDelayReverbMode, {}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE, 113, "Effect Send", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 114, "Delay Time", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 115, "Delay Feedback", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 116, "Delay Rate / Reverb Decay Time", {0,127}, {},{}, true, false, false}, - {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 117, "Delay Depth / Reverb Room Size", {0,127}, numToReverbRoomSize,{}, true, true, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 116, "Dly Rate / Rev Decay", {0,127}, {},{}, true, false, false}, + {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 117, "Dly Depth / Rev Size", {0,127}, numToReverbRoomSize,{}, true, true, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Delay Lfo Shape", {0,127}, numToDelayLfoShape, {}, true, true, false}, // {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 118, "Reverb Damping", {0,127}, {},{}, true, false, false}, {Parameter::Page::A, Parameter::Class::SOUNDBANK_A|Parameter::Class::MULTI_OR_SINGLE|Parameter::Class::NON_PART_SENSITIVE, 119, "Delay Color", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, @@ -1658,7 +1673,7 @@ namespace Virus {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 101, "Distortion Intensity", {0,127}, {},{}, true, false, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 103, "Assign 4 Source", {0,27}, numToModMatrixSource,{}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 104, "Assign 4 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, - {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 105, "Assign 4 Amount", {0,127}, {},{}, true, false, false}, + {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 105, "Assign 4 Amount", {0,127}, {},{}, true, false, false, true}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 106, "Assign 5 Source", {0,27}, numToModMatrixSource, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 107, "Assign 5 Destination", {0,122}, numToModMatrixDest, {}, true, true, false}, {Parameter::Page::B, Parameter::Class::SOUNDBANK_B|Parameter::Class::VIRUS_C, 108, "Assign 5 Amount", {0,127}, paramTo7bitSigned, textTo7bitSigned, true, false, false, true}, diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -1,6 +1,7 @@ #pragma once #include <juce_audio_processors/juce_audio_processors.h> +#include <juce_gui_extra/juce_gui_extra.h> #include "../synthLib/plugin.h" #include "VirusParameter.h" @@ -395,7 +396,7 @@ namespace Virus static constexpr auto kNameLength = 10; Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00); - + ~Controller() { stopTimer(); } // this is called by the plug-in on audio thread! void dispatchVirusOut(const std::vector<synthLib::SMidiEvent> &); @@ -426,6 +427,8 @@ namespace Virus void parseMessage(const SysEx &); void sendSysEx(const SysEx &); void onStateLoaded(); + juce::PropertiesFile* getConfig() { return m_config; } + std::function<void()> onProgramChange = {}; private: void timerCallback() override; static constexpr size_t kDataSizeInBytes = 256; // same for multi and single @@ -493,6 +496,7 @@ namespace Virus unsigned char m_deviceId; virusLib::BankNumber m_currentBank[16]; uint8_t m_currentProgram[16]; - uint8_t m_currentPart; + uint8_t m_currentPart = 0; + juce::PropertiesFile *m_config; }; }; // namespace Virus diff --git a/source/jucePlugin/VirusParameter.h b/source/jucePlugin/VirusParameter.h @@ -57,7 +57,21 @@ namespace Virus float getValue() const override { return convertTo0to1(m_value.getValue()); } void setValue(float newValue) override { return m_value.setValue(convertFrom0to1(newValue)); }; void setValueFromSynth(int newValue, bool notifyHost = true); - float getDefaultValue() const override { return m_desc.isBipolar ? 64.0f : 0.0f; /* maybe return from ROM state? */ } + float getDefaultValue() const override { + // default value should probably be in description instead of hardcoded like this. + if (m_desc.index == 21 || m_desc.index == 31) { // osc keyfollows + return 64+32; + } + else if (m_desc.index == 36) // osc vol / saturation + return 64; + else if (m_desc.index == 40) // filter1 cutoffs + return 127; + else if(m_desc.index == 41) //filter 2 + return 64; + else if(m_desc.index == 91) // patch volume + return 100; + return m_desc.isBipolar ? 64.0f : 0.0f; /* maybe return from ROM state? */ + } bool isDiscrete() const override { return m_desc.isDiscrete; } bool isBoolean() const override { return m_desc.isBool; } bool isBipolar() const { return m_desc.isBipolar; } diff --git a/source/jucePlugin/VirusParameterBinding.cpp b/source/jucePlugin/VirusParameterBinding.cpp @@ -1,7 +1,7 @@ #include "VirusParameterBinding.h" #include "VirusParameter.h" #include "PluginProcessor.h" - +class Parameter; VirusParameterBinding::~VirusParameterBinding() { @@ -31,8 +31,8 @@ void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _pa assert(false && "Failed to find parameter"); return; } + _slider.addMouseListener(new VirusParameterBindingMouseListener(v, _slider), false); const auto range = v->getNormalisableRange(); - _slider.setRange(range.start, range.end, range.interval); _slider.setDoubleClickReturnValue(true, v->getDefaultValue()); _slider.getValueObject().referTo(v->getValueObject()); @@ -42,7 +42,6 @@ void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _pa _slider.getProperties().set("bipolar", true); } } - void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param) { bind(_combo, _param, m_processor.getController().getCurrentPart()); } @@ -55,6 +54,7 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _p return; } _combo.setTextWhenNothingSelected("--"); + _combo.setScrollWheelEnabled(true); int idx = 1; for (auto vs : v->getAllValueStrings()) { if(vs.isNotEmpty()) @@ -64,9 +64,11 @@ void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _p //_combo.addItemList(v->getAllValueStrings(), 1); _combo.setSelectedId((int)v->getValueObject().getValueSource().getValue() + 1, juce::dontSendNotification); _combo.onChange = [this, &_combo, v]() { - v->getValueObject().getValueSource().setValue(_combo.getSelectedId() - 1); + v->beginChangeGesture(); + v->setValueNotifyingHost(v->convertTo0to1(v->getValueForText(_combo.getText()))); //.getSelectedId() - 1); + v->endChangeGesture(); + v->getValueObject().getValueSource().setValue((int)_combo.getSelectedId() - 1); }; - v->onValueChanged = [this, &_combo, v]() { _combo.setSelectedId((int)v->getValueObject().getValueSource().getValue() + 1, juce::dontSendNotification); }; m_bindings.add(v); } diff --git a/source/jucePlugin/VirusParameterBinding.h b/source/jucePlugin/VirusParameterBinding.h @@ -7,11 +7,23 @@ namespace juce { } class AudioPluginAudioProcessor; +class Parameter; +class VirusParameterBindingMouseListener : public juce::MouseListener +{ +public: + VirusParameterBindingMouseListener(Virus::Parameter* _param, juce::Slider &_slider) : m_param(_param), m_slider(&_slider) { + } + Virus::Parameter *m_param; + juce::Slider* m_slider; + void mouseDown(const juce::MouseEvent &event) { m_param->beginChangeGesture(); }; + void mouseUp(const juce::MouseEvent &event) { m_param->endChangeGesture(); }; + void mouseDrag(const juce::MouseEvent &event) { m_param->setValueNotifyingHost(m_param->convertTo0to1((float)m_slider->getValue())); }; +}; -class VirusParameterBinding +class VirusParameterBinding : juce::MouseListener { public: - VirusParameterBinding(AudioPluginAudioProcessor& _processor) : m_processor(_processor) + VirusParameterBinding(AudioPluginAudioProcessor &_processor) : m_processor(_processor) { } @@ -24,6 +36,7 @@ public: void bind(juce::ComboBox &_control, Virus::ParameterType _param, uint8_t _part); void bind(juce::DrawableButton &_control, Virus::ParameterType _param); void bind(juce::Component &_control, Virus::ParameterType _param); + AudioPluginAudioProcessor& m_processor; juce::Array<Virus::Parameter*> m_bindings; }; diff --git a/source/jucePlugin/assets/buttons/part_select_btc_18x36.png b/source/jucePlugin/assets/buttons/part_select_btc_18x36.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/assets/panels/bg_fxdelay_481x234.png b/source/jucePlugin/assets/panels/bg_fxdelay_481x234.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_fxreverb_481x234.png b/source/jucePlugin/assets/panels/bg_fxreverb_481x234.png Binary files differ. diff --git a/source/jucePlugin/ui/VirusEditor.cpp b/source/jucePlugin/ui/VirusEditor.cpp @@ -14,7 +14,16 @@ using namespace juce; constexpr auto kPanelWidth = 1377; constexpr auto kPanelHeight = 800; -static uint8_t currentTab = 3; +enum Tabs +{ + ArpSettings, + Effects, + LfoMatrix, + OscFilter, + Patches +}; +static uint8_t currentTab = Tabs::OscFilter; + VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAudioProcessor &_processorRef) : m_parameterBinding(_parameterBinding), processorRef(_processorRef), m_controller(processorRef.getController()), m_controlLabel("ctrlLabel", "") @@ -24,11 +33,12 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu m_background = Drawable::createFromImageData (BinaryData::bg_1377x800_png, BinaryData::bg_1377x800_pngSize); m_background->setBufferedToImage (true); + addAndMakeVisible (*m_background); addAndMakeVisible (m_mainButtons); m_arpEditor = std::make_unique<ArpEditor>(_parameterBinding); - m_fxEditor = std::make_unique<FxEditor>(_parameterBinding); + m_fxEditor = std::make_unique<FxEditor>(_parameterBinding, m_controller); m_lfoEditor = std::make_unique<LfoEditor>(_parameterBinding); m_oscEditor = std::make_unique<OscEditor>(_parameterBinding); m_patchBrowser = std::make_unique<PatchBrowser>(_parameterBinding, m_controller); @@ -43,19 +53,19 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu // show/hide section from buttons.. m_mainButtons.updateSection = [this]() { if (m_mainButtons.m_arpSettings.getToggleState()) { - currentTab = 0; + currentTab = Tabs::ArpSettings; } else if (m_mainButtons.m_effects.getToggleState()) { - currentTab = 1; + currentTab = Tabs::Effects; } else if (m_mainButtons.m_lfoMatrix.getToggleState()) { - currentTab = 2; + currentTab = Tabs::LfoMatrix; } else if (m_mainButtons.m_oscFilter.getToggleState()) { - currentTab = 3; + currentTab = Tabs::OscFilter; } else if (m_mainButtons.m_patches.getToggleState()) { - currentTab = 4; + currentTab = Tabs::Patches; } m_arpEditor->setVisible(m_mainButtons.m_arpSettings.getToggleState()); m_fxEditor->setVisible(m_mainButtons.m_effects.getToggleState()); @@ -83,12 +93,7 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu m_presetButtons.m_load.onClick = [this]() { loadFile(); }; m_presetButtons.m_save.onClick = [this]() { saveFile(); }; - 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); + m_properties = m_controller.getConfig(); auto midiIn = m_properties->getValue("midi_input", ""); auto midiOut = m_properties->getValue("midi_output", ""); if (midiIn != "") @@ -163,11 +168,8 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu m_patchName.onTextChange = [this]() { auto text = m_patchName.getText(); if(text.trim().length() > 0) { - if (text == "/pv") { // stupid debug thing to remove later - m_paramDisplayLocal = !m_paramDisplayLocal; - return; - } m_controller.setSinglePresetName(m_controller.getCurrentPart(), text); + m_partList->refreshParts(); } }; addAndMakeVisible(m_patchName); @@ -182,22 +184,27 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu addAndMakeVisible(m_controlLabel); + m_controller.onProgramChange = [this]() { + updateParts(); + m_partList->refreshParts(); + }; m_controller.getBankCount(); addMouseListener(this, true); startTimerHz(5); setSize (kPanelWidth, kPanelHeight); - // without this some combobox parameters are wrong on first load, no idea why. - //m_controller.getParameter(Virus::Param_PlayMode)->setValue(virusLib::PlayModeSingle); recreateControls(); } -VirusEditor::~VirusEditor() { setLookAndFeel(nullptr); } +VirusEditor::~VirusEditor() { stopTimer(); setLookAndFeel(nullptr); } void VirusEditor::timerCallback() { // ugly (polling!) way for refreshing presets names as this is temporary ui +} + +void VirusEditor::updateParts() { const auto multiMode = m_controller.isMultiMode(); for (auto pt = 0; pt < 16; pt++) { @@ -210,7 +217,6 @@ void VirusEditor::timerCallback() } } } - } void VirusEditor::updateMidiInput(int index) @@ -289,15 +295,14 @@ void VirusEditor::MainButtons::applyToMainButtons(std::function<void(DrawableBut action(section); } } -void VirusEditor::mouseDrag(const juce::MouseEvent & event) -{ - auto props = event.eventComponent->getProperties(); + +void VirusEditor::updateControlLabel(Component* eventComponent) { + auto props = eventComponent->getProperties(); if(props.contains("type") && props["type"] == "slider") { m_controlLabel.setVisible(true); - auto comp = dynamic_cast<juce::Slider*>(event.eventComponent); + auto comp = dynamic_cast<juce::Slider*>(eventComponent); if(comp) { auto name = props["name"]; - if(m_paramDisplayLocal) { m_controlLabel.setTopLeftPosition(getTopLevelComponent()->getLocalPoint(comp->getParentComponent(), comp->getPosition().translated(0, -16))); m_controlLabel.setSize(comp->getWidth(), 20); @@ -321,13 +326,34 @@ void VirusEditor::mouseDrag(const juce::MouseEvent & event) } } } +void VirusEditor::mouseDrag(const juce::MouseEvent & event) +{ + updateControlLabel(event.eventComponent); +} +void VirusEditor::mouseEnter(const juce::MouseEvent& event) { + if (event.mouseWasDraggedSinceMouseDown()) { + return; + } + updateControlLabel(event.eventComponent); +} +void VirusEditor::mouseExit(const juce::MouseEvent& event) { + if (event.mouseWasDraggedSinceMouseDown()) { + return; + } + m_controlLabel.setText("", juce::dontSendNotification); +} +void VirusEditor::mouseDown(const juce::MouseEvent &event) { + +} void VirusEditor::mouseUp(const juce::MouseEvent & event) { m_controlLabel.setText("", juce::dontSendNotification); m_controlLabel.setVisible(false); } - +void VirusEditor::mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) { + updateControlLabel(event.eventComponent); +} void VirusEditor::resized() { m_background->setBounds (getLocalBounds()); @@ -338,8 +364,10 @@ void VirusEditor::resized() } void VirusEditor::handleCommandMessage(int commandId) { - if (commandId == Commands::Rebind) { - recreateControls(); + switch (commandId) { + case Commands::Rebind: recreateControls(); + case Commands::UpdateParts: { updateParts(); m_partList->refreshParts(); }; + default: return; } } @@ -356,7 +384,7 @@ void VirusEditor::recreateControls() m_lfoEditor = std::make_unique<LfoEditor>(m_parameterBinding); addChildComponent(m_lfoEditor.get()); - m_fxEditor = std::make_unique<FxEditor>(m_parameterBinding); + m_fxEditor = std::make_unique<FxEditor>(m_parameterBinding, m_controller); addChildComponent(m_fxEditor.get()); m_arpEditor = std::make_unique<ArpEditor>(m_parameterBinding); @@ -499,10 +527,11 @@ void VirusEditor::loadFile() } void VirusEditor::saveFile() { + auto path = m_controller.getConfig()->getValue("virus_bank_dir", ""); juce::FileChooser chooser( "Save preset as syx", m_previousPath.isEmpty() - ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() + ? (path.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : juce::File(path)) : m_previousPath, "*.syx", true); diff --git a/source/jucePlugin/ui/VirusEditor.h b/source/jucePlugin/ui/VirusEditor.h @@ -21,13 +21,14 @@ public: ~VirusEditor() override; void resized() override; void recreateControls(); - void updatePartsPresetNames(); + void updateParts(); void loadFile(); void saveFile(); enum Commands { None, - Rebind = 0x100 + Rebind = 0x100, + UpdateParts = 0x101, }; private: void timerCallback() override; @@ -88,8 +89,12 @@ private: juce::String m_previousPath; bool m_paramDisplayLocal = false; - + void updateControlLabel(Component *eventComponent); + void mouseEnter (const juce::MouseEvent &event) override; + void mouseExit (const juce::MouseEvent &event) override; void mouseDrag (const juce::MouseEvent &event) override; + void mouseDown (const juce::MouseEvent &event) override; void mouseUp (const juce::MouseEvent &event) override; + void mouseWheelMove (const juce::MouseEvent &event, const juce::MouseWheelDetails &wheel) override; }; diff --git a/source/jucePlugin/ui/Virus_ArpEditor.cpp b/source/jucePlugin/ui/Virus_ArpEditor.cpp @@ -91,7 +91,7 @@ ArpEditor::Arpeggiator::Arpeggiator(VirusParameterBinding &_parameterBinding) addAndMakeVisible(m_arpHold); m_arpHold.setBounds(218, m_octaveRange.getY()-2, 36, 18); - + m_globalTempo.setEnabled(false); _parameterBinding.bind(m_globalTempo, Virus::Param_ClockTempo, 0); _parameterBinding.bind(m_noteLength, Virus::Param_ArpNoteLength); _parameterBinding.bind(m_noteSwing, Virus::Param_ArpSwing); diff --git a/source/jucePlugin/ui/Virus_Buttons.cpp b/source/jucePlugin/ui/Virus_Buttons.cpp @@ -34,7 +34,7 @@ namespace Buttons setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); setClickingTogglesState(true); - on->setOriginWithOriginalSize({0, -17}); + off->setOriginWithOriginalSize({0, -17}); setImages(off.get(), nullptr, on.get(), nullptr, on.get()); } diff --git a/source/jucePlugin/ui/Virus_FxEditor.cpp b/source/jucePlugin/ui/Virus_FxEditor.cpp @@ -6,12 +6,13 @@ using namespace juce; constexpr auto comboBoxWidth = 84; -FxEditor::FxEditor(VirusParameterBinding &_parameterBinding) : +FxEditor::FxEditor(VirusParameterBinding &_parameterBinding, Virus::Controller& _controller) : 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) + m_vocoder(_parameterBinding), m_fxMode("FX Mode"), m_parameterBinding(_parameterBinding) { setupBackground(*this, m_background, BinaryData::bg_fx_1018x620_png, BinaryData::bg_fx_1018x620_pngSize); + setupRotary(*this, m_fxSend); setBounds(m_background->getDrawableBounds().toNearestIntEdges()); m_dist.setBounds(23, 28, 273, 116); @@ -28,12 +29,60 @@ FxEditor::FxEditor(VirusParameterBinding &_parameterBinding) : addAndMakeVisible(m_envFollow); m_punch.setBounds(m_envFollow.getRight() + 2, m_envFollow.getY(), 174, 103); addAndMakeVisible(m_punch); - m_delayReverb.setBounds(m_phaser.getRight() + 2, m_dist.getY(), 481, m_phaser.getHeight() * 2); - addAndMakeVisible(m_delayReverb); - m_vocoder.setBounds(m_delayReverb.getBounds().withY(m_delayReverb.getBottom() + 2).withHeight(304)); + + m_delay = std::make_unique<Delay>(_parameterBinding); + m_reverb = std::make_unique<Reverb>(_parameterBinding); + m_delay->setBounds(m_phaser.getRight() + 2, m_dist.getY(), 481, m_phaser.getHeight() * 2); + m_reverb->setBounds(m_phaser.getRight() + 2, m_dist.getY(), 481, m_phaser.getHeight() * 2); + + addChildComponent(m_delay.get()); + m_delay->setVisible(true); + + addChildComponent(m_reverb.get()); + + m_vocoder.setBounds(m_delay->getBounds().withY(m_delay->getBottom() + 2).withHeight(304)); addAndMakeVisible(m_vocoder); -} + m_fxMode.setBounds(m_reverb->getX()+18, m_reverb->getY()+42, comboBoxWidth, comboBoxHeight); + addAndMakeVisible(m_fxMode); + m_fxMode.setAlwaysOnTop(true); + //m_fxSend.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_fxSend.setBounds(m_reverb->getX()+376, m_phaser.getY() - 2, knobSize, knobSize); + addAndMakeVisible(m_fxSend); + m_fxSend.setAlwaysOnTop(true); + _parameterBinding.bind(m_fxSend, Virus::Param_EffectSend); + _parameterBinding.bind(m_fxMode, Virus::Param_DelayReverbMode, 0); + auto p = _controller.getParameter(Virus::Param_DelayReverbMode, 0); + if (p) { + const auto val = (int)p->getValueObject().getValueSource().getValue(); + if (val > 1 && val < 5) { + m_fxMode.setSelectedId(val + 1, juce::dontSendNotification); + const bool isReverb = (val > 1 && val < 5); + m_reverb->setVisible(isReverb); + m_delay->setVisible(!isReverb); + } + p->onValueChanged = nullptr; + p->onValueChanged = [this, p]() { + rebind(); + const auto value = (int)p->getValueObject().getValueSource().getValue(); + m_fxMode.setSelectedId(value + 1, juce::dontSendNotification); + const bool isReverb = (value > 1 && value < 5); + m_reverb->setVisible(isReverb); + m_delay->setVisible(!isReverb); + + }; + } +} +void FxEditor::rebind() { + removeChildComponent(m_delay.get()); + m_delay = std::make_unique<FxEditor::Delay>(m_parameterBinding); + addChildComponent(m_delay.get()); + removeChildComponent(m_reverb.get()); + m_reverb = std::make_unique<FxEditor::Reverb>(m_parameterBinding); + addChildComponent(m_reverb.get()); + m_delay->setBounds(m_phaser.getRight() + 2, m_dist.getY(), 481, m_phaser.getHeight() * 2); + m_reverb->setBounds(m_phaser.getRight() + 2, m_dist.getY(), 481, m_phaser.getHeight() * 2); +} FxEditor::Distortion::Distortion(VirusParameterBinding &_parameterBinding) { setupRotary(*this, m_intensity); @@ -149,8 +198,9 @@ FxEditor::Punch::Punch(VirusParameterBinding &_parameterBinding) _parameterBinding.bind(m_amount, Virus::Param_PunchIntensity); } -FxEditor::DelayAndReverb::DelayAndReverb(VirusParameterBinding &_parameterBinding) : m_sync(_parameterBinding) +FxEditor::Delay::Delay(VirusParameterBinding &_parameterBinding) { + setupBackground(*this, m_background, BinaryData::bg_fxdelay_481x234_png, BinaryData::bg_fxdelay_481x234_pngSize); constexpr auto y = 18; for (auto *s : {&m_time, &m_rate, &m_depth, &m_color, &m_feedback}) setupRotary(*this, *s); @@ -160,44 +210,42 @@ FxEditor::DelayAndReverb::DelayAndReverb(VirusParameterBinding &_parameterBindin m_color.setBounds(m_depth.getRight() - 3, y, knobSize, knobSize); m_feedback.setBounds(m_color.getRight() - 3, y, knobSize, knobSize); - addAndMakeVisible(m_fxMode); - m_fxMode.setBounds(18, 42, comboBoxWidth, comboBoxHeight); - m_sync.setBounds(0, 116 + 2, 481, 116); - addAndMakeVisible(m_sync); + addAndMakeVisible(m_clock); + m_clock.setBounds(18, 116+2+22, comboBoxWidth, comboBoxHeight); + addAndMakeVisible(m_lfoShape); + m_lfoShape.setBounds(m_clock.getBounds().getRight() + 26, 116+2+22, comboBoxWidth, comboBoxHeight); + + _parameterBinding.bind(m_clock, Virus::Param_DelayClock, 0); + _parameterBinding.bind(m_lfoShape, Virus::Param_DelayLfoShape, 0); _parameterBinding.bind(m_time, Virus::Param_DelayTime, 0); _parameterBinding.bind(m_rate, Virus::Param_DelayRateReverbDecayTime, 0); _parameterBinding.bind(m_depth, Virus::Param_DelayDepthReverbRoomSize, 0); _parameterBinding.bind(m_color, Virus::Param_DelayColor, 0); _parameterBinding.bind(m_feedback, Virus::Param_DelayFeedback, 0); - _parameterBinding.bind(m_fxMode, Virus::Param_DelayReverbMode, 0); } -FxEditor::DelayAndReverb::Sync::Sync(VirusParameterBinding &_parameterBinding) +FxEditor::Reverb::Reverb(VirusParameterBinding &_parameterBinding) { - setupRotary(*this, m_mix); - setupRotary(*this, m_damping); - m_mix.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); - m_mix.setBounds(376, -2, knobSize, knobSize); - - addAndMakeVisible(m_clock); - m_clock.setBounds(18, 22, comboBoxWidth, comboBoxHeight); - addAndMakeVisible(m_lfoShape); - m_lfoShape.setBounds(m_clock.getBounds().getRight() + 26, 22, comboBoxWidth, comboBoxHeight); - + setupBackground(*this, m_background, BinaryData::bg_fxreverb_481x234_png, BinaryData::bg_fxreverb_481x234_pngSize); + constexpr auto y = 18; + for (auto *s : {&m_time, &m_rate, &m_damping, &m_color, &m_feedback}) + setupRotary(*this, *s); + m_time.setBounds(118, 18, knobSize, knobSize); + m_rate.setBounds(m_time.getRight() - 8, y, knobSize, knobSize); + m_damping.setBounds(m_rate.getRight() - 8, y, knobSize, knobSize); + m_color.setBounds(m_damping.getRight() - 3, y, knobSize, knobSize); + m_feedback.setBounds(m_color.getRight() - 3, y, knobSize, knobSize); + m_reverbMode.setBounds(18, 116+2+22, comboBoxWidth, comboBoxHeight); addAndMakeVisible(m_reverbMode); - m_reverbMode.setBounds(m_lfoShape.getBounds().getRight() + 26, 22, comboBoxWidth, comboBoxHeight); - - m_damping.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); - m_damping.setBounds(m_reverbMode.getBounds().getRight() + 2, -2, knobSize, knobSize); - addAndMakeVisible(m_damping); - _parameterBinding.bind(m_mix, Virus::Param_EffectSend); - _parameterBinding.bind(m_clock, Virus::Param_DelayClock, 0); - _parameterBinding.bind(m_lfoShape, Virus::Param_DelayLfoShape, 0); - _parameterBinding.bind(m_damping, Virus::Param_DelayLfoShape, 0); _parameterBinding.bind(m_reverbMode, Virus::Param_DelayDepthReverbRoomSize, 0); + _parameterBinding.bind(m_time, Virus::Param_DelayTime, 0); + _parameterBinding.bind(m_rate, Virus::Param_DelayRateReverbDecayTime, 0); + _parameterBinding.bind(m_damping, Virus::Param_DelayLfoShape, 0); + _parameterBinding.bind(m_color, Virus::Param_DelayColor, 0); + _parameterBinding.bind(m_feedback, Virus::Param_DelayFeedback, 0); } FxEditor::Vocoder::Vocoder(VirusParameterBinding &_parameterBinding) : diff --git a/source/jucePlugin/ui/Virus_FxEditor.h b/source/jucePlugin/ui/Virus_FxEditor.h @@ -2,14 +2,14 @@ #include "../PluginProcessor.h" #include "Virus_Buttons.h" - +#include "../VirusController.h" class VirusParameterBinding; class FxEditor : public juce::Component { public: - FxEditor(VirusParameterBinding& _parameterBinding); - + FxEditor(VirusParameterBinding& _parameterBinding, Virus::Controller& _controller); + void rebind(); private: struct Distortion : juce::Component { @@ -75,26 +75,29 @@ private: juce::Slider m_amount; } m_punch; - struct DelayAndReverb : juce::Component + struct Delay : juce::Component { - DelayAndReverb(VirusParameterBinding &_parameterBinding); + Delay(VirusParameterBinding &_parameterBinding); + std::unique_ptr<juce::Drawable> m_background; juce::Slider m_time; juce::Slider m_rate; juce::Slider m_depth; juce::Slider m_color; juce::Slider m_feedback; - juce::ComboBox m_fxMode; - - struct Sync : juce::Component - { - Sync(VirusParameterBinding &_parameterBinding); - juce::Slider m_mix; - juce::ComboBox m_clock, m_lfoShape; - juce::ComboBox m_reverbMode; - juce::Slider m_damping; - } m_sync; - } m_delayReverb; + juce::ComboBox m_clock, m_lfoShape; + }; + struct Reverb : juce::Component + { + Reverb(VirusParameterBinding &_parameterBinding); + std::unique_ptr<juce::Drawable> m_background; + juce::Slider m_time; + juce::Slider m_rate; + juce::Slider m_damping; + juce::Slider m_color; + juce::Slider m_feedback; + juce::ComboBox m_reverbMode; + }; struct Vocoder : juce::Component { Vocoder(VirusParameterBinding &_parameterBinding); @@ -123,6 +126,10 @@ private: juce::ComboBox m_modInput; } m_modulator; } m_vocoder; - + juce::ComboBox m_fxMode; + juce::Slider m_fxSend; + std::unique_ptr<Delay> m_delay; + std::unique_ptr<Reverb> m_reverb; + VirusParameterBinding &m_parameterBinding; std::unique_ptr<juce::Drawable> m_background; }; diff --git a/source/jucePlugin/ui/Virus_Parts.cpp b/source/jucePlugin/ui/Virus_Parts.cpp @@ -5,10 +5,10 @@ #include "VirusEditor.h" using namespace juce; -static uint8_t g_playMode = 0; Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _controller) : m_parameterBinding(_parameterBinding), m_controller(_controller), m_btSingleMode("Single\nMode"), m_btMultiSingleMode("Multi\nSingle"), m_btMultiMode("Multi\nMode") { + setSize(338, 800); for (auto pt = 0; pt < 16; pt++) { m_partLabels[pt].setBounds(34, 161 + pt * (36), 24, 36); @@ -46,6 +46,7 @@ Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _cont p.addItem(presetNames[j], [this, bank, j, pt, presetName] { m_controller.setCurrentPartPreset(pt, bank, j); m_presetNames[pt].setButtonText(presetName); + getParentComponent()->postCommandMessage(VirusEditor::Commands::UpdateParts); }); } std::stringstream bankName; @@ -68,11 +69,13 @@ Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _cont m_controller.setCurrentPartPreset( pt, m_controller.getCurrentPartBank(pt), std::max(0, m_controller.getCurrentPartProgram(pt) - 1)); + getParentComponent()->postCommandMessage(VirusEditor::Commands::UpdateParts); }; m_nextPatch[pt].onClick = [this, pt]() { m_controller.setCurrentPartPreset( pt, m_controller.getCurrentPartBank(pt), std::min(127, m_controller.getCurrentPartProgram(pt) + 1)); + getParentComponent()->postCommandMessage(VirusEditor::Commands::UpdateParts); }; addAndMakeVisible(m_prevPatch[pt]); addAndMakeVisible(m_nextPatch[pt]); @@ -102,14 +105,9 @@ Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _cont m_btMultiSingleMode.setRadioGroupId(0x3cf); addAndMakeVisible(m_btSingleMode); addAndMakeVisible(m_btMultiMode); - addAndMakeVisible(m_btMultiSingleMode); + //addAndMakeVisible(m_btMultiSingleMode); m_btSingleMode.setTopLeftPosition(102, 756); - m_btSingleMode.setSize(70, 30); - - //m_btMultiMode.getToggleStateValue().referTo(*m_controller.getParamValue(Virus::Param_PlayMode)); - m_btSingleMode.setClickingTogglesState(true); - m_btMultiMode.setClickingTogglesState(true); - m_btMultiSingleMode.setClickingTogglesState(true); + m_btSingleMode.setSize(103, 30); m_btSingleMode.setColour(TextButton::ColourIds::textColourOnId, juce::Colours::white); m_btSingleMode.setColour(TextButton::ColourIds::textColourOffId, juce::Colours::grey); @@ -121,52 +119,61 @@ Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _cont m_btMultiSingleMode.onClick = [this]() { setPlayMode(virusLib::PlayMode::PlayModeMultiSingle); }; m_btMultiMode.onClick = [this]() { setPlayMode(virusLib::PlayMode::PlayModeMulti); }; - m_btMultiSingleMode.setBounds(m_btSingleMode.getBounds().translated(m_btSingleMode.getWidth()+4, 0)); - m_btMultiMode.setBounds(m_btMultiSingleMode.getBounds().translated(m_btMultiSingleMode.getWidth()+4, 0)); - - const uint8_t playMode = g_playMode; - if (playMode == virusLib::PlayModeSingle) { + //m_btMultiSingleMode.setBounds(m_btSingleMode.getBounds().translated(m_btSingleMode.getWidth()+4, 0)); + m_btMultiMode.setBounds(m_btSingleMode.getBounds().translated(m_btSingleMode.getWidth()+4, 0)); +} +Parts::~Parts() { +} +void Parts::updatePlayModeButtons() +{ + const auto modeParam = m_controller.getParameter(Virus::Param_PlayMode, 0); + if (modeParam == nullptr) { + return; + } + const auto _mode = (int)modeParam->getValue(); + if (_mode == virusLib::PlayModeSingle) + { m_btSingleMode.setToggleState(true, juce::dontSendNotification); } - else if (playMode == virusLib::PlayModeMultiSingle) { + /*else if (_mode == virusLib::PlayModeMultiSingle) // disabled for now + { m_btMultiSingleMode.setToggleState(true, juce::dontSendNotification); - } - else if (playMode == virusLib::PlayModeMulti) { + }*/ + else if (_mode == virusLib::PlayModeMulti || _mode == virusLib::PlayModeMultiSingle) + { m_btMultiMode.setToggleState(true, juce::dontSendNotification); } - startTimerHz(5); - setSize(338, 800); } - +void Parts::refreshParts() { + 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)); + } + updatePlayModeButtons(); +} void Parts::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); + getParentComponent()->postCommandMessage(VirusEditor::Commands::UpdateParts); getParentComponent()->postCommandMessage(VirusEditor::Commands::Rebind); } void Parts::setPlayMode(uint8_t _mode) { - m_controller.getParameter(Virus::Param_PlayMode)->setValue(_mode); - g_playMode = _mode; - getParentComponent()->postCommandMessage(VirusEditor::Commands::Rebind); -} -void Parts::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)); + m_controller.getParameter(Virus::Param_PlayMode)->setValue(_mode); + if (_mode == virusLib::PlayModeSingle && m_controller.getCurrentPart() != 0) { + changePart(0); } - + getParentComponent()->postCommandMessage(VirusEditor::Commands::Rebind); } diff --git a/source/jucePlugin/ui/Virus_Parts.h b/source/jucePlugin/ui/Virus_Parts.h @@ -6,15 +6,17 @@ #include "../VirusController.h" class VirusParameterBinding; -class Parts : public juce::Component, private juce::Timer +class Parts : public juce::Component { public: Parts(VirusParameterBinding& _parameterBinding, Virus::Controller& _controller); + ~Parts(); + void refreshParts(); static constexpr auto kPartGroupId = 0x3FBBC; private: + void updatePlayModeButtons(); void changePart(uint8_t _part); void setPlayMode(uint8_t _mode); - void timerCallback() override; Virus::Controller &m_controller; VirusParameterBinding &m_parameterBinding; diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.cpp b/source/jucePlugin/ui/Virus_PatchBrowser.cpp @@ -2,7 +2,9 @@ #include "BinaryData.h" #include "Ui_Utils.h" #include "Virus_PatchBrowser.h" +#include "VirusEditor.h" #include <juce_gui_extra/juce_gui_extra.h> +#include <juce_cryptography/juce_cryptography.h> using namespace juce; using namespace virusLib; constexpr auto comboBoxWidth = 98; @@ -15,14 +17,10 @@ PatchBrowser::PatchBrowser(VirusParameterBinding & _parameterBinding, Virus::Con 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) + m_bankList(FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, NULL), + m_search("Search Box") { - 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); + m_properties = m_controller.getConfig(); auto bankDir = m_properties->getValue("virus_bank_dir", ""); if (bankDir != "" && juce::File(bankDir).isDirectory()) @@ -30,21 +28,42 @@ PatchBrowser::PatchBrowser(VirusParameterBinding & _parameterBinding, Virus::Con m_bankList.setRoot(bankDir); } - setBounds(22, 30, 1000, 570); + setBounds(22, 30, 1000, 600); m_bankList.setBounds(16, 28, 480, 540); m_patchList.setBounds(m_bankList.getBounds().translated(m_bankList.getWidth(), 0)); m_patchList.getHeader().addColumn("#", Columns::INDEX, 32); - m_patchList.getHeader().addColumn("Name", Columns::NAME, 140); - m_patchList.getHeader().addColumn("Category1", Columns::CAT1, 90); - m_patchList.getHeader().addColumn("Category2", Columns::CAT2, 90); + m_patchList.getHeader().addColumn("Name", Columns::NAME, 130); + m_patchList.getHeader().addColumn("Category1", Columns::CAT1, 84); + m_patchList.getHeader().addColumn("Category2", Columns::CAT2, 84); m_patchList.getHeader().addColumn("Arp", Columns::ARP, 32); m_patchList.getHeader().addColumn("Uni", Columns::UNI, 32); + m_patchList.getHeader().addColumn("ST+-", Columns::ST, 32); m_patchList.getHeader().addColumn("Ver", Columns::VER, 32); addAndMakeVisible(m_bankList); addAndMakeVisible(m_patchList); + m_search.setSize(m_patchList.getWidth(), 20); + m_search.setColour(TextEditor::textColourId, juce::Colours::white); + m_search.setTopLeftPosition(m_patchList.getBounds().getBottomLeft().translated(0, 8)); + m_search.onTextChange = [this] { + m_filteredPatches.clear(); + for(auto patch : m_patches) { + const auto searchValue = m_search.getText(); + if (searchValue.isEmpty()) { + m_filteredPatches.add(patch); + } + else if(patch.name.containsIgnoreCase(searchValue)) { + m_filteredPatches.add(patch); + } + } + m_patchList.updateContent(); + m_patchList.deselectAllRows(); + m_patchList.repaint(); + }; + m_search.setTextToShowWhenEmpty("search...", juce::Colours::grey); + addAndMakeVisible(m_search); m_bankList.addListener(this); m_patchList.setModel(this); } @@ -70,20 +89,17 @@ VirusModel guessVersion(uint8_t *data) { return VirusModel::C; } -void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e) -{ +int PatchBrowser::loadBankFile(const juce::File& file, const int _startIndex = 0, const bool dedupe = false) { auto ext = file.getFileExtension().toLowerCase(); auto path = file.getParentDirectory().getFullPathName(); - m_properties->setValue("virus_bank_dir", path); + int loadedCount = 0; + int index = _startIndex; if (ext == ".syx") { - m_patches.clear(); - juce::MemoryBlock data; if (!file.loadFileAsData(data)) { - return; + return 0; } - int index = 0; for (auto it = data.begin(); it != data.end(); it += 267) { if ((uint8_t)*it == (uint8_t)0xf0 @@ -100,29 +116,29 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e patch.category1 = patch.data[251]; patch.category2 = patch.data[252]; patch.unison = patch.data[97]; + patch.transpose = patch.data[93]; if ((uint8_t)*(it + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 266) != (uint8_t)0xf8) { patch.model = VirusModel::TI; } else { patch.model = guessVersion(patch.data); } - m_patches.add(patch); - index++; + auto md5 = juce::MD5(it+9 + 17, 256-17-3).toHexString(); + if(!dedupe || !m_checksums.contains(md5)) { + m_checksums.set(md5, true); + m_patches.add(patch); + index++; + } } } - m_patchList.updateContent(); - m_patchList.deselectAllRows(); - 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; + return 0; } - uint8_t index = 0; const uint8_t *ptr = (uint8_t *)data.getData(); const auto end = ptr + data.getSize(); @@ -148,14 +164,20 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e patch.category1 = patch.data[251]; patch.category2 = patch.data[252]; patch.unison = patch.data[97]; + patch.transpose = patch.data[93]; if ((uint8_t)*(it + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 266) != (uint8_t)0xf8) { patch.model = VirusModel::TI; } else { patch.model = guessVersion(patch.data); } - m_patches.add(patch); - index++; + auto md5 = juce::MD5(it+9 + 17, 256-17-3).toHexString(); + if(!dedupe || !m_checksums.contains(md5)) { + m_checksums.set(md5, true); + m_patches.add(patch); + index++; + } + it += 266; } else if((uint8_t)*(it+3) == 0x00 // some midi files have two bytes after the 0xf0 @@ -180,31 +202,86 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e patch.category1 = patch.data[251]; patch.category2 = patch.data[252]; patch.unison = patch.data[97]; + patch.transpose = patch.data[93]; if ((uint8_t)*(it + 2 + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 2 + 266) != (uint8_t)0xf8) { patch.model = VirusModel::TI; } else { patch.model = guessVersion(patch.data); } - m_patches.add(patch); - index++; + auto md5 = juce::MD5(it+2+9 + 17, 256-17-3).toHexString(); + if(!dedupe || !m_checksums.contains(md5)) { + m_checksums.set(md5, true); + m_patches.add(patch); + index++; + } + loadedCount++; it += 266; } + } + } + } + return index; +} +void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e) +{ + auto ext = file.getFileExtension().toLowerCase(); + auto path = file.getParentDirectory().getFullPathName(); + if (file.isDirectory() && e.mods.isRightButtonDown()) { + auto p = juce::PopupMenu(); + p.addItem("Add directory contents to patch list", [this, file]() { + m_patches.clear(); + m_checksums.clear(); + int lastIndex = 0; + for (auto f : juce::RangedDirectoryIterator(file, false, "*.syx;*.mid;*.midi", juce::File::findFiles)) { + lastIndex = loadBankFile(f.getFile(), lastIndex, true); + } + m_filteredPatches.clear(); + for(auto patch : m_patches) { + const auto searchValue = m_search.getText(); + if (searchValue.isEmpty()) { + m_filteredPatches.add(patch); + } + else if(patch.name.containsIgnoreCase(searchValue)) { + m_filteredPatches.add(patch); + } + } + m_patchList.updateContent(); + m_patchList.deselectAllRows(); + m_patchList.repaint(); + }); + p.showMenu(juce::PopupMenu::Options()); + + return; + } + m_properties->setValue("virus_bank_dir", path); + if(file.existsAsFile() && ext == ".syx" || ext == ".midi" || ext == ".mid") { + m_patches.clear(); + loadBankFile(file); + m_filteredPatches.clear(); + for(auto patch : m_patches) { + const auto searchValue = m_search.getText(); + if (searchValue.isEmpty()) { + m_filteredPatches.add(patch); + } + else if(patch.name.containsIgnoreCase(searchValue)) { + m_filteredPatches.add(patch); } } m_patchList.updateContent(); m_patchList.deselectAllRows(); m_patchList.repaint(); } + } void PatchBrowser::fileDoubleClicked(const juce::File &file) {} void PatchBrowser::browserRootChanged(const File &newRoot) {} -int PatchBrowser::getNumRows() { return m_patches.size(); } +int PatchBrowser::getNumRows() { return m_filteredPatches.size(); } void PatchBrowser::paintRowBackground(Graphics &g, int rowNumber, int width, int height, bool rowIsSelected) { auto alternateColour = getLookAndFeel() @@ -220,7 +297,7 @@ void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width g.setColour(rowIsSelected ? juce::Colours::darkblue : getLookAndFeel().findColour(juce::ListBox::textColourId)); // [5] - auto rowElement = m_patches[rowNumber]; + auto rowElement = m_filteredPatches[rowNumber]; //auto text = rowElement.name; juce::String text = ""; if (columnId == Columns::INDEX) @@ -235,6 +312,8 @@ void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width text = rowElement.data[129] != 0 ? "Y" : " "; else if(columnId == Columns::UNI) text = rowElement.unison == 0 ? " " : juce::String(rowElement.unison+1); + else if(columnId == Columns::ST) + text = rowElement.transpose != 64 ? juce::String(rowElement.transpose - 64) : " "; else if (columnId == Columns::VER) { if(rowElement.model < ModelList.size()) text = ModelList[rowElement.model]; @@ -256,7 +335,7 @@ void PatchBrowser::selectedRowsChanged(int lastRowSelected) { uint8_t data[256]; for (int i = 0; i < 256; i++) { - data[i] = m_patches[idx].data[i]; + data[i] = m_filteredPatches[idx].data[i]; cs += data[i]; } cs = cs & 0x7f; @@ -273,6 +352,7 @@ void PatchBrowser::selectedRowsChanged(int lastRowSelected) { syx.push_back(syxEof); m_controller.sendSysEx(syx); // send to edit buffer m_controller.parseMessage(syx); // update ui + getParentComponent()->postCommandMessage(VirusEditor::Commands::UpdateParts); } void PatchBrowser::cellDoubleClicked(int rowNumber, int columnId, const juce::MouseEvent &) @@ -313,6 +393,9 @@ public: else if (attributeToSort == Columns::VER) { return direction * (first.model - second.model); } + else if (attributeToSort == Columns::ST) { + return direction * (first.transpose - second.transpose); + } return direction * (first.progNumber - second.progNumber); } @@ -326,7 +409,7 @@ void PatchBrowser::sortOrderChanged(int newSortColumnId, bool isForwards) if (newSortColumnId != 0) { PatchBrowser::PatchBrowserSorter sorter (newSortColumnId, isForwards); - m_patches.sort(sorter); + m_filteredPatches.sort(sorter); m_patchList.updateContent(); } } diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.h b/source/jucePlugin/ui/Virus_PatchBrowser.h @@ -16,6 +16,7 @@ struct Patch uint8_t data[256]; virusLib::VirusModel model; uint8_t unison; + uint8_t transpose; }; class PatchBrowser : public juce::Component, juce::FileBrowserListener, juce::TableListBoxModel @@ -37,10 +38,13 @@ private: } juce::WildcardFileFilter m_fileFilter; juce::FileBrowserComponent m_bankList; + juce::TextEditor m_search; juce::TableListBox m_patchList; juce::Array<Patch> m_patches; + juce::Array<Patch> m_filteredPatches; juce::PropertiesFile *m_properties; - + juce::HashMap<juce::String, bool> m_checksums; + int loadBankFile(const juce::File &file, const int _startIndex, const bool dedupe); // Inherited via FileBrowserListener void selectionChanged() override; void fileClicked(const juce::File &file, const juce::MouseEvent &e) override; @@ -65,6 +69,7 @@ private: CAT2 = 4, ARP = 5, UNI = 6, - VER = 7 + ST = 7, + VER = 8, }; }; diff --git a/source/virusLib/microcontroller.cpp b/source/virusLib/microcontroller.cpp @@ -904,10 +904,10 @@ void Microcontroller::applyToSingleEditBuffer(TPreset& _single, const Page _page void Microcontroller::applyToMultiEditBuffer(const uint8_t _part, const uint8_t _param, const uint8_t _value) { // remap page C parameters into the multi edit buffer - if (_part == PAGE_C && _param >= PART_MIDI_CHANNEL && _param <= PART_OUTPUT_SELECT) { + if (_param >= PART_MIDI_CHANNEL && _param <= PART_OUTPUT_SELECT) { m_multiEditBuffer[MD_PART_MIDI_CHANNEL + ((_param-PART_MIDI_CHANNEL)*16) + _part] = _value; } - else if (_part == PAGE_B && _param == CLOCK_TEMPO) { + else if (_param == CLOCK_TEMPO) { m_multiEditBuffer[MD_CLOCK_TEMPO] = _value; } }