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 207049e4eabe13df430e5e823437eff4425c25fd
parent 09236ccd2695d52925078e845211e72070cfbf54
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sun, 24 Nov 2024 02:31:54 +0100

add midi channel transformer

Diffstat:
Msource/synthLib/CMakeLists.txt | 1+
Msource/synthLib/device.cpp | 9++++++++-
Msource/synthLib/device.h | 7+++++++
Asource/synthLib/midiTranslator.cpp | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/midiTranslator.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 172 insertions(+), 1 deletion(-)

diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES midiBufferParser.cpp midiBufferParser.h midiClock.cpp midiClock.h midiToSysex.cpp midiToSysex.h + midiTranslator.cpp midiTranslator.h midiTypes.h os.cpp os.h plugin.cpp plugin.h diff --git a/source/synthLib/device.cpp b/source/synthLib/device.cpp @@ -30,7 +30,14 @@ namespace synthLib void Device::process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, const size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut) { for (const auto& ev : _midiIn) - sendMidi(ev, _midiOut); + { + m_translatorOut.clear(); + + m_midiTranslator.process(m_translatorOut, ev); + + for(auto & e : m_translatorOut) + sendMidi(e, _midiOut); + } processAudio(_inputs, _outputs, _size); diff --git a/source/synthLib/device.h b/source/synthLib/device.h @@ -8,6 +8,7 @@ #include "midiTypes.h" #include "buildconfig.h" +#include "midiTranslator.h" namespace synthLib { @@ -63,6 +64,8 @@ namespace synthLib virtual uint32_t getDspClockPercent() const = 0; virtual uint64_t getDspClockHz() const = 0; + auto& getMidiTranslator() { return m_midiTranslator; } + protected: virtual void readMidiOut(std::vector<SMidiEvent>& _midiOut) = 0; virtual void processAudio(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, size_t _samples) = 0; @@ -72,6 +75,10 @@ namespace synthLib private: std::vector<SMidiEvent> m_midiIn; + uint32_t m_extraLatency = 0; + + MidiTranslator m_midiTranslator; + std::vector<SMidiEvent> m_translatorOut; }; } diff --git a/source/synthLib/midiTranslator.cpp b/source/synthLib/midiTranslator.cpp @@ -0,0 +1,105 @@ +#include "midiTranslator.h" + +#include "midiTypes.h" + +namespace synthLib +{ + MidiTranslator::MidiTranslator() + { + reset(); + } + + void MidiTranslator::process(std::vector<SMidiEvent>& _results, const SMidiEvent& _source) + { + const size_t size = _source.sysex.size(); + + if (!size) + { + if (_source.a < 0xf0) + { + auto& targets = m_targetChannels[_source.a & 0x0f]; + + for (auto target : targets) + { + auto& result = _results.emplace_back(_source); + result.a = static_cast<uint8_t>((_source.a & 0xf0) | target); + } + } + else + { + _results.push_back(_source); + } + + return; + } + + if (size < 4 || _source.sysex.front() != 0xf0 || _source.sysex.back() != 0xf7 || _source.sysex[1] != ManufacturerId) + { + _results.push_back(_source); + return; + } + + switch (_source.sysex[2]) + { + case CmdSkipTranslation: + if (size == 7) + { + auto& result = _results.emplace_back(_source); + result.a = _source.sysex[3]; + result.b = _source.sysex[4]; + result.c = _source.sysex[5]; + result.sysex.clear(); + } + break; + case CmdAddTargetChannel: + if (size == 6) + { + const auto sourceChannel = _source.sysex[3]; + const auto targetChannel = _source.sysex[4]; + addTargetChannel(sourceChannel, targetChannel); + } + break; + case CmdResetTargetChannels: + reset(); + break; + case CmdClearTargetChannels: + clear(); + break; + default:; + } + } + + bool MidiTranslator::addTargetChannel(const uint8_t _sourceChannel, const uint8_t _targetChannel) + { + if (_sourceChannel >= 16 || _targetChannel >= 16) + return false; + m_targetChannels[_sourceChannel].insert(_targetChannel); + return true; + } + + void MidiTranslator::reset() + { + for (uint8_t i = 0; i < static_cast<uint8_t>(m_targetChannels.size()); ++i) + m_targetChannels[i].insert(i); + } + + void MidiTranslator::clear() + { + for (uint8_t i = 0; i < static_cast<uint8_t>(m_targetChannels.size()); ++i) + m_targetChannels[i].clear(); + } + + SMidiEvent& MidiTranslator::createPacketSkipTranslation(SMidiEvent& _ev) + { + if (_ev.sysex.empty()) + _ev.sysex = { 0xf0, ManufacturerId, CmdSkipTranslation, _ev.a, _ev.b, _ev.c, 0xf7 }; + return _ev; + } + + SMidiEvent MidiTranslator::createPacketSetTargetChannel(const uint8_t _sourceChannel, const uint8_t _targetChannel) + { + SMidiEvent e; + e.sysex = { 0xf0, ManufacturerId, CmdAddTargetChannel, _sourceChannel, _targetChannel, 0xf7 }; + return e; + } +} diff --git a/source/synthLib/midiTranslator.h b/source/synthLib/midiTranslator.h @@ -0,0 +1,51 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <set> +#include <vector> + +namespace synthLib +{ + struct SMidiEvent; + + // This class receives midi events and can transform the incoming midi channels to one or more target channels. + // Furthermore, it can be remote-controlled via sysex messages + class MidiTranslator + { + public: + // https://midi.org/sysexidtable we use the first "Reserved for Other Uses" ID but as these + // messages never leave the plugin but are completely internal only it doesn't really matter anyway + static constexpr uint8_t ManufacturerId = 0x60; + + enum Command : uint8_t + { + CmdSkipTranslation = 0x01, // f0, ManufacturerId, CmdSkipTranslation, statusbyte, byte2, byte3, f7 + CmdAddTargetChannel, // f0, ManufacturerId, CmdAddTargetChannel, sourceChannel, targetChannel, f7 + CmdClearTargetChannels, // f0, ManufacturerId, CmdClearTargetChannels, f7 + CmdResetTargetChannels, // f0, ManufacturerId, CmdResetTargetChannels, f7 + }; + + MidiTranslator(); + MidiTranslator(const MidiTranslator&) = default; + MidiTranslator(MidiTranslator&&) = default; + + virtual ~MidiTranslator() = default; + + MidiTranslator& operator = (const MidiTranslator&) = default; + MidiTranslator& operator = (MidiTranslator&&) = default; + + virtual void process(std::vector<SMidiEvent>& _results, const SMidiEvent& _source); + + bool addTargetChannel(uint8_t _sourceChannel, uint8_t _targetChannel); + + void reset(); + void clear(); + + static SMidiEvent& createPacketSkipTranslation(SMidiEvent& _ev); + static SMidiEvent createPacketSetTargetChannel(uint8_t _sourceChannel, uint8_t _targetChannel); + + private: + std::array<std::set<uint8_t>, 16> m_targetChannels; + }; +}