commit 2a40ce7a2ab04f1bfdde3ed6f35c79522ff31d60
parent 3c8bf251e4ad12408e64da03237aa5d317cacab4
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Wed, 16 Mar 2022 23:01:05 +0100
remove old Hoverland UI from build
Diffstat:
8 files changed, 472 insertions(+), 533 deletions(-)
diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt
@@ -31,28 +31,6 @@ set(SOURCES
version.h
)
-set(SOURCES_UI
- ui/Virus_Buttons.cpp
- ui/Virus_LookAndFeel.cpp
- ui/VirusEditor.cpp
- ui/Virus_ArpEditor.cpp
- ui/Virus_FxEditor.cpp
- ui/Virus_LfoEditor.cpp
- ui/Virus_OscEditor.cpp
- ui/Virus_PatchBrowser.cpp
- ui/Virus_Parts.cpp
- ui/Virus_Buttons.h
- ui/Virus_LookAndFeel.h
- ui/VirusEditor.h
- ui/Virus_ArpEditor.h
- ui/Virus_FxEditor.h
- ui/Virus_LfoEditor.h
- ui/Virus_OscEditor.h
- ui/Virus_PatchBrowser.h
- ui/Virus_Parts.h
- ui/Ui_Utils.h
-)
-
set(SOURCES_UI2
ui2/Ui_Utils.h
ui2/Virus_Buttons.cpp
@@ -86,38 +64,13 @@ set(SOURCES_UI3
ui3/Tabs.h
ui3/VirusEditor.cpp
ui3/VirusEditor.h
+ ui3/VirusPatchBrowser.cpp
+ ui3/VirusPatchBrowser.h
)
# https://forum.juce.com/t/help-needed-using-binarydata-with-cmake-juce-6/40486
# "This might be because the BinaryData files are generated during the build, so the IDE may not be able to find them until the build has been run once (and even then, some IDEs might need a bit of a nudge to re-index the binary directory…)"
SET(ASSETS
- "assets/bg_1377x800.png"
- "assets/panels/bg_arp_1018x620.png"
- "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"
- "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"
- "assets/buttons/link_horizon_36x12.png"
- "assets/buttons/presets_btn_43_15.png"
- "assets/buttons/Handle_18x47.png"
- "assets/buttons/sync2_54x25.png"
- "assets/buttons/part_select_btn_36x36.png"
- "assets/buttons/arphold_btn_36x36.png"
- "assets/knobs/Gen_70x70_100.png"
- "assets/knobs/Gen_pol_70x70_100.png"
- "assets/knobs/GenBlue_70x70_100.png"
- "assets/knobs/GenRed_70x70_100.png"
- "assets/knobs/multi_18x18_100.png"
-
"assets2/main_background.png"
"assets2/panels/panel_1.png"
"assets2/panels/panel_2.png"
@@ -169,10 +122,9 @@ macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProjec
PRODUCT_NAME ${productName} # The name of the final executable, which can differ from the target name
)
- target_sources(${targetName} PRIVATE ${SOURCES} ${SOURCES_UI} ${SOURCES_UI2} ${SOURCES_UI3})
+ target_sources(${targetName} PRIVATE ${SOURCES} ${SOURCES_UI2} ${SOURCES_UI3})
source_group("source" FILES ${SOURCES})
- source_group("source\\ui" FILES ${SOURCES_UI})
source_group("source\\ui2" FILES ${SOURCES_UI2})
source_group("source\\ui3" FILES ${SOURCES_UI3})
diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp
@@ -3,7 +3,6 @@
#include "VirusController.h"
-#include "ui/VirusEditor.h"
#include "ui2/VirusEditor.h"
#include "ui3/VirusEditor.h"
@@ -47,7 +46,7 @@ void AudioPluginAudioProcessorEditor::loadSkin(int index)
virusEditor->m_AudioPlugInEditor = this;
m_virusEditor.reset(virusEditor);
}
- else if(index == 2)
+ else
{
try
{
@@ -59,13 +58,10 @@ void AudioPluginAudioProcessorEditor::loadSkin(int index)
catch(const std::runtime_error& _err)
{
LOG("ERROR: Failed to create editor: " << _err.what());
- return;
+ m_virusEditor->setSize(400,300);
}
}
- else {
- m_virusEditor.reset(new VirusEditor(m_parameterBinding, processorRef));
- setSize(1377, 800);
- }
+
m_virusEditor->setTopLeftPosition(0, 0);
addAndMakeVisible(m_virusEditor.get());
}
diff --git a/source/jucePlugin/VirusParameterBinding.h b/source/jucePlugin/VirusParameterBinding.h
@@ -1,7 +1,7 @@
#pragma once
#include "VirusParameter.h"
-#include "ui/Virus_Buttons.h"
+
namespace juce {
class Value;
}
diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.cpp b/source/jucePlugin/ui/Virus_PatchBrowser.cpp
@@ -1,388 +0,0 @@
-#include "../VirusParameterBinding.h"
-#include "Virus_PatchBrowser.h"
-#include "VirusEditor.h"
-#include <juce_gui_extra/juce_gui_extra.h>
-#include <juce_cryptography/juce_cryptography.h>
-
-#include "../../synthLib/midiToSysex.h"
-
-using namespace juce;
-using namespace virusLib;
-
-const Array<String> g_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_fileFilter("*.syx;*.mid;*.midi", "*", "Virus Patch Dumps"),
- m_bankList(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, nullptr),
- m_search("Search Box"),
- m_patchList("Patch Browser"),
- m_properties(m_controller.getConfig())
-{
- const auto bankDir = m_properties->getValue("virus_bank_dir", "");
- if (bankDir != "" && File(bankDir).isDirectory())
- {
- m_bankList.setRoot(bankDir);
- }
-
- 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, 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, Colours::white);
- m_search.setTopLeftPosition(m_patchList.getBounds().getBottomLeft().translated(0, 8));
- m_search.onTextChange = [this]
- {
- m_filteredPatches.clear();
- for(const 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...", Colours::grey);
- addAndMakeVisible(m_search);
- m_bankList.addListener(this);
- m_patchList.setModel(this);
-}
-
-void PatchBrowser::selectionChanged() {}
-
-VirusModel guessVersion(const uint8_t* _data)
-{
- const auto v = _data[0];
-
- if (v < 5)
- return VirusModel::A;
- if (v == 6)
- return VirusModel::B;
- if (v == 7)
- return VirusModel::C;
- return VirusModel::TI;
-}
-
-uint32_t PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets)
-{
- uint32_t count = 0;
- for (const auto& packet : _packets)
- {
- if (load(_result, _dedupeChecksums, packet))
- ++count;
- }
- return count;
-}
-
-bool PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data)
-{
- if (_data.size() < 267)
- return false;
-
- auto* it = &_data.front();
-
- if (*it == (uint8_t)0xf0
- && *(it + 1) == (uint8_t)0x00
- && *(it + 2) == (uint8_t)0x20
- && *(it + 3) == (uint8_t)0x33
- && *(it + 4) == (uint8_t)0x01
- && *(it + 6) == (uint8_t)virusLib::DUMP_SINGLE)
- {
- Patch patch;
- patch.progNumber = static_cast<int>(_result.size());
- patch.sysex = _data;
- patch.data.insert(patch.data.begin(), _data.begin() + 9, _data.end());
- patch.name = parseAsciiText(patch.data, 128 + 112);
- 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[0]);
- }
-
- if(!_dedupeChecksums)
- {
- _result.push_back(patch);
- }
- else
- {
- const auto md5 = std::string(MD5(it + 9 + 17, 256 - 17 - 3).toHexString().toRawUTF8());
-
- if (_dedupeChecksums->find(md5) == _dedupeChecksums->end())
- {
- _dedupeChecksums->insert(md5);
- _result.push_back(patch);
- }
- }
-
- return true;
- }
- return false;
-}
-
-uint32_t PatchBrowser::loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const File& file)
-{
- const auto ext = file.getFileExtension().toLowerCase();
- const auto path = file.getParentDirectory().getFullPathName();
-
- if (ext == ".syx")
- {
- MemoryBlock data;
-
- if (!file.loadFileAsData(data))
- return 0;
-
- std::vector<uint8_t> d;
- d.resize(data.getSize());
- memcpy(&d[0], data.getData(), data.getSize());
-
- std::vector<std::vector<uint8_t>> packets;
- splitMultipleSysex(packets, d);
-
- return load(_result, _dedupeChecksums, packets);
- }
-
- if (ext == ".mid" || ext == ".midi")
- {
- std::vector<uint8_t> data;
-
- synthLib::MidiToSysex::readFile(data, file.getFullPathName().getCharPointer());
-
- if(data.empty())
- return 0;
-
- std::vector<std::vector<uint8_t>> packets;
- splitMultipleSysex(packets, data);
-
- return load(_result, _dedupeChecksums, packets);
- }
- return 0;
-}
-
-void PatchBrowser::fileClicked(const File &file, const MouseEvent &e)
-{
- const auto ext = file.getFileExtension().toLowerCase();
- const auto path = file.getParentDirectory().getFullPathName();
- if (file.isDirectory() && e.mods.isRightButtonDown())
- {
- auto p = PopupMenu();
- p.addItem("Add directory contents to patch list", [this, file]()
- {
- m_patches.clear();
- m_checksums.clear();
- std::set<std::string> dedupeChecksums;
-
- std::vector<Patch> patches;
-
- for (const auto& f : RangedDirectoryIterator(file, false, "*.syx;*.mid;*.midi", File::findFiles))
- loadBankFile(patches, &dedupeChecksums, f.getFile());
-
- m_filteredPatches.clear();
-
- for(const auto& patch : patches)
- {
- const auto searchValue = m_search.getText();
-
- m_patches.add(patch);
-
- if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue))
- m_filteredPatches.add(patch);
- }
- m_patchList.updateContent();
- m_patchList.deselectAllRows();
- m_patchList.repaint();
- });
- p.showMenuAsync(PopupMenu::Options());
-
- return;
- }
- m_properties->setValue("virus_bank_dir", path);
-
- if(file.existsAsFile() && ext == ".syx" || ext == ".midi" || ext == ".mid")
- {
- m_patches.clear();
- std::vector<Patch> patches;
- loadBankFile(patches, nullptr, file);
- m_filteredPatches.clear();
- for(const auto& patch : patches)
- {
- const auto searchValue = m_search.getText();
- m_patches.add(patch);
- if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue))
- m_filteredPatches.add(patch);
- }
- m_patchList.updateContent();
- m_patchList.deselectAllRows();
- m_patchList.repaint();
- }
-
-}
-
-void PatchBrowser::fileDoubleClicked(const File &file) {}
-
-void PatchBrowser::browserRootChanged(const File &newRoot) {}
-
-int PatchBrowser::getNumRows() { return m_filteredPatches.size(); }
-
-void PatchBrowser::paintRowBackground(Graphics &g, int rowNumber, int width, int height, bool rowIsSelected) {
- const auto alternateColour = getLookAndFeel()
- .findColour(ListBox::backgroundColourId)
- .interpolatedWith(getLookAndFeel().findColour(ListBox::textColourId), 0.03f);
- if (rowIsSelected)
- g.fillAll(Colours::lightblue);
- else if (rowNumber & 1)
- g.fillAll(alternateColour);
-}
-
-void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width, int height, bool rowIsSelected) {
- g.setColour(rowIsSelected ? Colours::darkblue
- : getLookAndFeel().findColour(ListBox::textColourId)); // [5]
-
- if (rowNumber >= getNumRows())
- return; // Juce what are you up to?
-
- const auto rowElement = m_filteredPatches[rowNumber];
- //auto text = rowElement.name;
- String text = "";
- if (columnId == Columns::INDEX)
- text = String(rowElement.progNumber);
- else if (columnId == Columns::NAME)
- text = rowElement.name;
- else if (columnId == Columns::CAT1)
- text = g_categories[rowElement.category1];
- else if (columnId == Columns::CAT2)
- text = g_categories[rowElement.category2];
- else if (columnId == Columns::ARP)
- text = rowElement.data[129] != 0 ? "Y" : " ";
- else if(columnId == Columns::UNI)
- text = rowElement.unison == 0 ? " " : String(rowElement.unison+1);
- else if(columnId == Columns::ST)
- text = rowElement.transpose != 64 ? String(rowElement.transpose - 64) : " ";
- else if (columnId == Columns::VER) {
- if(rowElement.model < ModelList.size())
- text = ModelList[rowElement.model];
- }
- g.drawText(text, 2, 0, width - 4, height, Justification::centredLeft, true); // [6]
- g.setColour(getLookAndFeel().findColour(ListBox::backgroundColourId));
- g.fillRect(width - 1, 0, 1, height); // [7]
-}
-
-void PatchBrowser::selectedRowsChanged(int lastRowSelected)
-{
- const auto idx = m_patchList.getSelectedRow();
-
- if (idx == -1)
- return;
-
- // force to edit buffer
- const auto part = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE);
-
- auto sysex = m_filteredPatches[idx].sysex;
- sysex[7] = toMidiByte(virusLib::BankNumber::EditBuffer);
- sysex[8] = part;
-
- m_controller.sendSysEx(sysex);
-
- m_controller.sendSysEx(m_controller.constructMessage({ virusLib::REQUEST_SINGLE, 0x0, part }));
-}
-
-void PatchBrowser::cellDoubleClicked(int rowNumber, int columnId, const MouseEvent &)
-{
- if(rowNumber == m_patchList.getSelectedRow())
- selectedRowsChanged(0);
-}
-
-class PatchBrowser::PatchBrowserSorter
-{
-public:
- PatchBrowserSorter (const int attributeToSortBy, const bool forwards)
- : attributeToSort (attributeToSortBy),
- direction (forwards ? 1 : -1)
- {}
-
- int compareElements (const Patch& first, const Patch& second) const
- {
- if(attributeToSort == Columns::INDEX)
- return direction * (first.progNumber - second.progNumber);
- if (attributeToSort == Columns::NAME)
- return direction * first.name.compareIgnoreCase(second.name);
- if (attributeToSort == Columns::CAT1)
- return direction * (first.category1 - second.category1);
- if (attributeToSort == Columns::CAT2)
- return direction * (first.category2 - second.category2);
- if (attributeToSort == Columns::ARP)
- return direction * (first.data[129]- second.data[129]);
- if (attributeToSort == Columns::UNI)
- return direction * (first.unison - second.unison);
- if (attributeToSort == Columns::VER)
- return direction * (first.model - second.model);
- if (attributeToSort == Columns::ST)
- return direction * (first.transpose - second.transpose);
- return direction * (first.progNumber - second.progNumber);
- }
-
-private:
- const int attributeToSort;
- const int direction;
-};
-
-void PatchBrowser::sortOrderChanged(int newSortColumnId, bool isForwards)
-{
- if (newSortColumnId != 0)
- {
- PatchBrowserSorter sorter (newSortColumnId, isForwards);
- m_filteredPatches.sort(sorter);
- m_patchList.updateContent();
- }
-}
-
-void PatchBrowser::splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src)
-{
- for(size_t i=0; i<_src.size(); ++i)
- {
- if(_src[i] != 0xf0)
- continue;
-
- for(size_t j=i+1; j < _src.size(); ++j)
- {
- if(_src[j] != 0xf7)
- continue;
-
- std::vector<uint8_t> entry;
- entry.insert(entry.begin(), _src.begin() + i, _src.begin() + j + 1);
-
- _dst.emplace_back(entry);
-
- i = j;
- break;
- }
- }
-}
diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.h b/source/jucePlugin/ui/Virus_PatchBrowser.h
@@ -1,85 +0,0 @@
-#pragma once
-
-#include "../PluginProcessor.h"
-#include "Virus_Buttons.h"
-#include <juce_gui_extra/juce_gui_extra.h>
-#include "../VirusController.h"
-class VirusParameterBinding;
-
-const juce::Array<juce::String> ModelList = {"A","B","C","TI"};
-struct Patch
-{
- int progNumber;
- juce::String name;
- uint8_t category1;
- uint8_t category2;
- std::vector<uint8_t> data;
- std::vector<uint8_t> sysex;
- virusLib::VirusModel model;
- uint8_t unison;
- uint8_t transpose;
-};
-
-class PatchBrowser : public juce::Component, juce::FileBrowserListener, juce::TableListBoxModel
-{
-
-public:
- PatchBrowser(VirusParameterBinding &_parameterBinding, Virus::Controller& _controller);
-
- static uint32_t load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets);
- static bool load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data);
- static uint32_t loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const juce::File& file);
-
- static void splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src);
-
- juce::FileBrowserComponent& getBankList() {return m_bankList; }
- juce::TableListBox& getPatchList() {return m_patchList; }
- juce::TextEditor& getSearchBox() {return m_search; }
-
-private:
- VirusParameterBinding &m_parameterBinding;
- Virus::Controller& m_controller;
- template <typename T> static juce::String parseAsciiText(const T &msg, const int start)
- {
- 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::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;
- // 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;
- virtual void cellDoubleClicked (int rowNumber, int columnId, const juce::MouseEvent &) override;
- void sortOrderChanged(int newSortColumnId, bool isForwards) override;
- class PatchBrowserSorter;
- enum Columns {
- INDEX = 1,
- NAME = 2,
- CAT1 = 3,
- CAT2 = 4,
- ARP = 5,
- UNI = 6,
- ST = 7,
- VER = 8,
- };
-};
diff --git a/source/jucePlugin/ui3/PatchBrowser.h b/source/jucePlugin/ui3/PatchBrowser.h
@@ -1,6 +1,6 @@
#pragma once
-#include "../ui/Virus_PatchBrowser.h"
+#include "VirusPatchBrowser.h"
namespace genericVirusUI
{
diff --git a/source/jucePlugin/ui3/VirusPatchBrowser.cpp b/source/jucePlugin/ui3/VirusPatchBrowser.cpp
@@ -0,0 +1,380 @@
+#include "VirusPatchBrowser.h"
+
+#include "../VirusParameterBinding.h"
+
+#include <juce_gui_extra/juce_gui_extra.h>
+#include <juce_cryptography/juce_cryptography.h>
+
+#include "../../synthLib/midiToSysex.h"
+
+using namespace juce;
+using namespace virusLib;
+
+const Array<String> g_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_fileFilter("*.syx;*.mid;*.midi", "*", "Virus Patch Dumps"),
+ m_bankList(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, nullptr),
+ m_search("Search Box"),
+ m_patchList("Patch Browser"),
+ m_properties(m_controller.getConfig())
+{
+ const auto bankDir = m_properties->getValue("virus_bank_dir", "");
+ if (bankDir != "" && File(bankDir).isDirectory())
+ {
+ m_bankList.setRoot(bankDir);
+ }
+
+ 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, 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, Colours::white);
+ m_search.setTopLeftPosition(m_patchList.getBounds().getBottomLeft().translated(0, 8));
+ m_search.onTextChange = [this]
+ {
+ m_filteredPatches.clear();
+ for(const 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...", Colours::grey);
+ addAndMakeVisible(m_search);
+ m_bankList.addListener(this);
+ m_patchList.setModel(this);
+}
+
+void PatchBrowser::selectionChanged() {}
+
+VirusModel guessVersion(const uint8_t* _data)
+{
+ const auto v = _data[0];
+
+ if (v < 5)
+ return VirusModel::A;
+ if (v == 6)
+ return VirusModel::B;
+ if (v == 7)
+ return VirusModel::C;
+ return VirusModel::TI;
+}
+
+uint32_t PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets)
+{
+ uint32_t count = 0;
+ for (const auto& packet : _packets)
+ {
+ if (load(_result, _dedupeChecksums, packet))
+ ++count;
+ }
+ return count;
+}
+
+bool PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data)
+{
+ if (_data.size() < 267)
+ return false;
+
+ auto* it = &_data.front();
+
+ if (*it == (uint8_t)0xf0
+ && *(it + 1) == (uint8_t)0x00
+ && *(it + 2) == (uint8_t)0x20
+ && *(it + 3) == (uint8_t)0x33
+ && *(it + 4) == (uint8_t)0x01
+ && *(it + 6) == (uint8_t)virusLib::DUMP_SINGLE)
+ {
+ Patch patch;
+ patch.progNumber = static_cast<int>(_result.size());
+ patch.sysex = _data;
+ patch.data.insert(patch.data.begin(), _data.begin() + 9, _data.end());
+ patch.name = parseAsciiText(patch.data, 128 + 112);
+ 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[0]);
+ }
+
+ if(!_dedupeChecksums)
+ {
+ _result.push_back(patch);
+ }
+ else
+ {
+ const auto md5 = std::string(MD5(it + 9 + 17, 256 - 17 - 3).toHexString().toRawUTF8());
+
+ if (_dedupeChecksums->find(md5) == _dedupeChecksums->end())
+ {
+ _dedupeChecksums->insert(md5);
+ _result.push_back(patch);
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
+uint32_t PatchBrowser::loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const File& file)
+{
+ const auto ext = file.getFileExtension().toLowerCase();
+ const auto path = file.getParentDirectory().getFullPathName();
+
+ if (ext == ".syx")
+ {
+ MemoryBlock data;
+
+ if (!file.loadFileAsData(data))
+ return 0;
+
+ std::vector<uint8_t> d;
+ d.resize(data.getSize());
+ memcpy(&d[0], data.getData(), data.getSize());
+
+ std::vector<std::vector<uint8_t>> packets;
+ splitMultipleSysex(packets, d);
+
+ return load(_result, _dedupeChecksums, packets);
+ }
+
+ if (ext == ".mid" || ext == ".midi")
+ {
+ std::vector<uint8_t> data;
+
+ synthLib::MidiToSysex::readFile(data, file.getFullPathName().getCharPointer());
+
+ if(data.empty())
+ return 0;
+
+ std::vector<std::vector<uint8_t>> packets;
+ splitMultipleSysex(packets, data);
+
+ return load(_result, _dedupeChecksums, packets);
+ }
+ return 0;
+}
+
+void PatchBrowser::fileClicked(const File &file, const MouseEvent &e)
+{
+ const auto ext = file.getFileExtension().toLowerCase();
+ const auto path = file.getParentDirectory().getFullPathName();
+ if (file.isDirectory() && e.mods.isRightButtonDown())
+ {
+ auto p = PopupMenu();
+ p.addItem("Add directory contents to patch list", [this, file]()
+ {
+ m_patches.clear();
+ m_checksums.clear();
+ std::set<std::string> dedupeChecksums;
+
+ std::vector<Patch> patches;
+
+ for (const auto& f : RangedDirectoryIterator(file, false, "*.syx;*.mid;*.midi", File::findFiles))
+ loadBankFile(patches, &dedupeChecksums, f.getFile());
+
+ m_filteredPatches.clear();
+
+ for(const auto& patch : patches)
+ {
+ const auto searchValue = m_search.getText();
+
+ m_patches.add(patch);
+
+ if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue))
+ m_filteredPatches.add(patch);
+ }
+ m_patchList.updateContent();
+ m_patchList.deselectAllRows();
+ m_patchList.repaint();
+ });
+ p.showMenuAsync(PopupMenu::Options());
+
+ return;
+ }
+ m_properties->setValue("virus_bank_dir", path);
+
+ if(file.existsAsFile() && ext == ".syx" || ext == ".midi" || ext == ".mid")
+ {
+ m_patches.clear();
+ std::vector<Patch> patches;
+ loadBankFile(patches, nullptr, file);
+ m_filteredPatches.clear();
+ for(const auto& patch : patches)
+ {
+ const auto searchValue = m_search.getText();
+ m_patches.add(patch);
+ if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue))
+ m_filteredPatches.add(patch);
+ }
+ m_patchList.updateContent();
+ m_patchList.deselectAllRows();
+ m_patchList.repaint();
+ }
+}
+
+int PatchBrowser::getNumRows() { return m_filteredPatches.size(); }
+
+void PatchBrowser::paintRowBackground(Graphics &g, int rowNumber, int width, int height, bool rowIsSelected) {
+ const auto alternateColour = getLookAndFeel()
+ .findColour(ListBox::backgroundColourId)
+ .interpolatedWith(getLookAndFeel().findColour(ListBox::textColourId), 0.03f);
+ if (rowIsSelected)
+ g.fillAll(Colours::lightblue);
+ else if (rowNumber & 1)
+ g.fillAll(alternateColour);
+}
+
+void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width, int height, bool rowIsSelected) {
+ g.setColour(rowIsSelected ? Colours::darkblue
+ : getLookAndFeel().findColour(ListBox::textColourId)); // [5]
+
+ if (rowNumber >= getNumRows())
+ return; // Juce what are you up to?
+
+ const auto rowElement = m_filteredPatches[rowNumber];
+ //auto text = rowElement.name;
+ String text = "";
+ if (columnId == Columns::INDEX)
+ text = String(rowElement.progNumber);
+ else if (columnId == Columns::NAME)
+ text = rowElement.name;
+ else if (columnId == Columns::CAT1)
+ text = g_categories[rowElement.category1];
+ else if (columnId == Columns::CAT2)
+ text = g_categories[rowElement.category2];
+ else if (columnId == Columns::ARP)
+ text = rowElement.data[129] != 0 ? "Y" : " ";
+ else if(columnId == Columns::UNI)
+ text = rowElement.unison == 0 ? " " : String(rowElement.unison+1);
+ else if(columnId == Columns::ST)
+ text = rowElement.transpose != 64 ? String(rowElement.transpose - 64) : " ";
+ else if (columnId == Columns::VER) {
+ if(rowElement.model < ModelList.size())
+ text = ModelList[rowElement.model];
+ }
+ g.drawText(text, 2, 0, width - 4, height, Justification::centredLeft, true); // [6]
+ g.setColour(getLookAndFeel().findColour(ListBox::backgroundColourId));
+ g.fillRect(width - 1, 0, 1, height); // [7]
+}
+
+void PatchBrowser::selectedRowsChanged(int lastRowSelected)
+{
+ const auto idx = m_patchList.getSelectedRow();
+
+ if (idx == -1)
+ return;
+
+ // force to edit buffer
+ const auto part = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE);
+
+ auto sysex = m_filteredPatches[idx].sysex;
+ sysex[7] = toMidiByte(virusLib::BankNumber::EditBuffer);
+ sysex[8] = part;
+
+ m_controller.sendSysEx(sysex);
+
+ m_controller.sendSysEx(m_controller.constructMessage({ virusLib::REQUEST_SINGLE, 0x0, part }));
+}
+
+void PatchBrowser::cellDoubleClicked(int rowNumber, int columnId, const MouseEvent &)
+{
+ if(rowNumber == m_patchList.getSelectedRow())
+ selectedRowsChanged(0);
+}
+
+class PatchBrowser::PatchBrowserSorter
+{
+public:
+ PatchBrowserSorter (const int attributeToSortBy, const bool forwards)
+ : attributeToSort (attributeToSortBy),
+ direction (forwards ? 1 : -1)
+ {}
+
+ int compareElements (const Patch& first, const Patch& second) const
+ {
+ if(attributeToSort == Columns::INDEX)
+ return direction * (first.progNumber - second.progNumber);
+ if (attributeToSort == Columns::NAME)
+ return direction * first.name.compareIgnoreCase(second.name);
+ if (attributeToSort == Columns::CAT1)
+ return direction * (first.category1 - second.category1);
+ if (attributeToSort == Columns::CAT2)
+ return direction * (first.category2 - second.category2);
+ if (attributeToSort == Columns::ARP)
+ return direction * (first.data[129]- second.data[129]);
+ if (attributeToSort == Columns::UNI)
+ return direction * (first.unison - second.unison);
+ if (attributeToSort == Columns::VER)
+ return direction * (first.model - second.model);
+ if (attributeToSort == Columns::ST)
+ return direction * (first.transpose - second.transpose);
+ return direction * (first.progNumber - second.progNumber);
+ }
+
+private:
+ const int attributeToSort;
+ const int direction;
+};
+
+void PatchBrowser::sortOrderChanged(int newSortColumnId, bool isForwards)
+{
+ if (newSortColumnId != 0)
+ {
+ PatchBrowserSorter sorter (newSortColumnId, isForwards);
+ m_filteredPatches.sort(sorter);
+ m_patchList.updateContent();
+ }
+}
+
+void PatchBrowser::splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src)
+{
+ for(size_t i=0; i<_src.size(); ++i)
+ {
+ if(_src[i] != 0xf0)
+ continue;
+
+ for(size_t j=i+1; j < _src.size(); ++j)
+ {
+ if(_src[j] != 0xf7)
+ continue;
+
+ std::vector<uint8_t> entry;
+ entry.insert(entry.begin(), _src.begin() + i, _src.begin() + j + 1);
+
+ _dst.emplace_back(entry);
+
+ i = j;
+ break;
+ }
+ }
+}
diff --git a/source/jucePlugin/ui3/VirusPatchBrowser.h b/source/jucePlugin/ui3/VirusPatchBrowser.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "../PluginProcessor.h"
+#include <juce_gui_extra/juce_gui_extra.h>
+#include "../VirusController.h"
+class VirusParameterBinding;
+
+const juce::Array<juce::String> ModelList = {"A","B","C","TI"};
+struct Patch
+{
+ int progNumber;
+ juce::String name;
+ uint8_t category1;
+ uint8_t category2;
+ std::vector<uint8_t> data;
+ std::vector<uint8_t> sysex;
+ virusLib::VirusModel model;
+ uint8_t unison;
+ uint8_t transpose;
+};
+
+class PatchBrowser : public juce::Component, juce::FileBrowserListener, juce::TableListBoxModel
+{
+
+public:
+ PatchBrowser(VirusParameterBinding &_parameterBinding, Virus::Controller& _controller);
+
+ static uint32_t load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets);
+ static bool load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data);
+ static uint32_t loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const juce::File& file);
+
+ static void splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src);
+
+ juce::FileBrowserComponent& getBankList() {return m_bankList; }
+ juce::TableListBox& getPatchList() {return m_patchList; }
+ juce::TextEditor& getSearchBox() {return m_search; }
+
+private:
+ VirusParameterBinding &m_parameterBinding;
+ Virus::Controller& m_controller;
+ template <typename T> static juce::String parseAsciiText(const T &msg, const int start)
+ {
+ 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::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;
+ // 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;
+ virtual void cellDoubleClicked (int rowNumber, int columnId, const juce::MouseEvent &) override;
+ void sortOrderChanged(int newSortColumnId, bool isForwards) override;
+ class PatchBrowserSorter;
+ enum Columns {
+ INDEX = 1,
+ NAME = 2,
+ CAT1 = 3,
+ CAT2 = 4,
+ ARP = 5,
+ UNI = 6,
+ ST = 7,
+ VER = 8,
+ };
+};