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 1adff39743dfa6eb4fd37bd9c8d352ef400a554d
parent e0ed9111e1d2f628fee8624208f43c4b1e532a23
Author: dsp56300 <87139854+dsp56300@users.noreply.github.com>
Date:   Mon, 27 Dec 2021 19:01:27 +0100

Merge pull request #29 from 790/midiports

native midi ports
Diffstat:
MCMakeLists.txt | 2+-
Msource/jucePlugin/PluginEditor.cpp | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msource/jucePlugin/PluginEditor.h | 15++++++++++++---
Msource/jucePlugin/PluginProcessor.cpp | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msource/jucePlugin/PluginProcessor.h | 14++++++++++----
Msource/jucePlugin/VirusController.cpp | 9++++++++-
Msource/jucePlugin/VirusController.h | 5+++--
Msource/jucePlugin/version.h | 8++++----
8 files changed, 281 insertions(+), 25 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") -project(gearmulator VERSION 1.2.0) +project(gearmulator VERSION 1.2.1) include(base.cmake) diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp @@ -6,11 +6,18 @@ //============================================================================== AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) : AudioProcessorEditor(&p), processorRef(p), m_btSingleMode("Single Mode"), m_btMultiMode("Multi Mode"), - m_btLoadFile("Load bank"), - m_tempEditor(p) + m_btLoadFile("Load bank"), m_cmbMidiInput("Midi Input"), m_cmbMidiOutput("Midi Output"), + deviceManager(), m_tempEditor(p) { ignoreUnused (processorRef); + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300 Emulator"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300 Emulator"; + opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; + m_properties = new juce::PropertiesFile(opts); + // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. setSize(800, 800); @@ -31,11 +38,11 @@ AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudi m_btMultiMode.setToggleState(isMulti, juce::dontSendNotification); m_btSingleMode.setClickingTogglesState(true); m_btMultiMode.setClickingTogglesState(true); - m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, 0); + m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, m_btSingleMode.getY()); m_btMultiMode.setSize(120,30); addAndMakeVisible(m_btLoadFile); - m_btLoadFile.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + m_btMultiMode.getWidth() + 10, 0); + m_btLoadFile.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + m_btMultiMode.getWidth() + 10, m_btSingleMode.getY()); m_btLoadFile.setSize(120, 30); m_btLoadFile.onClick = [this]() { loadFile(); @@ -63,11 +70,121 @@ AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudi }; addAndMakeVisible(m_partSelectors[pt]); } + + auto midiIn = m_properties->getValue("midi_input", ""); + auto midiOut = m_properties->getValue("midi_output", ""); + if (midiIn != "") + { + processorRef.setMidiInput(midiIn); + } + if (midiOut != "") + { + processorRef.setMidiOutput(midiOut); + } + + m_cmbMidiInput.setSize(160, 30); + m_cmbMidiInput.setTopLeftPosition(0, 400); + m_cmbMidiOutput.setSize(160, 30); + m_cmbMidiOutput.setTopLeftPosition(164, 400); + addAndMakeVisible(m_cmbMidiInput); + addAndMakeVisible(m_cmbMidiOutput); + m_cmbMidiInput.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled"); + auto midiInputs = juce::MidiInput::getAvailableDevices(); + juce::StringArray midiInputNames; + midiInputNames.add(" - Midi In - "); + auto inIndex = 0; + for (int i = 0; i < midiInputs.size(); i++) + { + const auto input = midiInputs[i]; + if (processorRef.getMidiInput() != nullptr && input.identifier == processorRef.getMidiInput()->getIdentifier()) + { + inIndex = i + 1; + } + midiInputNames.add(input.name); + } + m_cmbMidiInput.addItemList(midiInputNames, 1); + m_cmbMidiInput.setSelectedItemIndex(inIndex, juce::dontSendNotification); + m_cmbMidiOutput.setTextWhenNoChoicesAvailable("No MIDI Outputs Enabled"); + auto midiOutputs = juce::MidiOutput::getAvailableDevices(); + juce::StringArray midiOutputNames; + midiOutputNames.add(" - Midi Out - "); + auto outIndex = 0; + for (int i = 0; i < midiOutputs.size(); i++) + { + const auto output = midiOutputs[i]; + if (processorRef.getMidiOutput() != nullptr && output.identifier == processorRef.getMidiOutput()->getIdentifier()) + { + outIndex = i+1; + } + midiOutputNames.add(output.name); + } + m_cmbMidiOutput.addItemList(midiOutputNames, 1); + m_cmbMidiOutput.setSelectedItemIndex(outIndex, juce::dontSendNotification); + m_cmbMidiInput.onChange = [this]() { updateMidiInput(m_cmbMidiInput.getSelectedItemIndex()); }; + m_cmbMidiOutput.onChange = [this]() { updateMidiOutput(m_cmbMidiOutput.getSelectedItemIndex()); }; addAndMakeVisible(m_tempEditor); startTimerHz(5); } +void AudioPluginAudioProcessorEditor::updateMidiInput(int index) +{ + auto list = juce::MidiInput::getAvailableDevices(); + + if (index == 0) + { + m_properties->setValue("midi_input", ""); + m_properties->save(); + m_lastInputIndex = index; + m_cmbMidiInput.setSelectedItemIndex(index, juce::dontSendNotification); + return; + } + index--; + auto newInput = list[index]; + + if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier)) + deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true); + + if (!processorRef.setMidiInput(newInput.identifier)) + { + m_cmbMidiInput.setSelectedItemIndex(0, juce::dontSendNotification); + m_lastInputIndex = 0; + return; + } + + m_properties->setValue("midi_input", newInput.identifier); + m_properties->save(); + + m_cmbMidiInput.setSelectedItemIndex(index+1, juce::dontSendNotification); + m_lastInputIndex = index; +} +void AudioPluginAudioProcessorEditor::updateMidiOutput(int index) +{ + auto list = juce::MidiOutput::getAvailableDevices(); + + if (index == 0) + { + m_properties->setValue("midi_output", ""); + m_properties->save(); + m_cmbMidiOutput.setSelectedItemIndex(index, juce::dontSendNotification); + m_lastOutputIndex = index; + processorRef.setMidiOutput(""); + return; + } + index--; + auto newOutput = list[index]; + if(!processorRef.setMidiOutput(newOutput.identifier)) + { + m_cmbMidiOutput.setSelectedItemIndex(0, juce::dontSendNotification); + m_lastOutputIndex = 0; + return; + } + m_properties->setValue("midi_output", newOutput.identifier); + m_properties->save(); + + m_cmbMidiOutput.setSelectedItemIndex(index+1, juce::dontSendNotification); + m_lastOutputIndex = index; +} AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() { diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h @@ -1,7 +1,7 @@ #pragma once #include "PluginProcessor.h" - +#include <juce_audio_devices/juce_audio_devices.h> //============================================================================== class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer { @@ -10,24 +10,33 @@ public: ~AudioPluginAudioProcessorEditor() override; //============================================================================== + //void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override; void paint (juce::Graphics&) override; void resized() override; private: + void updateMidiInput(int index); + void updateMidiOutput(int index); void timerCallback() override; void loadFile(); + // This reference is provided as a quick way for your editor to // access the processor object that created it. AudioPluginAudioProcessor& processorRef; juce::GenericAudioProcessorEditor m_tempEditor; - juce::TextButton m_partSelectors[16]; juce::TextButton m_btSingleMode; juce::TextButton m_btMultiMode; juce::TextButton m_btLoadFile; juce::String m_previousPath; - + juce::ComboBox m_cmbMidiInput; + juce::ComboBox m_cmbMidiOutput; + juce::AudioDeviceManager deviceManager; + juce::PropertiesFile *m_properties; + int m_lastInputIndex = 0; + int m_lastOutputIndex = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) + }; diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp @@ -1,6 +1,7 @@ #include "PluginProcessor.h" #include "PluginEditor.h" - +#include <juce_audio_processors/juce_audio_processors.h> +#include <juce_audio_devices/juce_audio_devices.h> #include "../synthLib/os.h" //============================================================================== @@ -8,7 +9,7 @@ AudioPluginAudioProcessor::AudioPluginAudioProcessor() : AudioProcessor(BusesProperties() .withInput("Input", juce::AudioChannelSet::stereo(), true) .withOutput("Output", juce::AudioChannelSet::stereo(), true)), - m_device(synthLib::findROM()), m_plugin(&m_device) + m_device(synthLib::findROM()), m_plugin(&m_device), m_midiOutput(), m_midiInput(), juce::MidiInputCallback() { auto &ctrl = getController(); // init controller } @@ -199,8 +200,11 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, m_midiOut.clear(); m_plugin.getMidiOut(m_midiOut); + if (!m_midiOut.empty()) - m_controller->dispatchVirusOut(m_midiOut); + { + m_controller->dispatchVirusOut(m_midiOut); + } for (size_t i = 0; i < m_midiOut.size(); ++i) { @@ -209,10 +213,30 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, if (e.source == synthLib::MidiEventSourceEditor) continue; - if(e.sysex.empty()) - midiMessages.addEvent(juce::MidiMessage (e.a, e.b, e.c, 0.0), 0); + if (e.sysex.empty()) + { + + midiMessages.addEvent(juce::MidiMessage(e.a, e.b, e.c, 0.0), 0); + + // additionally send to the midi output we've selected in the editor + if (m_midiOutput != nullptr) + { + m_midiOutput.get()->sendMessageNow(juce::MidiMessage(e.a, e.b, e.c, 0.0)); + } + } else - midiMessages.addEvent(juce::MidiMessage (&e.sysex[0], static_cast<int>(e.sysex.size()), 0.0), 0); + { + + // additionally send to the midi output we've selected in the editor + if (m_midiOutput != nullptr) + { + m_midiOutput.get()->sendMessageNow( + juce::MidiMessage(&e.sysex[0], static_cast<int>(e.sysex.size()), 0.0)); + } + midiMessages.addEvent(juce::MidiMessage(&e.sysex[0], static_cast<int>(e.sysex.size()), 0.0), 0); + } + + } } @@ -270,6 +294,98 @@ void AudioPluginAudioProcessor::addMidiEvent(const synthLib::SMidiEvent& ev) m_plugin.addMidiEvent(ev); } +juce::MidiOutput *AudioPluginAudioProcessor::getMidiOutput() { return m_midiOutput.get(); } +juce::MidiInput *AudioPluginAudioProcessor::getMidiInput() { return m_midiInput.get(); } + +bool AudioPluginAudioProcessor::setMidiOutput(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 AudioPluginAudioProcessor::setMidiInput(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 AudioPluginAudioProcessor::handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) +{ + auto raw = message.getSysExData(); + if (raw != 0) + { + auto count = message.getSysExDataSize(); + auto syx = Virus::SysEx(); + syx.push_back((uint8_t)0xf0); + for (int i = 0; i < count; i++) + { + syx.push_back((uint8_t)raw[i]); + } + syx.push_back((uint8_t)0xf7); + synthLib::SMidiEvent sm; + sm.source = synthLib::MidiEventSourcePlugin; + sm.sysex = syx; + getController().parseMessage(syx); + + + addMidiEvent(sm); + if (m_midiOutput != nullptr && m_midiOutput.get() != 0) + { + std::vector<synthLib::SMidiEvent> data; + getLastMidiOut(data); + if (data.size() > 0) + { + auto msg = juce::MidiMessage::createSysExMessage(data.data(), data.size()); + + m_midiOutput->sendMessageNow(msg); + } + } + } + else + { + auto count = message.getRawDataSize(); + auto raw = message.getRawData(); + if (count >= 1 && count <= 3) + { + synthLib::SMidiEvent sm; + sm.source = synthLib::MidiEventSourcePlugin; + sm.a = raw[0]; + sm.b = count > 1 ? raw[1] : 0; + sm.c = count > 2 ? raw[2] : 0; + addMidiEvent(sm); + } + else + { + synthLib::SMidiEvent sm; + sm.source = synthLib::MidiEventSourcePlugin; + auto syx = Virus::SysEx(); + for (int i = 0; i < count; i++) + { + syx.push_back((uint8_t)raw[i]); + } + sm.sysex = syx; + addMidiEvent(sm); + } + } +} + Virus::Controller &AudioPluginAudioProcessor::getController() { if (m_controller == nullptr) diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h @@ -1,13 +1,13 @@ #pragma once #include <juce_audio_processors/juce_audio_processors.h> - +#include <juce_audio_devices/juce_audio_devices.h> #include "../synthLib/plugin.h" #include "../virusLib/device.h" #include "VirusController.h" //============================================================================== -class AudioPluginAudioProcessor : public juce::AudioProcessor +class AudioPluginAudioProcessor : public juce::AudioProcessor, juce::MidiInputCallback { public: //============================================================================== @@ -54,12 +54,18 @@ public: bool isPluginValid() const { return m_plugin.isValid(); } void getLastMidiOut(std::vector<synthLib::SMidiEvent>& dst); void addMidiEvent(const synthLib::SMidiEvent& ev); - + bool setMidiOutput(juce::String _out); + juce::MidiOutput* getMidiOutput(); + bool setMidiInput(juce::String _in); + juce::MidiInput* getMidiInput(); + void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override; + // _____________ // private: std::unique_ptr<Virus::Controller> m_controller; - + std::unique_ptr<juce::MidiOutput> m_midiOutput; + std::unique_ptr<juce::MidiInput> m_midiInput; void setState(const void *_data, size_t _sizeInBytes); //============================================================================== diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -1,3 +1,4 @@ +#include <juce_audio_processors/juce_audio_processors.h> #include "VirusController.h" #include "PluginProcessor.h" @@ -104,6 +105,12 @@ namespace Virus case MessageType::PARAM_CHANGE_C: parseParamChange(msg); break; + case MessageType::REQUEST_SINGLE: + case MessageType::REQUEST_MULTI: + case MessageType::REQUEST_GLOBAL: + case MessageType::REQUEST_TOTAL: + sendSysEx(msg); + break; default: std::cout << "Controller: Begin Unhandled SysEx! --" << std::endl; printMessage(msg); @@ -1661,6 +1668,6 @@ namespace Virus { const juce::ScopedLock sl(m_eventQueueLock); - m_virusOut.insert(m_virusOut.end(), newData.begin(), newData.end()); + m_virusOut.insert(m_virusOut.end(), newData.begin(), newData.end()); } }; // namespace Virus diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -37,6 +37,7 @@ namespace Virus juce::String getCurrentPartPresetName(uint8_t part); uint32_t getBankCount() const { return static_cast<uint32_t>(m_singles.size()); } void parseMessage(const SysEx &); + void sendSysEx(const SysEx &); private: void timerCallback() override; @@ -93,8 +94,8 @@ namespace Virus void parseSingle(const SysEx &); void parseMulti(const SysEx &); void parseParamChange(const SysEx &); - void parseControllerDump(synthLib::SMidiEvent &); - void sendSysEx(const SysEx &); + void parseControllerDump(synthLib::SMidiEvent &); + std::vector<uint8_t> constructMessage(SysEx msg); AudioPluginAudioProcessor &m_processor; diff --git a/source/jucePlugin/version.h b/source/jucePlugin/version.h @@ -1,4 +1,4 @@ -#pragma once - -static constexpr const char* const g_pluginVersionString = "1.2.0"; -static constexpr uint32_t g_pluginVersion = 120; +#pragma once + +static constexpr const char* const g_pluginVersionString = "1.2.1"; +static constexpr uint32_t g_pluginVersion = 121;