commit 02ab7d99f28fe3931a34a916bb66e29b9956edd5 parent b1da3eb0ae2d88b38497be573c7405d642e11170 Author: 790 <790@users.noreply.github.com> Date: Sun, 16 Jan 2022 15:35:36 +0000 add scale option. fix automation not being sent to daw. add name search to patch browser. add scrollwheel to comboboxes. rework config file Diffstat:
17 files changed, 193 insertions(+), 78 deletions(-)
diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -103,6 +103,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(8,8,64,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] { @@ -345,6 +351,9 @@ namespace Virus if (auto *p = findSynthParam(ch, i > bankSize ? 0x71 : 0x70, i % bankSize)) p->setValueFromSynth(patch.data[i], true); } + if (onProgramChange) { + onProgramChange(); + } } else m_singles[virusLib::toArrayIndex(patch.bankNumber)][patch.progNumber] = patch; @@ -385,6 +394,8 @@ namespace Virus 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 +1386,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 +1556,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}, @@ -1559,8 +1570,8 @@ namespace Virus {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}, 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" @@ -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 @@ -494,5 +497,6 @@ namespace Virus virusLib::BankNumber m_currentBank[16]; uint8_t m_currentProgram[16]; 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,19 @@ 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 || m_desc.index == 41) { // filter cutoffs + return 127; + } + 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,8 +1,7 @@ #include "VirusParameterBinding.h" #include "VirusParameter.h" #include "PluginProcessor.h" - - +class Parameter; VirusParameterBinding::~VirusParameterBinding() { clearBindings(); @@ -31,8 +30,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 +41,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 +53,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 +63,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 VirusParameterBindingMouseListener::mouseDown(const juce::MouseEvent &event) { m_param->beginChangeGesture(); }; + void VirusParameterBindingMouseListener::mouseUp(const juce::MouseEvent &event) { m_param->endChangeGesture(); }; + void VirusParameterBindingMouseListener::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/ui/VirusEditor.cpp b/source/jucePlugin/ui/VirusEditor.cpp @@ -93,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 != "") @@ -192,6 +187,10 @@ VirusEditor::VirusEditor(VirusParameterBinding &_parameterBinding, AudioPluginAu addAndMakeVisible(m_controlLabel); + m_controller.onProgramChange = [this]() { + updateParts(); + m_partList->refreshParts(); + }; m_controller.getBankCount(); addMouseListener(this, true); @@ -348,14 +347,16 @@ void VirusEditor::mouseExit(const juce::MouseEvent& event) { 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()); @@ -368,7 +369,7 @@ void VirusEditor::resized() void VirusEditor::handleCommandMessage(int commandId) { switch (commandId) { case Commands::Rebind: recreateControls(); - case Commands::UpdateParts: { updateParts(); m_partList->updatePartNames(); }; + case Commands::UpdateParts: { updateParts(); m_partList->refreshParts(); }; default: return; } } @@ -529,10 +530,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 @@ -95,5 +95,6 @@ private: 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_Parts.cpp b/source/jucePlugin/ui/Virus_Parts.cpp @@ -105,7 +105,7 @@ 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); @@ -121,15 +121,8 @@ Parts::Parts(VirusParameterBinding & _parameterBinding, Virus::Controller& _cont m_btMultiSingleMode.setBounds(m_btSingleMode.getBounds().translated(m_btSingleMode.getWidth()+4, 0)); m_btMultiMode.setBounds(m_btMultiSingleMode.getBounds().translated(m_btMultiSingleMode.getWidth()+4, 0)); - - m_controller.getParameter(Virus::Param_PlayMode, 0)->onValueChanged = [this] { updatePlayModeButtons(); }; - //updatePlayModeButtons(); - - startTimerHz(5); } Parts::~Parts() { - stopTimer(); - m_controller.getParameter(Virus::Param_PlayMode, 0)->onValueChanged = nullptr; } void Parts::updatePlayModeButtons() { @@ -142,19 +135,16 @@ void Parts::updatePlayModeButtons() { m_btSingleMode.setToggleState(true, juce::dontSendNotification); } - else if (_mode == virusLib::PlayModeMultiSingle) + /*else if (_mode == virusLib::PlayModeMultiSingle) // disabled for now { m_btMultiSingleMode.setToggleState(true, juce::dontSendNotification); - } - else if (_mode == virusLib::PlayModeMulti) + }*/ + else if (_mode == virusLib::PlayModeMulti || _mode == virusLib::PlayModeMultiSingle) { m_btMultiMode.setToggleState(true, juce::dontSendNotification); } - - updatePartNames(); - getParentComponent()->postCommandMessage(VirusEditor::Commands::UpdateParts); } -void Parts::updatePartNames() { +void Parts::refreshParts() { const auto multiMode = m_controller.isMultiMode(); for (auto pt = 0; pt < 16; pt++) { @@ -165,6 +155,7 @@ void Parts::updatePartNames() { if (singlePartOrInMulti) m_presetNames[pt].setButtonText(m_controller.getCurrentPartPresetName(pt)); } + updatePlayModeButtons(); } void Parts::changePart(uint8_t _part) { @@ -182,9 +173,4 @@ void Parts::setPlayMode(uint8_t _mode) { m_controller.getParameter(Virus::Param_PlayMode)->setValue(_mode); getParentComponent()->postCommandMessage(VirusEditor::Commands::Rebind); -} - -void Parts::timerCallback() -{ - // ugly (polling!) way for refreshing presets names as this is temporary ui -} +} +\ No newline at end of file diff --git a/source/jucePlugin/ui/Virus_Parts.h b/source/jucePlugin/ui/Virus_Parts.h @@ -6,18 +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 updatePartNames(); + void refreshParts(); static constexpr auto kPartGroupId = 0x3FBBC; private: - void updatePlayModeButtons(); + 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 @@ -4,6 +4,7 @@ #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; @@ -16,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()) @@ -31,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); } @@ -71,7 +89,7 @@ VirusModel guessVersion(uint8_t *data) { return VirusModel::C; } -int PatchBrowser::loadBankFile(const juce::File& file, const int _startIndex = 0) { +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(); int loadedCount = 0; @@ -98,14 +116,19 @@ int PatchBrowser::loadBankFile(const juce::File& file, const int _startIndex = 0 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++; + } } } } @@ -141,14 +164,20 @@ int PatchBrowser::loadBankFile(const juce::File& file, const int _startIndex = 0 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 @@ -173,19 +202,23 @@ int PatchBrowser::loadBankFile(const juce::File& file, const int _startIndex = 0 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); + 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++; - index++; it += 266; } - } } } @@ -200,9 +233,20 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e 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); + 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(); @@ -216,6 +260,16 @@ void PatchBrowser::fileClicked(const juce::File &file, const juce::MouseEvent &e 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(); @@ -227,7 +281,7 @@ 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() @@ -243,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) @@ -258,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]; @@ -279,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; @@ -337,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); } @@ -350,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,11 +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; - - int loadBankFile(const juce::File &file, const int _startIndex); + 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; @@ -66,6 +69,7 @@ private: CAT2 = 4, ARP = 5, UNI = 6, - VER = 7 + ST = 7, + VER = 8, }; };