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 b92ef1704a1851a0690b48cb1fc2cc6506c0e733
parent fb419ef3affabb7b19fe762f129fb6037e9a7aa8
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sun, 25 Jun 2023 23:02:24 +0200

Osirus open source update

Diffstat:
MCMakeLists.txt | 2+-
Msource/jucePlugin/PluginProcessor.cpp | 74++++++++++----------------------------------------------------------------
Msource/jucePlugin/PluginProcessor.h | 45+++++++++++----------------------------------
Msource/jucePluginEditorLib/patchbrowser.cpp | 2+-
Msource/jucePluginEditorLib/pluginEditorState.cpp | 2++
Msource/jucePluginEditorLib/pluginEditorState.h | 2++
Msource/jucePluginEditorLib/pluginProcessor.cpp | 6++++++
Msource/jucePluginEditorLib/pluginProcessor.h | 3+++
Msource/jucePluginLib/CMakeLists.txt | 1+
Asource/jucePluginLib/dummydevice.cpp | 8++++++++
Asource/jucePluginLib/dummydevice.h | 22++++++++++++++++++++++
Msource/jucePluginLib/parameter.cpp | 45+++++++++++++++++++++++++++++++--------------
Msource/jucePluginLib/parameter.h | 9++++++---
Msource/jucePluginLib/parameterbinding.cpp | 4++--
Msource/jucePluginLib/processor.cpp | 182++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msource/jucePluginLib/processor.h | 35++++++++++++++++++++++++++++++-----
Msource/synthLib/CMakeLists.txt | 2++
Asource/synthLib/binarystream.h | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/synthLib/device.h | 4+---
Asource/synthLib/deviceException.cpp | 21+++++++++++++++++++++
Asource/synthLib/deviceException.h | 20++++++++++++++++++++
Msource/synthLib/deviceTypes.h | 8++++++++
Msource/synthLib/midiBufferParser.cpp | 14+-------------
Msource/synthLib/midiBufferParser.h | 24++++++++++++++++++++++++
Msource/virusLib/device.cpp | 4+++-
Mtemp/cmake_win64/gearmulator.sln.DotSettings | 1+
26 files changed, 522 insertions(+), 151 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -10,7 +10,7 @@ else() set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") endif() -project(gearmulator VERSION 1.2.30) +project(gearmulator VERSION 1.2.36) include(base.cmake) include(CTest) diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp @@ -27,8 +27,6 @@ AudioPluginAudioProcessor::AudioPluginAudioProcessor() : .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) #endif , getConfigOptions()) - , m_rom(std::string()) - , m_device(m_rom), m_plugin(&m_device) { m_clockTempoParam = getController().getParameterIndexByName(Virus::g_paramClockTempo); @@ -71,55 +69,6 @@ bool AudioPluginAudioProcessor::isMidiEffect() const #endif } -double AudioPluginAudioProcessor::getTailLengthSeconds() const -{ - return 0.0; -} - -int AudioPluginAudioProcessor::getNumPrograms() -{ - return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, - // so this should be at least 1, even if you're not really implementing programs. -} - -int AudioPluginAudioProcessor::getCurrentProgram() -{ - return 0; -} - -void AudioPluginAudioProcessor::setCurrentProgram (int index) -{ - juce::ignoreUnused (index); -} - -const juce::String AudioPluginAudioProcessor::getProgramName (int index) -{ - juce::ignoreUnused (index); - return "default"; -} - -void AudioPluginAudioProcessor::changeProgramName (int index, const juce::String& newName) -{ - juce::ignoreUnused (index, newName); -} - -//============================================================================== -void AudioPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) -{ - // Use this method as the place to do any pre-playback - // initialisation that you need.. - m_plugin.setSamplerate(static_cast<float>(sampleRate)); - m_plugin.setBlockSize(samplesPerBlock); - - updateLatencySamples(); -} - -void AudioPluginAudioProcessor::releaseResources() -{ - // When playback stops, you can use this as an opportunity to free up any - // spare memory, etc. -} - bool AudioPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { // This is the place where you check if the layout is supported. @@ -211,7 +160,7 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, ev.offset = metadata.samplePosition; - m_plugin.addMidiEvent(ev); + getPlugin().addMidiEvent(ev); } midiMessages.clear(); @@ -231,15 +180,15 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, } } - m_plugin.process(inputs, outputs, buffer.getNumSamples(), static_cast<float>(pos.bpm), + getPlugin().process(inputs, outputs, buffer.getNumSamples(), static_cast<float>(pos.bpm), static_cast<float>(pos.ppqPosition), pos.isPlaying); m_midiOut.clear(); - m_plugin.getMidiOut(m_midiOut); + getPlugin().getMidiOut(m_midiOut); if (!m_midiOut.empty()) { - static_cast<Virus::Controller&>(getController()).addPluginMidiOut(m_midiOut); + getController().addPluginMidiOut(m_midiOut); } for (auto& e : m_midiOut) @@ -269,10 +218,6 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, } //============================================================================== -bool AudioPluginAudioProcessor::hasEditor() const -{ - return true; // (change this to false if you choose to not supply an editor) -} juce::AudioProcessorEditor* AudioPluginAudioProcessor::createEditor() { @@ -289,16 +234,17 @@ void AudioPluginAudioProcessor::updateLatencySamples() setLatencySamples(getPlugin().getLatencyInputToOutput()); } -bool AudioPluginAudioProcessor::setLatencyBlocks(uint32_t _blocks) +synthLib::Device* AudioPluginAudioProcessor::createDevice() { - if(!Processor::setLatencyBlocks(_blocks)) - return false; - updateLatencySamples(); - return true; + m_rom.reset(new virusLib::ROMFile(std::string())); + return new virusLib::Device(*m_rom); } pluginLib::Controller* AudioPluginAudioProcessor::createController() { + // force creation of device as the controller decides how to initialize based on the used ROM + getPlugin(); + return new Virus::Controller(*this); } diff --git a/source/jucePlugin/PluginProcessor.h b/source/jucePlugin/PluginProcessor.h @@ -13,70 +13,47 @@ class PluginEditorState; class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor { public: - //============================================================================== AudioPluginAudioProcessor(); ~AudioPluginAudioProcessor() override; - //============================================================================== - void prepareToPlay (double sampleRate, int samplesPerBlock) override; - void releaseResources() override; - bool isBusesLayoutSupported (const BusesLayout& layouts) const override; - void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override; - using AudioProcessor::processBlock; - - //============================================================================== juce::AudioProcessorEditor* createEditor() override; - bool hasEditor() const override; - - //============================================================================== const juce::String getName() const override; bool acceptsMidi() const override; bool producesMidi() const override; bool isMidiEffect() const override; - double getTailLengthSeconds() const override; - - //============================================================================== - int getNumPrograms() override; - int getCurrentProgram() override; - void setCurrentProgram (int index) override; - const juce::String getProgramName (int index) override; - void changeProgramName (int index, const juce::String& newName) override; // _____________ // - std::string getRomName() const + + std::string getRomName() const { - return juce::File(juce::String(m_rom.getFilename())).getFileNameWithoutExtension().toStdString(); + if(!m_rom) + return "<invalid>"; + return juce::File(juce::String(m_rom->getFilename())).getFileNameWithoutExtension().toStdString(); } virusLib::ROMFile::Model getModel() const { - return m_rom.getModel(); + return m_rom ? m_rom->getModel() : virusLib::ROMFile::Model::Invalid; } - bool setLatencyBlocks(uint32_t _blocks) override; // _____________ // private: + void updateLatencySamples() override; - synthLib::Plugin& getPlugin() override - { - return m_plugin; - } + synthLib::Device* createDevice() override; pluginLib::Controller* createController() override; - void updateLatencySamples(); - //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) - virusLib::ROMFile m_rom; - virusLib::Device m_device; - synthLib::Plugin m_plugin; - uint32_t m_clockTempoParam = 0xffffffff; + std::unique_ptr<virusLib::ROMFile> m_rom; + + uint32_t m_clockTempoParam = 0xffffffff; std::unique_ptr<PluginEditorState> m_editorState; }; diff --git a/source/jucePluginEditorLib/patchbrowser.cpp b/source/jucePluginEditorLib/patchbrowser.cpp @@ -354,7 +354,7 @@ namespace jucePluginEditorLib bool operator()(const std::shared_ptr<Patch>& _a, const std::shared_ptr<Patch>& _b) const { - return (compareElements(*_a, *_b) < 0) == m_forward; + return m_forward ? compareElements(*_a, *_b) < 0 : compareElements(*_a, *_b) > 0; } private: diff --git a/source/jucePluginEditorLib/pluginEditorState.cpp b/source/jucePluginEditorLib/pluginEditorState.cpp @@ -190,6 +190,8 @@ void PluginEditorState::openMenu() menu.addSubMenu("GUI Scale", scaleMenu); menu.addSubMenu("Latency (blocks)", latencyMenu); + initContextMenu(menu); + menu.showMenuAsync(juce::PopupMenu::Options()); } diff --git a/source/jucePluginEditorLib/pluginEditorState.h b/source/jucePluginEditorLib/pluginEditorState.h @@ -66,6 +66,8 @@ namespace jucePluginEditorLib void loadDefaultSkin(); + virtual void initContextMenu(juce::PopupMenu& _menu) {}; + protected: virtual genericUI::Editor* createEditor(const Skin& _skin, std::function<void()> _openMenuCallback) = 0; diff --git a/source/jucePluginEditorLib/pluginProcessor.cpp b/source/jucePluginEditorLib/pluginProcessor.cpp @@ -15,6 +15,12 @@ namespace jucePluginEditorLib getConfig().setValue("latencyBlocks", static_cast<int>(_blocks)); getConfig().saveIfNeeded(); + return true; } + + bool Processor::hasEditor() const + { + return true; // (change this to false if you choose to not supply an editor) + } } diff --git a/source/jucePluginEditorLib/pluginProcessor.h b/source/jucePluginEditorLib/pluginProcessor.h @@ -12,6 +12,9 @@ namespace jucePluginEditorLib juce::PropertiesFile& getConfig() { return m_config; } bool setLatencyBlocks(uint32_t _blocks) override; + + bool hasEditor() const override; + private: juce::PropertiesFile m_config; }; diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt @@ -4,6 +4,7 @@ project(jucePluginLib VERSION ${CMAKE_PROJECT_VERSION}) set(SOURCES event.cpp event.h controller.cpp controller.h + dummydevice.cpp dummydevice.h midipacket.cpp midipacket.h parameter.cpp parameter.h parameterbinding.cpp parameterbinding.h diff --git a/source/jucePluginLib/dummydevice.cpp b/source/jucePluginLib/dummydevice.cpp @@ -0,0 +1,8 @@ +#include "dummydevice.h" + +namespace pluginLib +{ + void DummyDevice::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) + { + } +} diff --git a/source/jucePluginLib/dummydevice.h b/source/jucePluginLib/dummydevice.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../synthLib/device.h" + +namespace pluginLib +{ + class DummyDevice : public synthLib::Device + { + public: + float getSamplerate() const override { return 44100.0f; } + bool isValid() const override { return false; } + bool getState(std::vector<uint8_t>& _state, synthLib::StateType _type) override { return false; } + bool setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) override { return false; } + uint32_t getChannelCountIn() override { return 2; } + uint32_t getChannelCountOut() override { return 2; } + + protected: + void readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) override {} + void processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) override; + bool sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) override { return false; } + }; +} diff --git a/source/jucePluginLib/parameter.cpp b/source/jucePluginLib/parameter.cpp @@ -16,15 +16,7 @@ namespace pluginLib void Parameter::valueChanged(juce::Value &) { - const uint8_t value = roundToInt(m_value.getValue()); - jassert (m_range.getRange().contains(value) || m_range.end == value); - if (value != m_lastValue) - { - // ignore initial update - if(m_lastValue != -1) - m_ctrl.sendParameterChange(*this, value); - m_lastValue = value; - } + sendToSynth(); for (const auto& func : onValueChanged) func.second(); @@ -46,8 +38,7 @@ namespace pluginLib { beginChangeGesture(); const float v = convertTo0to1(static_cast<float>(newValue)); - setValue(v, _origin); - sendValueChangedMessageToListeners(v); + setValueNotifyingHost(v, _origin); endChangeGesture(); } else @@ -56,6 +47,30 @@ namespace pluginLib } } + void Parameter::sendToSynth() + { + const float floatValue = m_value.getValue(); + const auto value = juce::roundToInt(floatValue); + + jassert(m_range.getRange().contains(floatValue) || m_range.end == floatValue); + jassert(value >= 0 && value <= 127); + + if (value == m_lastValue) + return; + + // ignore initial update + if (m_lastValue != -1) + m_ctrl.sendParameterChange(*this, static_cast<uint8_t>(value)); + + m_lastValue = value; + } + + void Parameter::setValueNotifyingHost(const float _value, const ChangedBy _origin) + { + setValue(_value, _origin); + sendValueChangedMessageToListeners(_value); + } + bool Parameter::isMetaParameter() const { return !m_derivedParameters.empty(); @@ -71,8 +86,11 @@ namespace pluginLib if (m_changingDerivedValues) return; + const auto floatValue = convertFrom0to1(_newValue); m_lastValueOrigin = _origin; - m_value.setValue(convertFrom0to1(_newValue)); + m_value.setValue(floatValue); + + sendToSynth(); m_changingDerivedValues = true; @@ -99,8 +117,7 @@ namespace pluginLib { beginChangeGesture(); const auto v = convertTo0to1(static_cast<float>(clampedValue)); - setValue(v, _origin); - sendValueChangedMessageToListeners(v); + setValueNotifyingHost(v, _origin); endChangeGesture(); } else diff --git a/source/jucePluginLib/parameter.h b/source/jucePluginLib/parameter.h @@ -27,10 +27,10 @@ namespace pluginLib Parameter(Controller& _controller, const Description& _desc, uint8_t _partNum, int uniqueId); - juce::Value &getValueObject() { return m_value; }; - const juce::Value &getValueObject() const { return m_value; }; + juce::Value &getValueObject() { return m_value; } + const juce::Value &getValueObject() const { return m_value; } - const Description& getDescription() const { return m_desc; }; + const Description& getDescription() const { return m_desc; } uint8_t getPart() const { return m_partNum; } @@ -80,10 +80,13 @@ namespace pluginLib ChangedBy getChangeOrigin() const { return m_lastValueOrigin; } + void setValueNotifyingHost(float _value, ChangedBy _origin); + private: static juce::String genId(const Description &d, int part, int uniqueId); void valueChanged(juce::Value &) override; void setDerivedValue(int _value, ChangedBy _origin); + void sendToSynth(); Controller &m_ctrl; const Description m_desc; diff --git a/source/jucePluginLib/parameterbinding.cpp b/source/jucePluginLib/parameterbinding.cpp @@ -24,7 +24,7 @@ namespace pluginLib void ParameterBinding::MouseListener::mouseDrag(const juce::MouseEvent& event) { - m_param->setValueNotifyingHost(m_param->convertTo0to1(static_cast<float>(m_slider->getValue()))); + m_param->setValueNotifyingHost(m_param->convertTo0to1(static_cast<float>(m_slider->getValue())), Parameter::ChangedBy::ControlChange); } ParameterBinding::~ParameterBinding() @@ -122,7 +122,7 @@ namespace pluginLib if(v->getDescription().isPublic) { v->beginChangeGesture(); - v->setValueNotifyingHost(v->convertTo0to1(static_cast<float>(id - 1))); + v->setValueNotifyingHost(v->convertTo0to1(static_cast<float>(id - 1)), Parameter::ChangedBy::ControlChange); v->endChangeGesture(); } v->getValueObject().setValue(id - 1); diff --git a/source/jucePluginLib/processor.cpp b/source/jucePluginLib/processor.cpp @@ -1,7 +1,24 @@ #include "processor.h" +#include "dummydevice.h" + +#include "../synthLib/deviceException.h" +#include "../synthLib/os.h" +#include "../synthLib/binarystream.h" + +#include "dsp56kEmu/logging.h" + +namespace synthLib +{ + class DeviceException; +} namespace pluginLib { + constexpr char g_saveMagic[] = "DSP56300"; + constexpr uint32_t g_saveVersion = 1; + + using PluginStream = synthLib::BinaryStream<uint32_t>; + Processor::Processor(const BusesProperties& _busesProperties) : juce::AudioProcessor(_busesProperties) { } @@ -9,6 +26,8 @@ namespace pluginLib Processor::~Processor() { m_controller.reset(); + m_plugin.reset(); + m_device.reset(); } void Processor::getLastMidiOut(std::vector<synthLib::SMidiEvent>& dst) @@ -124,9 +143,66 @@ namespace pluginLib return *m_controller; } + synthLib::Plugin& Processor::getPlugin() + { + if(m_plugin) + return *m_plugin; + + try + { + m_device.reset(createDevice()); + } + catch(const synthLib::DeviceException& e) + { + LOG("Failed to create device: " << e.what()); + + std::string msg = e.what(); + + m_deviceError = e.errorCode(); + + if(e.errorCode() == synthLib::DeviceError::FirmwareMissing) + { + msg += "\n\n"; + msg += "The firmware file needs to be located next to the plugin."; + msg += "\n\n"; + msg += "The plugin was loaded from path:\n\n" + synthLib::getModulePath() + "\n\nCopy the requested file to this path and reload the plugin."; + } + juce::NativeMessageBox::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Device Initialization failed", msg); + } + + if(!m_device) + { + m_device.reset(new DummyDevice()); + } + + m_plugin.reset(new synthLib::Plugin(m_device.get())); + + return *m_plugin; + } + bool Processor::setLatencyBlocks(uint32_t _blocks) { - return getPlugin().setLatencyBlocks(_blocks); + if (!getPlugin().setLatencyBlocks(_blocks)) + return false; + updateLatencySamples(); + return true; + } + + //============================================================================== + void Processor::prepareToPlay(double sampleRate, int samplesPerBlock) + { + // Use this method as the place to do any pre-playback + // initialisation that you need.. + getPlugin().setSamplerate(static_cast<float>(sampleRate)); + getPlugin().setBlockSize(samplesPerBlock); + + updateLatencySamples(); + } + + void Processor::releaseResources() + { + // When playback stops, you can use this as an opportunity to free up any + // spare memory, etc. } //============================================================================== @@ -136,23 +212,35 @@ namespace pluginLib // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. - std::vector<uint8_t> state; - getPlugin().getState(state, synthLib::StateTypeGlobal); - destData.append(&state[0], state.size()); + std::vector<uint8_t> buffer; + getPlugin().getState(buffer, synthLib::StateTypeGlobal); + + PluginStream ss; + ss.write(g_saveMagic); + ss.write(g_saveVersion); + ss.write(buffer); + buffer.clear(); + saveCustomData(buffer); + ss.write(buffer); + + std::vector<uint8_t> buf; + ss.toVector(buf); + + destData.append(buf.data(), buf.size()); } - void Processor::setStateInformation (const void* data, int sizeInBytes) + void Processor::setStateInformation (const void* _data, const int _sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. - setState(data, sizeInBytes); + setState(_data, _sizeInBytes); } void Processor::getCurrentProgramStateInformation(juce::MemoryBlock& destData) { std::vector<uint8_t> state; getPlugin().getState(state, synthLib::StateTypeCurrentProgram); - destData.append(&state[0], state.size()); + destData.append(state.data(), state.size()); } void Processor::setCurrentProgramStateInformation(const void* data, int sizeInBytes) @@ -160,16 +248,90 @@ namespace pluginLib setState(data, sizeInBytes); } - void Processor::setState(const void* _data, size_t _sizeInBytes) + void Processor::setState(const void* _data, const size_t _sizeInBytes) { if(_sizeInBytes < 1) return; std::vector<uint8_t> state; state.resize(_sizeInBytes); - memcpy(&state[0], _data, _sizeInBytes); - getPlugin().setState(state); + memcpy(state.data(), _data, _sizeInBytes); + + PluginStream ss(state); + + if (ss.checkString(g_saveMagic)) + { + try + { + const std::string magic = ss.readString(); + + if (magic != g_saveMagic) + return; + + const auto version = ss.read<uint32_t>(); + + if (version != g_saveVersion) + return; + + std::vector<uint8_t> buffer; + ss.read(buffer); + getPlugin().setState(buffer); + ss.read(buffer); + + try + { + loadCustomData(buffer); + } + catch (std::range_error&) + { + } + } + catch (std::range_error& e) + { + LOG("Failed to read state: " << e.what()); + return; + } + } + else + { + getPlugin().setState(state); + } + if (hasController()) getController().onStateLoaded(); } + + //============================================================================== + + int Processor::getNumPrograms() + { + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. + } + + int Processor::getCurrentProgram() + { + return 0; + } + + void Processor::setCurrentProgram(int _index) + { + juce::ignoreUnused(_index); + } + + const juce::String Processor::getProgramName(int _index) + { + juce::ignoreUnused(_index); + return "default"; + } + + void Processor::changeProgramName(int _index, const juce::String& _newName) + { + juce::ignoreUnused(_index, _newName); + } + + double Processor::getTailLengthSeconds() const + { + return 0.0f; + } } diff --git a/source/jucePluginLib/processor.h b/source/jucePluginLib/processor.h @@ -34,28 +34,53 @@ namespace pluginLib Controller& getController(); bool isPluginValid() { return getPlugin().isValid(); } - virtual synthLib::Plugin& getPlugin() = 0; + synthLib::Plugin& getPlugin(); + + virtual synthLib::Device* createDevice() = 0; + bool hasController() const { return m_controller.get(); } virtual bool setLatencyBlocks(uint32_t _blocks); + virtual void updateLatencySamples() = 0; + + virtual void saveCustomData(std::vector<uint8_t>& _targetBuffer) {} + virtual void loadCustomData(const std::vector<uint8_t>& _sourceBuffer) {} private: - //============================================================================== + void prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock) override; + void releaseResources() override; + + //============================================================================== void getStateInformation (juce::MemoryBlock& destData) override; - void setStateInformation (const void* data, int sizeInBytes) override; + void setStateInformation (const void* _data, int _sizeInBytes) override; void getCurrentProgramStateInformation (juce::MemoryBlock& destData) override; void setCurrentProgramStateInformation (const void* data, int sizeInBytes) override; void setState(const void *_data, size_t _sizeInBytes); - virtual Controller* createController() = 0; + //============================================================================== + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram(int _index) override; + const juce::String getProgramName(int _index) override; + void changeProgramName(int _index, const juce::String &_newName) override; + + //============================================================================== + double getTailLengthSeconds() const override; + //============================================================================== + virtual Controller *createController() = 0; + + std::unique_ptr<Controller> m_controller{}; - std::unique_ptr<pluginLib::Controller> m_controller{}; + synthLib::DeviceError getDeviceError() const { return m_deviceError; } protected: + 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{}; diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -6,8 +6,10 @@ add_library(synthLib STATIC) set(SOURCES audiobuffer.cpp audiobuffer.h audioTypes.h + binarystream.h configFile.cpp configFile.h device.cpp device.h + deviceException.cpp deviceException.h deviceTypes.h hybridcontainer.h midiBufferParser.cpp midiBufferParser.h diff --git a/source/synthLib/binarystream.h b/source/synthLib/binarystream.h @@ -0,0 +1,133 @@ +#pragma once + +#include <cstdint> +#include <iosfwd> +#include <sstream> +#include <vector> + +namespace synthLib +{ + template<typename SizeType> + class BinaryStream final : std::stringstream + { + public: + BinaryStream() = default; + + template<typename T> explicit BinaryStream(const std::vector<T>& _data) + { + std::stringstream::write(reinterpret_cast<const char*>(_data.data()), _data.size() * sizeof(T)); + seekg(0); + } + + // ___________________________________ + // tools + // + + void toVector(std::vector<uint8_t>& _buffer) + { + const auto size = tellp(); + if(size <= 0) + { + _buffer.clear(); + return; + } + _buffer.resize(size); + seekg(0); + std::stringstream::read(reinterpret_cast<char*>(_buffer.data()), size); + } + + bool checkString(const std::string& _str) + { + const auto pos = tellg(); + + const auto size = read<SizeType>(); + if (size != _str.size()) + { + seekg(pos); + return false; + } + std::string s; + s.resize(size); + std::stringstream::read(s.data(), size); + const auto result = _str == s; + seekg(pos); + return result; + } + + // ___________________________________ + // write + // + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const T& _value) + { + std::stringstream::write(reinterpret_cast<const char*>(&_value), sizeof(_value)); + } + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const std::vector<T>& _vector) + { + const auto size = static_cast<SizeType>(_vector.size()); + write(size); + if(size) + std::stringstream::write(reinterpret_cast<const char*>(_vector.data()), sizeof(T) * size); + } + + void write(const std::string& _string) + { + const auto s = static_cast<SizeType>(_string.size()); + write(s); + std::stringstream::write(_string.c_str(), s); + } + + void write(const char* const _value) + { + write(std::string(_value)); + } + + // ___________________________________ + // read + // + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> T read() + { + T v{}; + std::stringstream::read(reinterpret_cast<char*>(&v), sizeof(v)); + checkFail(); + return v; + } + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void read(std::vector<T>& _vector) + { + const auto size = read<SizeType>(); + checkFail(); + if (!size) + { + _vector.clear(); + return; + } + _vector.resize(size); + std::stringstream::read(reinterpret_cast<char*>(_vector.data()), sizeof(T) * size); + checkFail(); + } + + std::string readString() + { + const auto size = read<SizeType>(); + std::string s; + s.resize(size); + std::stringstream::read(s.data(), size); + checkFail(); + return s; + } + + // ___________________________________ + // helpers + // + + private: + void checkFail() const + { + if(fail()) + throw std::range_error("end-of-stream"); + } + }; +} diff --git a/source/synthLib/device.h b/source/synthLib/device.h @@ -4,10 +4,8 @@ #include "audioTypes.h" #include "deviceTypes.h" -#include "../synthLib/midiTypes.h" -#include "../dsp56300/source/dsp56kEmu/dspthread.h" -#include "../dsp56300/source/dsp56kEmu/memory.h" +#include "../synthLib/midiTypes.h" namespace synthLib { diff --git a/source/synthLib/deviceException.cpp b/source/synthLib/deviceException.cpp @@ -0,0 +1,21 @@ +#include "deviceException.h" + +#include <string> + +namespace synthLib +{ + static std::string createDefaultErrorMessage(const DeviceError _error) + { + switch (_error) + { + case DeviceError::None: return "No Error, code " + std::to_string(static_cast<int32_t>(_error)); + case DeviceError::Unknown: return "Unknown Error, code " + std::to_string(static_cast<int32_t>(_error)); + case DeviceError::FirmwareMissing: return "The firmware file for this device is missing. Copy the firmware next to the plugin and restart it."; + default:; return "Error code " + std::to_string(static_cast<int32_t>(_error)); + } + } + + DeviceException::DeviceException(DeviceError _error, const std::string& _message) : std::runtime_error(_message.empty() ? createDefaultErrorMessage(_error) : _message), m_errorCode(_error) + { + } +} diff --git a/source/synthLib/deviceException.h b/source/synthLib/deviceException.h @@ -0,0 +1,20 @@ +#pragma once + +#include "deviceTypes.h" + +#include <stdexcept> +#include <string> + +namespace synthLib +{ + class DeviceException : public std::runtime_error + { + public: + explicit DeviceException(DeviceError _error, const std::string& _message = std::string()); + + DeviceError errorCode() const { return m_errorCode; } + + private: + const DeviceError m_errorCode; + }; +} diff --git a/source/synthLib/deviceTypes.h b/source/synthLib/deviceTypes.h @@ -7,4 +7,12 @@ namespace synthLib StateTypeGlobal, StateTypeCurrentProgram, }; + + enum class DeviceError + { + Invalid = -1, + None = 0, + Unknown = 1, + FirmwareMissing = 2, + }; } diff --git a/source/synthLib/midiBufferParser.cpp b/source/synthLib/midiBufferParser.cpp @@ -50,20 +50,8 @@ namespace synthLib ++m_pendingEventLen; - if(m_pendingEventLen == 1) - { - if((m_pendingEvent.a & 0xf0) == 0xf0) - flushEvent(); - } - else if(m_pendingEventLen == 2) - { - if(m_pendingEvent.a == synthLib::M_QUARTERFRAME || (m_pendingEvent.a & 0xf0) == synthLib::M_AFTERTOUCH) - flushEvent(); - } - else if(m_pendingEventLen == 3) - { + if(lengthFromStatusByte(m_pendingEvent.a) == m_pendingEventLen) flushEvent(); - } } } diff --git a/source/synthLib/midiBufferParser.h b/source/synthLib/midiBufferParser.h @@ -13,6 +13,30 @@ namespace synthLib void write(const std::vector<uint8_t>& _data); void getEvents(std::vector<synthLib::SMidiEvent>& _events); + static constexpr uint32_t lengthFromStatusByte(const uint8_t _sb) + { + switch (_sb & 0xf0) + { + case M_NOTEOFF: + case M_NOTEON: + case M_POLYPRESSURE: + case M_CONTROLCHANGE: + case M_PITCHBEND: return 3; + case M_PROGRAMCHANGE: + case M_AFTERTOUCH: return 2; + default: + switch(_sb) + { + case M_STARTOFSYSEX: + case M_ENDOFSYSEX: return 0; + case M_QUARTERFRAME: + case M_SONGSELECT: return 2; + case M_SONGPOSITION: return 3; + default: return 1; + } + } + } + private: void flushSysex(); void flushEvent(); diff --git a/source/virusLib/device.cpp b/source/virusLib/device.cpp @@ -5,6 +5,8 @@ #include "dsp56kEmu/jit.h" +#include "../synthLib/deviceException.h" + namespace virusLib { Device::Device(const ROMFile& _rom, const bool _createDebugger/* = false*/) @@ -12,7 +14,7 @@ namespace virusLib , m_rom(_rom) { if(!m_rom.isValid()) - return; + throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "Either a ROM file (.bin) or an OS update file (.mid) is required, but neither was found."); DspSingle* dsp1; createDspInstances(dsp1, m_dsp2, m_rom); diff --git a/temp/cmake_win64/gearmulator.sln.DotSettings b/temp/cmake_win64/gearmulator.sln.DotSettings @@ -13,6 +13,7 @@ <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=LCD/@EntryIndexedValue">LCD</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=PC/@EntryIndexedValue">PC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=ROM/@EntryIndexedValue">ROM</s:String> + <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=RX/@EntryIndexedValue">RX</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SP/@EntryIndexedValue">SP</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SR/@EntryIndexedValue">SR</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Abbreviations/=SSH/@EntryIndexedValue">SSH</s:String>