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 8b3d0c0dca30f9a2119d809408b4d1ce15a88a74
parent 29a329f3b36a08180880b54b9a42119786277a25
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Thu,  8 Aug 2024 20:12:57 +0200

Merge branch 'oss/main' into priv/n2x

# Conflicts:
#	doc/changelog.txt

Diffstat:
Mdoc/changelog.txt | 7+++++++
Msource/jucePluginEditorLib/midiPorts.cpp | 132++++++++++++++++++++++++++----------------------------------------------------
Msource/jucePluginEditorLib/midiPorts.h | 22+++++++++++++---------
Msource/jucePluginLib/CMakeLists.txt | 1+
Asource/jucePluginLib/midiports.cpp | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePluginLib/midiports.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginLib/processor.cpp | 53++++++++++++-----------------------------------------
Msource/jucePluginLib/processor.h | 17+++++++----------
Msource/xtJucePlugin/skins/xtDefault/xtDefault.json | 4++--
Msource/xtJucePlugin/xtEditor.cpp | 4++++
Msource/xtJucePlugin/xtEditor.h | 3+++
11 files changed, 241 insertions(+), 150 deletions(-)

diff --git a/doc/changelog.txt b/doc/changelog.txt @@ -2,6 +2,12 @@ Release Notes 1.3.18 +Framework: + +- [Imp] Physical MIDI port selection is now stored per instance + +- [Fix] Physical MIDI ports were not opened before GUI was opened + NodalRed2x: - [Imp] Implement Morph parameters / VM Map button @@ -14,6 +20,7 @@ NodalRed2x: Xenia: - [Fix] Do not expose parameters for parts 9-16 as the machine only has 8 +- [Fix] Physical MIDI in/out port selectors didn't work 1.3.17 (2024.08.04) diff --git a/source/jucePluginEditorLib/midiPorts.cpp b/source/jucePluginEditorLib/midiPorts.cpp @@ -4,75 +4,53 @@ #include "juceUiLib/editor.h" -namespace jucePluginEditorLib +namespace { - MidiPorts::MidiPorts(const genericUI::Editor& _editor, Processor& _processor) : m_processor(_processor) + void initComboBox(juce::ComboBox* _combo, const juce::Array<juce::MidiDeviceInfo>& _entries, const juce::String& _selectedEntry) { - const auto& properties = m_processor.getConfig(); - - const auto midiIn = properties.getValue("midi_input", ""); - const auto midiOut = properties.getValue("midi_output", ""); - - if (!midiIn.isEmpty()) - m_processor.setMidiInput(midiIn); - - if (!midiOut.isEmpty()) - m_processor.setMidiOutput(midiOut); - - m_midiIn = _editor.findComponentT<juce::ComboBox>("MidiIn"); - m_midiOut = _editor.findComponentT<juce::ComboBox>("MidiOut"); - - m_midiIn->setTextWhenNoChoicesAvailable("-"); - - const auto midiInputs = juce::MidiInput::getAvailableDevices(); - int inIndex = 0; - m_midiIn->addItem("<none>", 1); + _combo->addItem("<none>", 1); - for (int i = 0; i < midiInputs.size(); i++) + for (int i = 0; i < _entries.size(); i++) { - const auto input = midiInputs[i]; + const auto& input = _entries[i]; - if (m_processor.getMidiInput() != nullptr && input.identifier == m_processor.getMidiInput()->getIdentifier()) + if (input.identifier == _selectedEntry) inIndex = i + 1; - m_midiIn->addItem(input.name, i+2); + _combo->addItem(input.name, i+2); } - m_midiIn->setSelectedItemIndex(inIndex, juce::dontSendNotification); - - m_midiIn->onChange = [this]() { updateMidiInput(m_midiIn->getSelectedItemIndex()); }; - - m_midiOut->setTextWhenNoChoicesAvailable("-"); - - const auto midiOutputs = juce::MidiOutput::getAvailableDevices(); - - auto outIndex = 0; + _combo->setSelectedItemIndex(inIndex, juce::dontSendNotification); + } +} - m_midiOut->addItem("<none>", 1); +namespace jucePluginEditorLib +{ + MidiPorts::MidiPorts(const genericUI::Editor& _editor, Processor& _processor) : m_processor(_processor) + { + m_midiIn = _editor.findComponentT<juce::ComboBox>("MidiIn", false); + m_midiOut = _editor.findComponentT<juce::ComboBox>("MidiOut", false); - for (int i = 0; i < midiOutputs.size(); i++) + if(m_midiIn) { - const auto output = midiOutputs[i]; - if (m_processor.getMidiOutput() != nullptr && - output.identifier == m_processor.getMidiOutput()->getIdentifier()) - { - outIndex = i + 1; - } - m_midiOut->addItem(output.name, i+2); + const auto* in = getMidiPorts().getMidiInput(); + initComboBox(m_midiIn, juce::MidiInput::getAvailableDevices(), in != nullptr ? in->getIdentifier() : juce::String()); + m_midiIn->onChange = [this]{ updateMidiInput(m_midiIn->getSelectedItemIndex()); }; } - m_midiOut->setSelectedItemIndex(outIndex, juce::dontSendNotification); - - m_midiOut->onChange = [this]() { updateMidiOutput(m_midiOut->getSelectedItemIndex()); }; - - deviceManager = new juce::AudioDeviceManager(); + if(m_midiOut) + { + const auto* out = getMidiPorts().getMidiOutput(); + initComboBox(m_midiOut, juce::MidiOutput::getAvailableDevices(), out != nullptr ? out->getIdentifier() : juce::String()); + m_midiOut->onChange = [this]{ updateMidiOutput(m_midiOut->getSelectedItemIndex()); }; + } } - MidiPorts::~MidiPorts() + pluginLib::MidiPorts& MidiPorts::getMidiPorts() const { - delete deviceManager; + return m_processor.getMidiPorts(); } void MidiPorts::showMidiPortFailedMessage(const char* _name) const @@ -82,71 +60,49 @@ namespace jucePluginEditorLib "Make sure that the device is not already in use by another program.", nullptr, juce::ModalCallbackFunction::create([](int){})); } - void MidiPorts::updateMidiInput(int index) + void MidiPorts::updateMidiInput(int _index) const { const auto list = juce::MidiInput::getAvailableDevices(); - auto& properties = m_processor.getConfig(); - - if (index <= 0) + if (_index <= 0) { - properties.setValue("midi_input", ""); - properties.save(); - m_lastInputIndex = 0; - m_midiIn->setSelectedItemIndex(index, juce::dontSendNotification); + m_midiIn->setSelectedItemIndex(_index, juce::dontSendNotification); return; } - index--; - - const auto newInput = list[index]; + _index--; - if (!deviceManager->isMidiInputDeviceEnabled(newInput.identifier)) - deviceManager->setMidiInputDeviceEnabled(newInput.identifier, true); + const auto newInput = list[_index]; - if (!m_processor.setMidiInput(newInput.identifier)) + if (!getMidiPorts().setMidiInput(newInput.identifier)) { showMidiPortFailedMessage("Input"); m_midiIn->setSelectedItemIndex(0, juce::dontSendNotification); - m_lastInputIndex = 0; return; } - properties.setValue("midi_input", newInput.identifier); - properties.save(); - - m_midiIn->setSelectedItemIndex(index + 1, juce::dontSendNotification); - m_lastInputIndex = index; + m_midiIn->setSelectedItemIndex(_index + 1, juce::dontSendNotification); } - void MidiPorts::updateMidiOutput(int index) + void MidiPorts::updateMidiOutput(int _index) const { const auto list = juce::MidiOutput::getAvailableDevices(); - auto& properties = m_processor.getConfig(); - - if (index == 0) + if (_index == 0) { - properties.setValue("midi_output", ""); - properties.save(); - m_midiOut->setSelectedItemIndex(index, juce::dontSendNotification); - m_lastOutputIndex = index; - m_processor.setMidiOutput(""); + m_midiOut->setSelectedItemIndex(_index, juce::dontSendNotification); + getMidiPorts().setMidiOutput({}); return; } - index--; - const auto newOutput = list[index]; - if (!m_processor.setMidiOutput(newOutput.identifier)) + _index--; + const auto newOutput = list[_index]; + if (!getMidiPorts().setMidiOutput(newOutput.identifier)) { showMidiPortFailedMessage("Output"); m_midiOut->setSelectedItemIndex(0, juce::dontSendNotification); - m_lastOutputIndex = 0; return; } - properties.setValue("midi_output", newOutput.identifier); - properties.save(); - - m_midiOut->setSelectedItemIndex(index + 1, juce::dontSendNotification); - m_lastOutputIndex = index; + + m_midiOut->setSelectedItemIndex(_index + 1, juce::dontSendNotification); } } diff --git a/source/jucePluginEditorLib/midiPorts.h b/source/jucePluginEditorLib/midiPorts.h @@ -1,5 +1,10 @@ #pragma once +namespace pluginLib +{ + class MidiPorts; +} + namespace genericUI { class Editor; @@ -7,6 +12,8 @@ namespace genericUI namespace juce { + struct MidiDeviceInfo; + class String; class AudioDeviceManager; class ComboBox; } @@ -19,20 +26,17 @@ namespace jucePluginEditorLib { public: explicit MidiPorts(const genericUI::Editor& _editor, Processor& _processor); - ~MidiPorts(); private: + pluginLib::MidiPorts& getMidiPorts() const; + + void showMidiPortFailedMessage(const char* _name) const; + void updateMidiInput(int _index) const; + void updateMidiOutput(int _index) const; + Processor& m_processor; juce::ComboBox* m_midiIn = nullptr; juce::ComboBox* m_midiOut = nullptr; - - juce::AudioDeviceManager* deviceManager = nullptr; - int m_lastInputIndex = 0; - int m_lastOutputIndex = 0; - - void showMidiPortFailedMessage(const char* _name) const; - void updateMidiInput(int _index); - void updateMidiOutput(int _index); }; } diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES dummydevice.cpp dummydevice.h event.cpp event.h midipacket.cpp midipacket.h + midiports.cpp midiports.h parameter.cpp parameter.h parameterbinding.cpp parameterbinding.h parameterdescription.cpp parameterdescription.h diff --git a/source/jucePluginLib/midiports.cpp b/source/jucePluginLib/midiports.cpp @@ -0,0 +1,102 @@ +#include "midiports.h" + +#include "processor.h" +#include "juce_audio_devices/juce_audio_devices.h" + +namespace pluginLib +{ + MidiPorts::MidiPorts(Processor& _processor) : m_processor(_processor) + { + } + + MidiPorts::~MidiPorts() + { + setMidiInput({}); + setMidiOutput({}); + + m_deviceManager.reset(); + } + + juce::MidiOutput *MidiPorts::getMidiOutput() const + { + return m_midiOutput.get(); + } + + juce::MidiInput *MidiPorts::getMidiInput() const + { + return m_midiInput.get(); + } + + void MidiPorts::saveChunkData(synthLib::BinaryStream& _binaryStream) const + { + synthLib::ChunkWriter cw(_binaryStream, "mpIO", 1); + + if(m_midiInput) + _binaryStream.write(m_midiInput->getIdentifier().toStdString()); + else + _binaryStream.write(std::string()); + if(m_midiOutput) + _binaryStream.write(m_midiOutput->getIdentifier().toStdString()); + else + _binaryStream.write(std::string()); + } + + void MidiPorts::loadChunkData(synthLib::ChunkReader& _cr) + { + _cr.add("mpIO", 1, [&](synthLib::BinaryStream& _data, uint32_t) + { + const auto input = _data.readString(); + const auto output = _data.readString(); + + setMidiInput(input); + setMidiOutput(output); + }); + } + + bool MidiPorts::setMidiOutput(const juce::String& _out) + { + if (m_midiOutput != nullptr && m_midiOutput->isBackgroundThreadRunning()) + { + m_midiOutput->stopBackgroundThread(); + } + if(_out.isEmpty()) + return false; + m_midiOutput = juce::MidiOutput::openDevice(_out); + if (m_midiOutput != nullptr) + { + m_midiOutput->startBackgroundThread(); + return true; + } + return false; + } + + bool MidiPorts::setMidiInput(const juce::String& _in) + { + if (m_midiInput != nullptr) + { + m_midiInput->stop(); + } + + if(_in.isEmpty()) + return false; + + if(!m_deviceManager) + m_deviceManager.reset(new juce::AudioDeviceManager()); + + if (!m_deviceManager->isMidiInputDeviceEnabled(_in)) + m_deviceManager->setMidiInputDeviceEnabled(_in, true); + + m_midiInput = juce::MidiInput::openDevice(_in, this); + if (m_midiInput != nullptr) + { + m_midiInput->start(); + return true; + } + return false; + } + + void MidiPorts::handleIncomingMidiMessage(juce::MidiInput* _source, const juce::MidiMessage& _message) + { + m_processor.handleIncomingMidiMessage(_source, _message); + } +} diff --git a/source/jucePluginLib/midiports.h b/source/jucePluginLib/midiports.h @@ -0,0 +1,46 @@ +#pragma once + +#include <memory> + +#include "juce_audio_devices/juce_audio_devices.h" +#include "synthLib/binarystream.h" + +namespace juce +{ + class String; +} + +namespace pluginLib +{ + class Processor; + + class MidiPorts : juce::MidiInputCallback + { + public: + MidiPorts(Processor& _processor); + MidiPorts(MidiPorts&&) = delete; + MidiPorts(const MidiPorts&) = delete; + + ~MidiPorts() override; + + MidiPorts& operator = (const MidiPorts&) = delete; + MidiPorts& operator = (MidiPorts&&) = delete; + + bool setMidiOutput(const juce::String& _out); + juce::MidiOutput* getMidiOutput() const; + bool setMidiInput(const juce::String& _in); + juce::MidiInput* getMidiInput() const; + + void saveChunkData(synthLib::BinaryStream& _binaryStream) const; + void loadChunkData(synthLib::ChunkReader& _cr); + + private: + void handleIncomingMidiMessage(juce::MidiInput* _source, const juce::MidiMessage& _message) override; + + Processor& m_processor; + + std::unique_ptr<juce::MidiOutput> m_midiOutput{}; + std::unique_ptr<juce::MidiInput> m_midiInput{}; + std::unique_ptr<juce::AudioDeviceManager> m_deviceManager; + }; +} diff --git a/source/jucePluginLib/processor.cpp b/source/jucePluginLib/processor.cpp @@ -22,7 +22,7 @@ namespace pluginLib constexpr char g_saveMagic[] = "DSP56300"; constexpr uint32_t g_saveVersion = 2; - Processor::Processor(const BusesProperties& _busesProperties, Properties _properties) : juce::AudioProcessor(_busesProperties), m_properties(std::move(_properties)) + Processor::Processor(const BusesProperties& _busesProperties, Properties _properties) : juce::AudioProcessor(_busesProperties), m_properties(std::move(_properties)), m_midiPorts(*this) { } @@ -38,47 +38,14 @@ namespace pluginLib getPlugin().addMidiEvent(ev); } - juce::MidiOutput *Processor::getMidiOutput() const { return m_midiOutput.get(); } - juce::MidiInput *Processor::getMidiInput() const { return m_midiInput.get(); } - - bool Processor::setMidiOutput(const juce::String& _out) - { - if (m_midiOutput != nullptr && m_midiOutput->isBackgroundThreadRunning()) - { - m_midiOutput->stopBackgroundThread(); - } - m_midiOutput = juce::MidiOutput::openDevice(_out); - if (m_midiOutput != nullptr) - { - m_midiOutput->startBackgroundThread(); - return true; - } - return false; - } - - bool Processor::setMidiInput(const juce::String& _in) - { - if (m_midiInput != nullptr) - { - m_midiInput->stop(); - } - m_midiInput = juce::MidiInput::openDevice(_in, this); - if (m_midiInput != nullptr) - { - m_midiInput->start(); - return true; - } - return false; - } - - void Processor::handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) + void Processor::handleIncomingMidiMessage(juce::MidiInput *_source, const juce::MidiMessage &_message) { synthLib::SMidiEvent sm(synthLib::MidiEventSource::PhysicalInput); - const auto* raw = message.getSysExData(); + const auto* raw = _message.getSysExData(); if (raw) { - const auto count = message.getSysExDataSize(); + const auto count = _message.getSysExDataSize(); auto syx = pluginLib::SysEx(); syx.push_back(0xf0); for (int i = 0; i < count; i++) @@ -93,8 +60,8 @@ namespace pluginLib } else { - const auto count = message.getRawDataSize(); - const auto* rawData = message.getRawData(); + const auto count = _message.getRawDataSize(); + const auto* rawData = _message.getRawData(); if (count >= 1 && count <= 3) { sm.a = rawData[0]; @@ -228,6 +195,8 @@ namespace pluginLib baseLib::ChunkWriter cw(s, "DSSR", 1); s.write(m_preferredDeviceSamplerate); } + + m_midiPorts.saveChunkData(s); } bool Processor::loadCustomData(const std::vector<uint8_t>& _sourceBuffer) @@ -277,6 +246,8 @@ namespace pluginLib const auto sr = _binaryStream.read<float>(); setPreferredDeviceSamplerate(sr); }); + + m_midiPorts.loadChunkData(_cr); } void Processor::readGain(baseLib::BinaryStream& _s) @@ -585,8 +556,8 @@ namespace pluginLib midiMessages.addEvent(message, 0); // additionally send to the midi output we've selected in the editor - if (m_midiOutput) - m_midiOutput->sendMessageNow(message); + if (auto* out = m_midiPorts.getMidiOutput()) + out->sendMessageNow(message); } } } diff --git a/source/jucePluginLib/processor.h b/source/jucePluginLib/processor.h @@ -4,6 +4,7 @@ #include <juce_audio_devices/juce_audio_devices.h> #include "controller.h" +#include "midiports.h" #include "synthLib/plugin.h" @@ -21,7 +22,7 @@ namespace synthLib namespace pluginLib { - class Processor : public juce::AudioProcessor, juce::MidiInputCallback + class Processor : public juce::AudioProcessor { public: struct Properties @@ -38,12 +39,7 @@ namespace pluginLib void addMidiEvent(const synthLib::SMidiEvent& ev); - bool setMidiOutput(const juce::String& _out); - juce::MidiOutput* getMidiOutput() const; - bool setMidiInput(const juce::String& _in); - juce::MidiInput* getMidiInput() const; - - void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override; + void handleIncomingMidiMessage(juce::MidiInput* _source, const juce::MidiMessage& _message); Controller& getController(); bool isPluginValid() { return getPlugin().isValid(); } @@ -110,6 +106,8 @@ namespace pluginLib bool rebootDevice(); + auto& getMidiPorts() { return m_midiPorts; } + protected: void destroyController(); @@ -153,9 +151,7 @@ namespace pluginLib synthLib::DeviceError m_deviceError = synthLib::DeviceError::None; std::unique_ptr<synthLib::Device> m_device; std::unique_ptr<synthLib::Plugin> m_plugin; - std::unique_ptr<juce::MidiOutput> m_midiOutput{}; - std::unique_ptr<juce::MidiInput> m_midiInput{}; - std::vector<synthLib::SMidiEvent> m_midiOut{}; + std::vector<synthLib::SMidiEvent> m_midiOut; private: const Properties m_properties; @@ -164,5 +160,6 @@ namespace pluginLib uint32_t m_dspClockPercent = 100; float m_preferredDeviceSamplerate = 0.0f; float m_hostSamplerate = 0.0f; + MidiPorts m_midiPorts; }; } diff --git a/source/xtJucePlugin/skins/xtDefault/xtDefault.json b/source/xtJucePlugin/skins/xtDefault/xtDefault.json @@ -6298,7 +6298,7 @@ } }, { - "name" : "dropdown", + "name" : "MidiIn", "combobox" : { "offsetL" : "15", "offsetT" : "1", @@ -6317,7 +6317,7 @@ } }, { - "name" : "dropdown", + "name" : "MidiOut", "combobox" : { "offsetL" : "15", "offsetT" : "1", diff --git a/source/xtJucePlugin/xtEditor.cpp b/source/xtJucePlugin/xtEditor.cpp @@ -10,6 +10,8 @@ #include "xtPatchManager.h" #include "xtWaveEditor.h" +#include "jucePluginEditorLib/midiPorts.h" + #include "jucePluginLib/parameterbinding.h" namespace xtJucePlugin @@ -119,6 +121,8 @@ namespace xtJucePlugin auto* waveEditorButtonParent = findComponent("waveEditorButtonParent"); waveEditorButtonParent->setVisible(false); #endif + + m_midiPorts.reset(new jucePluginEditorLib::MidiPorts(*this, getProcessor())); } Editor::~Editor() diff --git a/source/xtJucePlugin/xtEditor.h b/source/xtJucePlugin/xtEditor.h @@ -11,6 +11,7 @@ class Controller; namespace jucePluginEditorLib { + class MidiPorts; class FocusedParameter; class Processor; } @@ -62,6 +63,8 @@ namespace xtJucePlugin std::unique_ptr<FocusedParameter> m_focusedParameter; std::unique_ptr<FrontPanel> m_frontPanel; std::unique_ptr<Parts> m_parts; + std::unique_ptr<jucePluginEditorLib::MidiPorts> m_midiPorts; + WaveEditor* m_waveEditor = nullptr; pluginLib::EventListener<bool> m_playModeChangeListener;