commit fe520f8e9e7be9be0e279b24f6295fc1ae854755
parent 8db7a2163f134f21b1334b2a4e5c8553a5793544
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sat, 19 Mar 2022 01:25:44 +0100
rework patch browser to be able to be put into multiple predefined containers
Diffstat:
9 files changed, 499 insertions(+), 500 deletions(-)
diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt
@@ -64,8 +64,6 @@ 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
diff --git a/source/jucePlugin/skins/Hoverland/VirusC_Hoverland.json b/source/jucePlugin/skins/Hoverland/VirusC_Hoverland.json
@@ -98,7 +98,7 @@
"alignV" : "C",
"bold" : "1",
"x" : "818.6",
- "y" : "95.9",
+ "y" : "95.90002",
"width" : "440",
"height" : "75"
}
@@ -113,7 +113,7 @@
"alignV" : "T",
"bold" : "1",
"x" : "1190.6",
- "y" : "92.9",
+ "y" : "92.90002",
"width" : "368",
"height" : "80"
}
@@ -1111,7 +1111,7 @@
},
"spritesheet" : {
"x" : "552.3",
- "y" : "916.13",
+ "y" : "916.1299",
"width" : "36",
"height" : "36",
"texture" : "Mult_36x36_x2i",
@@ -1125,7 +1125,7 @@
},
"spritesheet" : {
"x" : "614.38",
- "y" : "916.13",
+ "y" : "916.1299",
"width" : "36",
"height" : "36",
"texture" : "Mult_36x36_x2i",
@@ -1207,7 +1207,7 @@
},
"spritesheet" : {
"x" : "552.3",
- "y" : "988.13",
+ "y" : "988.1299",
"width" : "36",
"height" : "36",
"texture" : "Mult_36x36_x2i",
@@ -1221,7 +1221,7 @@
},
"spritesheet" : {
"x" : "614.38",
- "y" : "988.13",
+ "y" : "988.1299",
"width" : "36",
"height" : "36",
"texture" : "Mult_36x36_x2i",
@@ -3276,7 +3276,7 @@
"normalImageOn" : "1",
"downImageOn" : "0",
"x" : "178",
- "y" : "679.4",
+ "y" : "679.3999",
"width" : "46",
"height" : "38",
"texture" : "lfo_btn_46x76_x2",
@@ -5438,7 +5438,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "969",
- "y" : "617.4",
+ "y" : "617.3999",
"width" : "162",
"height" : "36"
},
@@ -5538,7 +5538,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "1608",
- "y" : "617.4",
+ "y" : "617.3999",
"width" : "160",
"height" : "36"
},
@@ -5554,7 +5554,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "1822",
- "y" : "617.4",
+ "y" : "617.3999",
"width" : "160",
"height" : "36"
},
@@ -5570,7 +5570,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "1608",
- "y" : "716.4",
+ "y" : "716.3999",
"width" : "160",
"height" : "36"
},
@@ -5586,7 +5586,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "1822",
- "y" : "716.4",
+ "y" : "716.3999",
"width" : "160",
"height" : "36"
},
@@ -5602,7 +5602,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "1608",
- "y" : "815.4",
+ "y" : "815.3999",
"width" : "160",
"height" : "36"
},
@@ -5618,7 +5618,7 @@
"alignH" : "L",
"alignV" : "C",
"x" : "1822",
- "y" : "815.4",
+ "y" : "815.3999",
"width" : "160",
"height" : "36"
},
@@ -5665,13 +5665,31 @@
},
"children" : [
{
- "name" : "ContainerPresetBrowser",
+ "name" : "ContainerFileSelector",
"component" : {
"x" : "88",
"y" : "96",
- "width" : "1857",
+ "width" : "928",
"height" : "1055"
}
+ },
+ {
+ "name" : "ContainerPatchList",
+ "component" : {
+ "x" : "1016",
+ "y" : "96",
+ "width" : "929",
+ "height" : "1005"
+ }
+ },
+ {
+ "name" : "ContainerPatchListSearchBox",
+ "component" : {
+ "x" : "1016",
+ "y" : "1101",
+ "width" : "929",
+ "height" : "50"
+ }
}
]
},
diff --git a/source/jucePlugin/ui3/FxPage.cpp b/source/jucePlugin/ui3/FxPage.cpp
@@ -2,6 +2,8 @@
#include "VirusEditor.h"
+#include "../VirusController.h"
+
namespace genericVirusUI
{
FxPage::FxPage(VirusEditor& _editor) : m_editor(_editor)
diff --git a/source/jucePlugin/ui3/MidiPorts.cpp b/source/jucePlugin/ui3/MidiPorts.cpp
@@ -2,6 +2,9 @@
#include "VirusEditor.h"
+#include "../VirusController.h"
+#include "../PluginProcessor.h"
+
namespace genericVirusUI
{
MidiPorts::MidiPorts(VirusEditor& _editor) : m_editor(_editor)
diff --git a/source/jucePlugin/ui3/PatchBrowser.cpp b/source/jucePlugin/ui3/PatchBrowser.cpp
@@ -2,28 +2,397 @@
#include "VirusEditor.h"
+#include "../../virusLib/microcontrollerTypes.h"
+
+#include "../VirusController.h"
+#include "juce_cryptography/hashing/juce_MD5.h"
+
+#include "../../synthLib/midiToSysex.h"
+
+using namespace juce;
+
+const juce::Array<juce::String> ModelList = {"A","B","C","TI"};
+
+const Array<String> g_categories = { "", "Lead", "Bass", "Pad", "Decay", "Pluck",
+ "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion",
+ "Input", "Vocoder", "Favourite 1", "Favourite 2", "Favourite 3" };
+
namespace genericVirusUI
{
- PatchBrowser::PatchBrowser(const VirusEditor& _editor) : m_patchBrowser(_editor.getParameterBinding(), _editor.getController())
+ PatchBrowser::PatchBrowser(const VirusEditor& _editor) : m_editor(_editor), m_controller(_editor.getController()),
+ 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.isNotEmpty() && File(bankDir).isDirectory())
+ m_bankList.setRoot(bankDir);
+
+ 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);
+
+ fitInParent(m_bankList, "ContainerFileSelector");
+ fitInParent(m_patchList, "ContainerPatchList");
+
+ m_search.setColour(TextEditor::textColourId, Colours::white);
+ m_search.onTextChange = [this]
+ {
+ m_filteredPatches.clear();
+ for (const auto& patch : m_patches)
+ {
+ const auto searchValue = m_search.getText();
+ if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue))
+ m_filteredPatches.add(patch);
+ }
+ m_patchList.updateContent();
+ m_patchList.deselectAllRows();
+ m_patchList.repaint();
+ };
+ m_search.setTextToShowWhenEmpty("Search...", Colours::grey);
+
+ fitInParent(m_search, "ContainerPatchListSearchBox");
+
+ m_bankList.addListener(this);
+ m_patchList.setModel(this);
+ }
+
+ void PatchBrowser::fitInParent(juce::Component& _component, const std::string& _parentName) const
+ {
+ auto* parent = m_editor.findComponent(_parentName);
+
+ _component.setTransform(juce::AffineTransform::scale(2.0f));
+
+ const auto& bounds = parent->getBounds();
+ const auto w = bounds.getWidth() >> 1;
+ const auto h = bounds.getHeight() >> 1;
+
+ _component.setBounds(0,0, w,h);
+
+ parent->addAndMakeVisible(_component);
+ }
+
+ void PatchBrowser::selectionChanged() {}
+
+ virusLib::VirusModel guessVersion(const uint8_t* _data)
{
- // We use the old patch browser for now. Fit into the desired parent
- auto* pagePresets = _editor.findComponent("ContainerPresetBrowser");
+ const auto v = _data[0];
+
+ if (v < 5)
+ return virusLib::A;
+ if (v == 6)
+ return virusLib::B;
+ if (v == 7)
+ return virusLib::C;
+ return virusLib::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;
+ }
+
+ static juce::String parseAsciiText(const std::vector<uint8_t>& msg, const int start)
+ {
+ char text[Virus::Controller::kNameLength + 1];
+ text[Virus::Controller::kNameLength] = 0; // termination
+ for (int pos = 0; pos < Virus::Controller::kNameLength; ++pos)
+ text[pos] = static_cast<char>(msg[start + pos]);
+ return {text};
+ }
+
+
+ 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];
+ 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());
- const auto parentBounds = pagePresets->getBounds();
+ std::vector<std::vector<uint8_t>> packets;
+ splitMultipleSysex(packets, d);
- const auto w = parentBounds.getWidth() >> 1;
- const auto h = parentBounds.getHeight() >> 1;
+ return load(_result, _dedupeChecksums, packets);
+ }
- m_patchBrowser.setBounds(0,0,w,h);
+ 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 = m_patchList.getLookAndFeel()
+ .findColour(ListBox::backgroundColourId)
+ .interpolatedWith(m_patchList.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
+ : m_patchList.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(m_patchList.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)
+ : m_attributeToSort(attributeToSortBy),
+ m_direction(forwards ? 1 : -1)
+ {}
+
+ int compareElements(const Patch& first, const Patch& second) const
+ {
+ if (m_attributeToSort == Columns::INDEX)
+ return m_direction * (first.progNumber - second.progNumber);
+ if (m_attributeToSort == Columns::NAME)
+ return m_direction * first.name.compareIgnoreCase(second.name);
+ if (m_attributeToSort == Columns::CAT1)
+ return m_direction * (first.category1 - second.category1);
+ if (m_attributeToSort == Columns::CAT2)
+ return m_direction * (first.category2 - second.category2);
+ if (m_attributeToSort == Columns::ARP)
+ return m_direction * (first.data[129] - second.data[129]);
+ if (m_attributeToSort == Columns::UNI)
+ return m_direction * (first.unison - second.unison);
+ if (m_attributeToSort == Columns::VER)
+ return m_direction * (first.model - second.model);
+ if (m_attributeToSort == Columns::ST)
+ return m_direction * (first.transpose - second.transpose);
+ return m_direction * (first.progNumber - second.progNumber);
+ }
+
+ private:
+ const int m_attributeToSort;
+ const int m_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;
- const auto searchY = h - m_patchBrowser.getSearchBox().getHeight() - 5;
+ for (size_t j = i + 1; j < _src.size(); ++j)
+ {
+ if (_src[j] != 0xf7)
+ continue;
- m_patchBrowser.getBankList().setBounds(0, 0, w>>1, h);
- m_patchBrowser.getPatchList().setBounds(w>>1, 0, w>>1, searchY);
- m_patchBrowser.getSearchBox().setBounds(w>>1, searchY, w>>1, m_patchBrowser.getSearchBox().getHeight());
+ std::vector<uint8_t> entry;
+ entry.insert(entry.begin(), _src.begin() + i, _src.begin() + j + 1);
- pagePresets->addAndMakeVisible(&m_patchBrowser);
+ _dst.emplace_back(entry);
- m_patchBrowser.setTransform(juce::AffineTransform::scale(2.0f));
+ i = j;
+ break;
+ }
+ }
}
}
diff --git a/source/jucePlugin/ui3/PatchBrowser.h b/source/jucePlugin/ui3/PatchBrowser.h
@@ -1,16 +1,89 @@
#pragma once
-#include "VirusPatchBrowser.h"
+#include <set>
+
+#include <juce_audio_processors/juce_audio_processors.h>
+
+namespace Virus
+{
+ class Controller;
+}
+
+namespace virusLib
+{
+ enum VirusModel : uint8_t;
+}
namespace genericVirusUI
{
class VirusEditor;
- class PatchBrowser
+ struct Patch
+ {
+ int progNumber = 0;
+ juce::String name;
+ uint8_t category1 = 0;
+ uint8_t category2 = 0;
+ std::vector<uint8_t> data;
+ std::vector<uint8_t> sysex;
+ virusLib::VirusModel model;
+ uint8_t unison = 0;
+ uint8_t transpose = 0;
+ };
+
+ class PatchBrowser : public juce::FileBrowserListener, juce::TableListBoxModel
{
public:
explicit PatchBrowser(const VirusEditor& _editor);
+
+ 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);
+
private:
- ::PatchBrowser m_patchBrowser;
- };
+ juce::FileBrowserComponent& getBankList() {return m_bankList; }
+ juce::TableListBox& getPatchList() {return m_patchList; }
+ juce::TextEditor& getSearchBox() {return m_search; }
+
+ void fitInParent(juce::Component& _component, const std::string& _parentName) const;
+
+ // 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
+ int getNumRows() override;
+ void paintRowBackground(juce::Graphics &, int rowNumber, int width, int height, bool rowIsSelected) override;
+ void paintCell(juce::Graphics &, int rowNumber, int columnId, int width, int height, bool rowIsSelected) override;
+ void selectedRowsChanged(int lastRowSelected) override;
+ 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,
+ };
+
+ const VirusEditor& m_editor;
+ Virus::Controller& m_controller;
+ 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;
+ };
}
diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp
@@ -272,7 +272,7 @@ namespace genericVirusUI
const auto ext = result.getFileExtension().toLowerCase();
std::vector<Patch> patches;
- ::PatchBrowser::loadBankFile(patches, nullptr, result);
+ PatchBrowser::loadBankFile(patches, nullptr, result);
if (patches.empty())
return;
diff --git a/source/jucePlugin/ui3/VirusPatchBrowser.cpp b/source/jucePlugin/ui3/VirusPatchBrowser.cpp
@@ -1,380 +0,0 @@
-#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
@@ -1,84 +0,0 @@
-#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,
- };
-};