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:
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;
+ };
+}