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 635ee4c2342be5c8dc8b8d8c0fec566eec8f340a
parent e5bfaa4d02148f0afaae32f0067f95397436326f
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Wed, 31 Jul 2024 12:09:41 +0200

begin state saving

Diffstat:
Msource/nord/n2x/n2xLib/CMakeLists.txt | 1+
Msource/nord/n2x/n2xLib/n2xdevice.cpp | 35+++++++++++++++++++----------------
Msource/nord/n2x/n2xLib/n2xdevice.h | 6+++++-
Msource/nord/n2x/n2xLib/n2xmiditypes.h | 1+
Asource/nord/n2x/n2xLib/n2xstate.cpp | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xstate.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 356 insertions(+), 17 deletions(-)

diff --git a/source/nord/n2x/n2xLib/CMakeLists.txt b/source/nord/n2x/n2xLib/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES n2xmiditypes.h n2xrom.cpp n2xrom.h n2xromdata.cpp n2xromdata.h + n2xstate.cpp n2xstate.h n2xtypes.h ) diff --git a/source/nord/n2x/n2xLib/n2xdevice.cpp b/source/nord/n2x/n2xLib/n2xdevice.cpp @@ -5,9 +5,8 @@ namespace n2x { - Device::Device() + Device::Device() : m_state(m_hardware) { - m_hardware.reset(new Hardware()); } float Device::getSamplerate() const @@ -17,19 +16,17 @@ namespace n2x bool Device::isValid() const { - return m_hardware->isValid(); + return m_hardware.isValid(); } bool Device::getState(std::vector<uint8_t>& _state, synthLib::StateType _type) { - // TODO - return false; + return m_state.getState(_state); } bool Device::setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) { - // TODO - return false; + return m_state.setState(_state); } uint32_t Device::getChannelCountIn() @@ -44,33 +41,36 @@ namespace n2x bool Device::setDspClockPercent(const uint32_t _percent) { - bool res = m_hardware->getDSPA().getPeriph().getEsaiClock().setSpeedPercent(_percent); - res &= m_hardware->getDSPB().getPeriph().getEsaiClock().setSpeedPercent(_percent); + bool res = m_hardware.getDSPA().getPeriph().getEsaiClock().setSpeedPercent(_percent); + res &= m_hardware.getDSPB().getPeriph().getEsaiClock().setSpeedPercent(_percent); return res; } uint32_t Device::getDspClockPercent() const { - return m_hardware->getDSPA().getPeriph().getEsaiClock().getSpeedPercent(); + return const_cast<Hardware&>(m_hardware).getDSPA().getPeriph().getEsaiClock().getSpeedPercent(); } uint64_t Device::getDspClockHz() const { - return m_hardware->getDSPA().getPeriph().getEsaiClock().getSpeedInHz(); + return const_cast<Hardware&>(m_hardware).getDSPA().getPeriph().getEsaiClock().getSpeedInHz(); } void Device::readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) { - m_hardware->getMidi().read(m_midiOutBuffer); + m_hardware.getMidi().read(m_midiOutBuffer); m_midiParser.write(m_midiOutBuffer); m_midiOutBuffer.clear(); m_midiParser.getEvents(_midiOut); + + for (const auto& midiOut : _midiOut) + m_state.receive(midiOut); } void Device::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) { - m_hardware->processAudio(_outputs, static_cast<uint32_t>(_samples), getExtraLatencySamples()); - m_numSamplesProcessed += _samples; + m_hardware.processAudio(_outputs, static_cast<uint32_t>(_samples), getExtraLatencySamples()); + m_numSamplesProcessed += static_cast<uint32_t>(_samples); } bool Device::sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) @@ -79,11 +79,14 @@ namespace n2x { auto e = _ev; e.offset += m_numSamplesProcessed + getExtraLatencySamples(); - m_hardware->sendMidi(e); + m_hardware.sendMidi(e); } else { - m_hardware->sendMidi(_ev); + if(m_state.receive(_ev)) + return true; + + m_hardware.sendMidi(_ev); } return true; } diff --git a/source/nord/n2x/n2xLib/n2xdevice.h b/source/nord/n2x/n2xLib/n2xdevice.h @@ -2,6 +2,9 @@ #include <memory> +#include "n2xhardware.h" +#include "n2xstate.h" + #include "wLib/wDevice.h" namespace n2x @@ -29,7 +32,8 @@ namespace n2x bool sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) override; private: - std::unique_ptr<Hardware> m_hardware = nullptr; + Hardware m_hardware; + State m_state; std::vector<uint8_t> m_midiOutBuffer; synthLib::MidiBufferParser m_midiParser; uint32_t m_numSamplesProcessed = 0; diff --git a/source/nord/n2x/n2xLib/n2xmiditypes.h b/source/nord/n2x/n2xLib/n2xmiditypes.h @@ -33,4 +33,5 @@ namespace n2x static constexpr uint32_t g_singleDumpSize = g_singleDataSize + g_sysexContainerSize; static constexpr uint32_t g_multiDumpSize = g_multiDataSize + g_sysexContainerSize; + static constexpr uint32_t g_patchRequestSize = g_sysexContainerSize; } diff --git a/source/nord/n2x/n2xLib/n2xstate.cpp b/source/nord/n2x/n2xLib/n2xstate.cpp @@ -0,0 +1,277 @@ +#include "n2xstate.h" + +#include <cassert> + +#include "n2xhardware.h" +#include "synthLib/midiToSysex.h" +#include "synthLib/midiTypes.h" + +namespace n2x +{ + static constexpr uint8_t g_singleDefault[] = + { + 72, // O2Pitch + 67, // O2PitchFine + 64, // Mix + 100, // Cutoff + 10, // Resonance + 0, // FilterEnvAmount + 0, // PW + 0, // FmDepth + 0, // FilterEnvA + 10, // FilterEnvD + 100, // FilterEnvS + 10, // FilterEnvR + 0, // AmpEnvA + 10, // AmpEnvD + 100, // AmpEnvS + 0, // AmpEnvR + 0, // Portamento + 127, // Gain + 0, // ModEnvA + 0, // ModEnvD + 0, // ModEnvLevel + 10, // Lfo1Rate + 0, // Lfo1Level + 10, // Lfo2Rate + 0, // ArpRange + 0, // O2PitchSens + 0, // O2PitchFineSens + 0, // MixSens + 0, // CutoffSens + 0, // ResonanceSens + 0, // FilterEnvAmountSens + 0, // PWSens + 0, // FmDepthSens + 0, // FilterEnvASens + 0, // FilterEnvDSens + 0, // FilterEnvSSens + 0, // FilterEnvRSens + 0, // AmpEnvASens + 0, // AmpEnvDSens + 0, // AmpEnvSSens + 0, // AmpEnvRSens + 0, // PortamentoSens + 0, // GainSens + 0, // ModEnvASens + 0, // ModEnvDSens + 0, // ModEnvLevelSens + 0, // Lfo1RateSens + 0, // Lfo1LevelSens + 0, // Lfo2RateSens + 0, // ArpRangeSens + 0, // O1Waveform + 0, // O2Waveform + 0, // Sync/RM/Dist + 0, // FilterType + 1, // O2Keytrack + 0, // FilterKeytrack + 0, // Lfo1Waveform + 0, // Lfo1Dest + 0, // VoiceMode + 0, // ModWheelDest + 0, // Unison + 0, // ModEnvDest + 0, // Auto + 0, // FilterVelocity + 0, // OctaveShift + 0, // Lfo2Dest + }; + + static_assert(std::size(g_singleDefault) == g_singleDataSize/2); + + using MultiDefaultData = std::array<uint8_t, ((g_multiDataSize - g_singleDataSize * 4)>>1)>; + + MultiDefaultData createMultiDefaultData() + { + uint32_t i=0; + + MultiDefaultData multi{}; + + auto set4 = [&](const uint8_t _a, const uint8_t _b, const uint8_t _c, const uint8_t _d) + { + multi[i++] = _a; multi[i++] = _b; multi[i++] = _c; multi[i++] = _d; + }; + + set4( 0, 1, 2, 3); // MIDI channel + set4( 0, 0, 0, 0); // LFO 1 Sync + set4( 0, 0, 0, 0); // LFO 2 Sync + set4( 0, 0, 0, 0); // Filter Env Trigger + set4( 0, 1, 2, 3); // Filter Env Trigger Midi Channel + set4(23,23,23,23); // Filter Env Trigger Note Number + set4( 0, 0, 0, 0); // Amp Env Trigger + set4( 0, 1, 2, 3); // Amp Env Trigger Midi Channel + set4(23,23,23,23); // Amp Env Trigger Note Number + set4( 0, 0, 0, 0); // Morph Trigger + set4( 0, 1, 2, 3); // Morph Trigger Midi Channel + set4(23,23,23,23); // Morph Trigger Note Number + multi[i++] = 2; // Bend Range + multi[i++] = 2; // Unison Detune + multi[i++] = 0; // Out Mode A&B (lower nibble) and C&D (upper nibble) + multi[i++] = 15; // Global Midi Channel + multi[i++] = 0; // Program Change Enable + multi[i++] = 1; // Midi Control + multi[i++] = 0; // Master Tune + multi[i++] = 0; // Pedal Type + multi[i++] = 1; // Local Control + multi[i++] = 0; // Keyboard Octave Shift + multi[i++] = 0; // Selected Channel + multi[i++] = 0; // Arp Midi Out + set4(1,0,0,0); // Channel Active + set4(0,0,0,0); // Program Select + set4(0,0,0,0); // Bank Select + set4(7,7,7,7); // Channel Pressure Amount + set4(0,0,0,0); // Channel Pressure Dest + set4(7,7,7,7); // Expression Pedal Amount + set4(0,0,0,0); // Expression Pedal Dest + multi[i++] = 0; // Keyboard Split + multi[i++] = 64; // Split Point + + assert(i == multi.size()); + + return multi; + } + + static const MultiDefaultData g_multiDefault = createMultiDefaultData(); + + State::State(Hardware& _hardware) : m_hardware(_hardware) + { + for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) + createDefaultSingle(m_singles[i], i); + createDefaultMulti(m_multi); + + receive(m_multi); + for (const auto& single : m_singles) + receive(single); + } + + bool State::getState(std::vector<uint8_t>& _state) + { + _state.reserve(sizeof(m_multi) + sizeof(m_singles)); + _state.insert(_state.begin(), m_multi.begin(), m_multi.end()); + for (const auto& single : m_singles) + _state.insert(_state.begin(), single.begin(), single.end()); + return true; + } + + bool State::setState(const std::vector<uint8_t>& _state) + { + std::vector<std::vector<uint8_t>> msgs; + synthLib::MidiToSysex::splitMultipleSysex(msgs, _state); + + for (auto& msg : msgs) + { + synthLib::SMidiEvent e; + e.source = synthLib::MidiEventSource::Host; + e.sysex = std::move(msg); + receive(e); + } + + return false; + } + + bool State::receive(const synthLib::SMidiEvent& _ev) + { + auto& sysex = _ev.sysex; + + if(sysex.size() <= SysexIndex::IdxMsgSpec) + return false; + + const auto bank = sysex[SysexIndex::IdxMsgType]; + const auto prog = sysex[SysexIndex::IdxMsgSpec]; + + if(sysex.size() == g_singleDumpSize) + { + if(bank != SysexByte::SingleDumpBankEditBuffer) + return false; + if(prog > m_singles.size()) + return false; + std::copy(sysex.begin(), sysex.end(), m_singles[prog].begin()); + send(_ev); + return true; + } + + if(sysex.size() == g_multiDumpSize) + { + if(bank != SysexByte::MultiDumpBankEditBuffer) + return false; + if(prog != 0) + return false; + std::copy(sysex.begin(), sysex.end(), m_multi.begin()); + send(_ev); + return true; + } + + if(sysex.size() == g_patchRequestSize) + return false; + + return false; + } + + void State::createDefaultSingle(SingleDump& _single, uint8_t _program) + { + createHeader(_single, SysexByte::SingleDumpBankEditBuffer, _program); + + uint32_t o = IdxMsgSpec + 1; + + for (const auto b : g_singleDefault) + { + _single[o++] = b & 0xf; + _single[o++] = (b>>4) & 0xf; + } + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + void State::copySingleToMulti(MultiDump& _multi, const SingleDump& _single, const uint8_t _index) + { + uint32_t i = SysexIndex::IdxMsgSpec + 1; + i += g_singleDataSize * _index; + std::copy(_single.begin() + g_sysexHeaderSize, _single.end() - g_sysexFooterSize, _multi.begin() + i); + } + + void State::createDefaultMulti(MultiDump& _multi) + { + createHeader(_multi, SysexByte::MultiDumpBankEditBuffer, 0); + + SingleDump single; + createDefaultSingle(single, 0); + + copySingleToMulti(_multi, single, 0); + copySingleToMulti(_multi, single, 1); + copySingleToMulti(_multi, single, 2); + copySingleToMulti(_multi, single, 3); + + uint32_t i = SysexIndex::IdxMsgSpec + 1 + 4 * g_singleDataSize; + assert(i == 264 * 2 + g_sysexHeaderSize); + + for (const auto b : g_multiDefault) + { + _multi[i++] = b & 0xf; + _multi[i++] = (b>>4) & 0xf; + } + assert(i + g_sysexFooterSize == g_multiDumpSize); + } + + void State::send(const synthLib::SMidiEvent& _e) const + { + if(_e.source == synthLib::MidiEventSource::Plugin) + return; + m_hardware.sendMidi(_e); + } + + template<size_t Size> + void State::createHeader(std::array<uint8_t, Size>& _buffer, uint8_t _msgType, uint8_t _msgSpec) + { + _buffer.fill(0); + + _buffer[0] = 0xf0; + + _buffer[SysexIndex::IdxClavia] = SysexByte::IdClavia; + _buffer[SysexIndex::IdxDevice] = SysexByte::DefaultDeviceId; + _buffer[SysexIndex::IdxN2x] = SysexByte::IdN2X; + _buffer[SysexIndex::IdxMsgType] = _msgType; + _buffer[SysexIndex::IdxMsgSpec] = _msgSpec; + + _buffer.back() = 0xf7; + } +} diff --git a/source/nord/n2x/n2xLib/n2xstate.h b/source/nord/n2x/n2xLib/n2xstate.h @@ -0,0 +1,53 @@ +#pragma once + +#include <array> +#include <vector> + +#include "n2xmiditypes.h" +#include "synthLib/midiTypes.h" + +namespace n2x +{ + class Hardware; + + class State + { + public: + enum class ReceiveOrder + { + SingleA, SingleB, SingleC, SingleD, Multi, Count + }; + + using SingleDump = std::array<uint8_t, g_singleDumpSize>; + using MultiDump = std::array<uint8_t, g_multiDumpSize>; + + State(Hardware& _hardware); + + bool getState(std::vector<uint8_t>& _state); + bool setState(const std::vector<uint8_t>& _state); + + bool receive(const synthLib::SMidiEvent& _ev); + + static void createDefaultSingle(SingleDump& _single, uint8_t _program); + static void copySingleToMulti(MultiDump& _multi, const SingleDump& _single, uint8_t _index); + static void createDefaultMulti(MultiDump& _multi); + + template<size_t Size> + static void createHeader(std::array<uint8_t, Size>& _buffer, uint8_t _msgType, uint8_t _msgSpec); + + private: + template<size_t Size> bool receive(const std::array<uint8_t, Size>& _data) + { + synthLib::SMidiEvent e; + e.sysex.insert(e.sysex.begin(), _data.begin(), _data.end()); + e.source = synthLib::MidiEventSource::Host; + return receive(e); + } + void send(const synthLib::SMidiEvent& _e) const; + + Hardware& m_hardware; + std::array<SingleDump, 4> m_singles; + MultiDump m_multi; + std::vector<ReceiveOrder> m_receiveOrder; + }; +}