gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 21a450e90b83ef8e892a57454ad27141b42cac0c
parent 1f3ae1c0ba3f1cfe12c409ebb9f52d5035cda57d
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sun,  4 Dec 2022 02:35:51 +0100

add more save options, now with the ability to save singles / arrangement / banks to .mid and .syx

Diffstat:
Msource/jucePlugin/VirusController.cpp | 27++++++++++++++++++++++++---
Msource/jucePlugin/VirusController.h | 36++++++++++++++++++++++++++++++++----
Msource/jucePlugin/ui3/VirusEditor.cpp | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msource/jucePlugin/ui3/VirusEditor.h | 16++++++++++++++++
4 files changed, 200 insertions(+), 29 deletions(-)

diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -93,7 +93,7 @@ namespace Virus if(name == midiPacketName(MidiPacketType::SingleDump)) parseSingle(_msg, data, parameterValues); else if(name == midiPacketName(MidiPacketType::MultiDump)) - parseMulti(data, parameterValues); + parseMulti(_msg, data, parameterValues); else if(name == midiPacketName(MidiPacketType::ParameterChange)) parseParamChange(data); else @@ -189,10 +189,20 @@ namespace Virus std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const { + return getPresetName("SingleName", _values); + } + + std::string Controller::getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const + { + return getPresetName("MultiName", _values); + } + + std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const + { std::string name; for(uint32_t i=0; i<kNameLength; ++i) { - const std::string paramName = "SingleName" + std::to_string(i); + const std::string paramName = _paramNamePrefix + std::to_string(i); const auto idx = getParameterIndexByName(paramName); if(idx == InvalidParameterIndex) break; @@ -364,6 +374,11 @@ namespace Virus if (patch.bankNumber == virusLib::BankNumber::EditBuffer) { + if(patch.progNumber == virusLib::SINGLE) + m_singleEditBuffer = patch; + else + m_singleEditBuffers[patch.progNumber] = patch; + // virus sends also the single buffer not matter what's the mode. (?? no, both is requested, so both is sent) // instead of keeping both, we 'encapsulate' this into first channel. // the logic to maintain this is done by listening the global single/multi param. @@ -422,16 +437,22 @@ namespace Virus onProgramChange(); } else + { m_singles[virusLib::toArrayIndex(patch.bankNumber)][patch.progNumber] = patch; + } } - void Controller::parseMulti(const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) + void Controller::parseMulti(const SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues) { const auto bankNumber = _data.find(pluginLib::MidiDataType::Bank)->second; /* If it's a multi edit buffer, set the part page C parameters to their multi equivalents */ if (bankNumber == 0) { + m_multiEditBuffer.progNumber = _data.find(pluginLib::MidiDataType::Program)->second; + m_multiEditBuffer.name = getMultiPresetName(_parameterValues); + m_multiEditBuffer.data = _msg; + for (const auto & paramValue : _parameterValues) { const auto part = paramValue.first.first; diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -15,14 +15,20 @@ namespace Virus class Controller : public pluginLib::Controller, juce::Timer { public: - struct SinglePatch + struct Patch { - virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); - uint8_t progNumber = 0; std::string name; std::vector<uint8_t> data; + uint8_t progNumber = 0; + }; + + struct SinglePatch : Patch + { + virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); }; + struct MultiPatch : Patch {}; + using Singles = std::array<std::array<SinglePatch, 128>, 8>; static constexpr auto kNameLength = 10; @@ -79,12 +85,29 @@ namespace Virus juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; std::string getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const; const Singles& getSinglePresets() const { return m_singles; } + const SinglePatch& getSingleEditBuffer() const + { + return m_singleEditBuffer; + } + + const SinglePatch& getSingleEditBuffer(const uint8_t _part) const + { + return m_singleEditBuffers[_part]; + } + + const MultiPatch& getMultiEditBuffer() const + { + return m_multiEditBuffer; + } + void setSinglePresetName(uint8_t _part, const juce::String& _name); bool isMultiMode() const; @@ -132,11 +155,16 @@ namespace Virus void timerCallback() override; Singles m_singles; + SinglePatch m_singleEditBuffer; // single mode + std::array<SinglePatch, 16> m_singleEditBuffers; // multi mode + + MultiPatch m_multiEditBuffer; void parseSingle(const SysEx& _msg); void parseSingle(const SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); - void parseMulti(const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); + void parseMulti(const SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues); + void parseParamChange(const pluginLib::MidiPacket::Data& _data); void parseControllerDump(synthLib::SMidiEvent &); diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp @@ -9,6 +9,7 @@ #include "../version.h" #include "../../synthLib/os.h" +#include "../../synthLib/sysexToMidi.h" namespace genericVirusUI { @@ -399,35 +400,46 @@ namespace genericVirusUI void VirusEditor::savePreset() { - const auto path = getController().getConfig()->getValue("virus_bank_dir", ""); - m_fileChooser = std::make_unique<juce::FileChooser>( - "Save preset as syx", - m_previousPath.isEmpty() - ? (path.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : juce::File(path)) - : m_previousPath, - "*.syx", true); + juce::PopupMenu menu; - constexpr auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles; + auto addEntry = [&](juce::PopupMenu& _menu, const std::string& _name, const std::function<void(FileType)>& _callback) + { + juce::PopupMenu subMenu; + + subMenu.addItem(".syx", [_callback](){_callback(FileType::Syx); }); + subMenu.addItem(".mid", [_callback](){_callback(FileType::Mid); }); + + _menu.addSubMenu(_name, subMenu); + }; - auto onFileChooser = [this](const juce::FileChooser& chooser) + addEntry(menu, "Current Single (Edit Buffer)", [this](FileType _type) { - if (chooser.getResults().isEmpty()) - return; + savePresets(SaveType::CurrentSingle, _type); + }); - const auto result = chooser.getResult(); - m_previousPath = result.getParentDirectory().getFullPathName(); - const auto ext = result.getFileExtension().toLowerCase(); + if(getController().isMultiMode()) + { + addEntry(menu, "Arrangement (Multi + 16 Singles)", [this](FileType _type) + { + savePresets(SaveType::Arrangement, _type); + }); + } - const auto data = getController().createSingleDump(getController().getCurrentPart(), virusLib::toMidiByte(virusLib::BankNumber::A), 0); + juce::PopupMenu banksMenu; + for(auto b=0; b<getController().getBankCount(); ++b) + { + std::stringstream bankName; + bankName << "Bank " << static_cast<char>('A' + b); - if(!data.empty()) + addEntry(banksMenu, bankName.str(), [this, b](const FileType _type) { - result.deleteFile(); - result.create(); - result.appendData(&data[0], data.size()); - } - }; - m_fileChooser->launchAsync(flags, onFileChooser); + savePresets(SaveType::Bank, _type, static_cast<uint8_t>(b)); + }); + } + + menu.addSubMenu("Bank", banksMenu); + + menu.showMenuAsync(juce::PopupMenu::Options()); } void VirusEditor::loadPreset() @@ -490,6 +502,100 @@ namespace genericVirusUI onPlayModeChanged(); } + void VirusEditor::savePresets(SaveType _saveType, FileType _fileType, uint8_t _bankNumber/* = 0*/) + { + const auto path = getController().getConfig()->getValue("virus_bank_dir", ""); + m_fileChooser = std::make_unique<juce::FileChooser>( + "Save preset(s) as syx or mid", + m_previousPath.isEmpty() + ? (path.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : juce::File(path)) + : m_previousPath, + "*.syx,*.mid", true); + + constexpr auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles; + + auto onFileChooser = [this, _saveType, _bankNumber](const juce::FileChooser& chooser) + { + if (chooser.getResults().isEmpty()) + return; + + const auto result = chooser.getResult(); + m_previousPath = result.getParentDirectory().getFullPathName(); + const auto ext = result.getFileExtension().toLowerCase(); + + if (!result.existsAsFile() || juce::NativeMessageBox::showYesNoBox(juce::AlertWindow::WarningIcon, "File exists", "Do you want to overwrite the existing file?") == 1) + { + savePresets(result.getFullPathName().toStdString(), _saveType, ext.endsWith("mid") ? FileType::Mid : FileType::Syx, _bankNumber); + } + }; + m_fileChooser->launchAsync(flags, onFileChooser); + } + + bool VirusEditor::savePresets(const std::string& _pathName, SaveType _saveType, FileType _fileType, uint8_t _bankNumber/* = 0*/) const + { + std::vector< std::vector<uint8_t> > messages; + + switch (_saveType) + { + case SaveType::CurrentSingle: + { + const auto dump = getController().createSingleDump(getController().getCurrentPart(), toMidiByte(virusLib::BankNumber::A), 0); + messages.push_back(dump); + } + break; + case SaveType::Bank: + { + const auto& presets = getController().getSinglePresets(); + if(_bankNumber < presets.size()) + { + const auto& bankPresets = presets[_bankNumber]; + for (const auto& bankPreset : bankPresets) + messages.push_back(bankPreset.data); + } + } + break; + case SaveType::Arrangement: + { + messages.push_back(getController().getMultiEditBuffer().data); + + for(uint8_t i=0; i<16; ++i) + { + const auto dump = getController().createSingleDump(i, toMidiByte(virusLib::BankNumber::EditBuffer), i); + messages.push_back(dump); + } + } + break; + default: + return false; + } + + if(messages.empty()) + return false; + + if(_fileType == FileType::Mid) + { + return synthLib::SysexToMidi::write(_pathName.c_str(), messages); + } + + FILE* hFile = fopen(_pathName.c_str(), "wb"); + + if(!hFile) + return false; + + for (const auto& message : messages) + { + const auto written = fwrite(&message[0], 1, message.size(), hFile); + + if(written != message.size()) + { + fclose(hFile); + return false; + } + } + fclose(hFile); + return true; + } + void VirusEditor::setPart(size_t _part) { m_parameterBinding.setPart(static_cast<uint8_t>(_part)); diff --git a/source/jucePlugin/ui3/VirusEditor.h b/source/jucePlugin/ui3/VirusEditor.h @@ -16,6 +16,19 @@ namespace genericVirusUI class VirusEditor : public genericUI::EditorInterface, public genericUI::Editor { public: + enum class FileType + { + Syx, + Mid + }; + + enum class SaveType + { + CurrentSingle, + Bank, + Arrangement + }; + VirusEditor(VirusParameterBinding& _binding, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, std::string _skinFolder, std::function<void()> _openMenuCallback); ~VirusEditor() override; @@ -59,6 +72,9 @@ namespace genericVirusUI void setPlayMode(uint8_t _playMode); + void savePresets(SaveType _saveType, FileType _fileType, uint8_t _bankNumber = 0); + bool savePresets(const std::string& _pathName, SaveType _saveType, FileType _fileType, uint8_t _bankNumber = 0) const; + AudioPluginAudioProcessor& m_processor; VirusParameterBinding& m_parameterBinding;