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:
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;