From cf32e7e72d81269e97f18ba51c42ba10715d1852 Mon Sep 17 00:00:00 2001 From: Pieter Pas Date: Sat, 26 Jun 2021 05:00:51 +0200 Subject: [PATCH] SysCommon input support Make Channel and Cable printable --- .../MIDI-Input-Callback.ino | 13 +- ...-Control-Universal-Reverse-Engineering.ino | 14 ++ src/Control_Surface/Control_Surface_Class.cpp | 15 ++ src/Control_Surface/Control_Surface_Class.hpp | 13 +- src/Def/Cable.cpp | 10 + src/Def/Cable.hpp | 4 +- src/Def/Channel.cpp | 11 +- src/Def/Channel.hpp | 4 +- .../BluetoothMIDI_Interface.cpp | 4 + .../BluetoothMIDI_Interface.hpp | 3 +- src/MIDI_Interfaces/MIDI_Callbacks.hpp | 220 ++++++++++++++++++ src/MIDI_Interfaces/MIDI_Interface.cpp | 6 + src/MIDI_Interfaces/MIDI_Interface.hpp | 18 +- src/MIDI_Interfaces/MIDI_Pipes.cpp | 6 + src/MIDI_Interfaces/MIDI_Pipes.hpp | 10 + .../Wrappers/FortySevenEffects.hpp | 35 ++- src/MIDI_Parsers/MIDI_MessageTypes.cpp | 36 +++ src/MIDI_Parsers/MIDI_MessageTypes.hpp | 37 ++- test/MIDI_Interfaces/test-MIDI_Pipes.cpp | 3 + 19 files changed, 439 insertions(+), 23 deletions(-) create mode 100644 src/Def/Cable.cpp create mode 100644 src/MIDI_Parsers/MIDI_MessageTypes.cpp diff --git a/examples/Other/MIDI-Input-Callback/MIDI-Input-Callback.ino b/examples/Other/MIDI-Input-Callback/MIDI-Input-Callback.ino index f3b10dc844..1c68846042 100644 --- a/examples/Other/MIDI-Input-Callback/MIDI-Input-Callback.ino +++ b/examples/Other/MIDI-Input-Callback/MIDI-Input-Callback.ino @@ -48,6 +48,16 @@ bool sysExMessageCallback(SysExMessage se) { // return false. } +bool sysCommonMessageCallback(SysCommonMessage sc) { + Serial << F("System Common message: ") << hex // + << sc.header << ' ' << sc.data1 << ' ' << sc.data2 // + << dec << F(" on cable ") << sc.cable.getOneBased() << endl; + return true; // Return true to indicate that handling is done, + // and Control_Surface shouldn't handle it anymore. + // If you want Control_Surface to handle it as well, + // return false. +} + bool realTimeMessageCallback(RealTimeMessage rt) { Serial << F("Real-time message: ") // << hex << rt.message << dec // @@ -63,8 +73,9 @@ void setup() { Control_Surface.begin(); Control_Surface.setMIDIInputCallbacks(channelMessageCallback, // sysExMessageCallback, // + sysCommonMessageCallback, // realTimeMessageCallback); // - // If you don't need all three callbacks, you can pass `nullptr` instead of a + // If you don't need all four callbacks, you can pass `nullptr` instead of a // function pointer } diff --git a/examples/Other/Mackie-Control-Universal-Reverse-Engineering/Mackie-Control-Universal-Reverse-Engineering.ino b/examples/Other/Mackie-Control-Universal-Reverse-Engineering/Mackie-Control-Universal-Reverse-Engineering.ino index 38eb9b6206..120edbd47c 100644 --- a/examples/Other/Mackie-Control-Universal-Reverse-Engineering/Mackie-Control-Universal-Reverse-Engineering.ino +++ b/examples/Other/Mackie-Control-Universal-Reverse-Engineering/Mackie-Control-Universal-Reverse-Engineering.ino @@ -38,6 +38,19 @@ bool sysExMessageCallback(SysExMessage se) { // return false. } +bool sysCommonMessageCallback(SysCommonMessage sc) { + Serial << F("System Common message: ") << hex << sc.getMessageType(); + if (sc.getNumberOfDataBytes() >= 1) + Serial << sc.getData1(); + if (sc.getNumberOfDataBytes() >= 2) + Serial << sc.getData2(); + Serial << dec << F(" on cable ") << sc.cable.getOneBased() << endl; + return false; // Return true to indicate that handling is done, + // and Control_Surface shouldn't handle it anymore. + // If you want Control_Surface to handle it as well, + // return false. +} + bool realTimeMessageCallback(RealTimeMessage rt) { Serial << F("Real-Time: ") << hex << rt.message << dec << F(" on cable ") << rt.cable.getOneBased() << endl; @@ -54,6 +67,7 @@ void setup() { Control_Surface.begin(); Control_Surface.setMIDIInputCallbacks(channelMessageCallback, // sysExMessageCallback, // + sysCommonMessageCallback, // realTimeMessageCallback); // } diff --git a/src/Control_Surface/Control_Surface_Class.cpp b/src/Control_Surface/Control_Surface_Class.cpp index 88d5a6d7b6..0bb638d0a1 100644 --- a/src/Control_Surface/Control_Surface_Class.cpp +++ b/src/Control_Surface/Control_Surface_Class.cpp @@ -85,6 +85,9 @@ void Control_Surface_::sendChannelMessageImpl(ChannelMessage msg) { void Control_Surface_::sendSysExImpl(SysExMessage msg) { this->sourceMIDItoPipe(msg); } +void Control_Surface_::sendSysCommonImpl(SysCommonMessage msg) { + this->sourceMIDItoPipe(msg); +} void Control_Surface_::sendRealTimeImpl(RealTimeMessage msg) { this->sourceMIDItoPipe(msg); } @@ -191,6 +194,18 @@ void Control_Surface_::sinkMIDIfromPipe(SysExMessage msg) { MIDIInputElementSysEx::updateAllWith(msg); } +void Control_Surface_::sinkMIDIfromPipe(SysCommonMessage msg) { +#ifdef DEBUG_MIDI_PACKETS + DEBUG_OUT << ">>> " << hex << msg.getMessageType() << ' ' << msg.getData1() + << ' ' << msg.getData2() << " (" << msg.cable << ')' << dec + << endl; +#endif + // If the SysEx Message callback exists, call it to see if we have to + // continue handling it. + if (sysCommonMessageCallback && sysCommonMessageCallback(msg)) + return; +} + void Control_Surface_::sinkMIDIfromPipe(RealTimeMessage rtMessage) { #ifdef DEBUG_MIDI_PACKETS DEBUG(">>> " << hex << rtMessage.message << " (" diff --git a/src/Control_Surface/Control_Surface_Class.hpp b/src/Control_Surface/Control_Surface_Class.hpp index 3f81177010..e242d34b11 100644 --- a/src/Control_Surface/Control_Surface_Class.hpp +++ b/src/Control_Surface/Control_Surface_Class.hpp @@ -37,7 +37,7 @@ class Control_Surface_ : public MIDI_Sender, /// Copying is not allowed Control_Surface_ &operator=(Control_Surface_ const &) = delete; - /// Return the static Control_Surface_ instance (Control_Surface_ is a + /// Return the static Control_Surface_ instance (Control_Surface_ is a /// singleton.) static Control_Surface_ &getInstance(); @@ -74,17 +74,19 @@ class Control_Surface_ : public MIDI_Sender, /// Low-level function for sending a MIDI channel voice message. void sendChannelMessageImpl(ChannelMessage); /// Low-level function for sending a MIDI system common message. - void sendSysCommonImpl(SysCommonMessage) { /* TODO */ } + void sendSysCommonImpl(SysCommonMessage); /// Low-level function for sending a system exclusive MIDI message. void sendSysExImpl(SysExMessage); /// Low-level function for sending a MIDI real-time message. void sendRealTimeImpl(RealTimeMessage); /// Low-level function for sending any buffered outgoing MIDI messages. + /// @todo Implement this in MIDI_Pipe void sendNowImpl() { /* TODO */ } private: void sinkMIDIfromPipe(ChannelMessage msg) override; void sinkMIDIfromPipe(SysExMessage msg) override; + void sinkMIDIfromPipe(SysCommonMessage msg) override; void sinkMIDIfromPipe(RealTimeMessage msg) override; private: @@ -105,6 +107,10 @@ class Control_Surface_ : public MIDI_Sender, /// done in the user-provided callback, false if `Control_Surface` /// should handle the message. using SysExMessageCallback = bool (*)(SysExMessage); + /// Callback function type for System Common messages. Return true if + /// handling is done in the user-provided callback, false if + /// `Control_Surface` should handle the message. + using SysCommonMessageCallback = bool (*)(SysCommonMessage); /// Callback function type for Real-Time messages. Return true if handling /// is done in the user-provided callback, false if `Control_Surface` /// should handle the message. @@ -114,9 +120,11 @@ class Control_Surface_ : public MIDI_Sender, void setMIDIInputCallbacks(ChannelMessageCallback channelMessageCallback, SysExMessageCallback sysExMessageCallback, + SysCommonMessageCallback sysCommonMessageCallback, RealTimeMessageCallback realTimeMessageCallback) { this->channelMessageCallback = channelMessageCallback; this->sysExMessageCallback = sysExMessageCallback; + this->sysCommonMessageCallback = sysCommonMessageCallback; this->realTimeMessageCallback = realTimeMessageCallback; } @@ -125,6 +133,7 @@ class Control_Surface_ : public MIDI_Sender, private: ChannelMessageCallback channelMessageCallback = nullptr; SysExMessageCallback sysExMessageCallback = nullptr; + SysCommonMessageCallback sysCommonMessageCallback = nullptr; RealTimeMessageCallback realTimeMessageCallback = nullptr; MIDI_Pipe inpipe, outpipe; }; diff --git a/src/Def/Cable.cpp b/src/Def/Cable.cpp new file mode 100644 index 0000000000..5a346b16d0 --- /dev/null +++ b/src/Def/Cable.cpp @@ -0,0 +1,10 @@ +#include "Cable.hpp" +#include + +BEGIN_CS_NAMESPACE + +Print &operator<<(Print &os, Cable c) { + return os << F("Cable ") << c.getOneBased(); +} + +END_CS_NAMESPACE \ No newline at end of file diff --git a/src/Def/Cable.hpp b/src/Def/Cable.hpp index dbcd796bb5..d8a89bad64 100644 --- a/src/Def/Cable.hpp +++ b/src/Def/Cable.hpp @@ -2,8 +2,8 @@ #pragma once +#include // Print #include -#include // uint8_t BEGIN_CS_NAMESPACE @@ -132,4 +132,6 @@ constexpr Cable CABLE_14 = Cable::createCable(14); constexpr Cable CABLE_15 = Cable::createCable(15); constexpr Cable CABLE_16 = Cable::createCable(16); +Print &operator<<(Print &, Cable); + END_CS_NAMESPACE \ No newline at end of file diff --git a/src/Def/Channel.cpp b/src/Def/Channel.cpp index e9ec06fb7f..09383909c3 100644 --- a/src/Def/Channel.cpp +++ b/src/Def/Channel.cpp @@ -1,3 +1,10 @@ -#ifdef TEST_COMPILE_ALL_HEADERS_SEPARATELY #include "Channel.hpp" -#endif \ No newline at end of file +#include + +BEGIN_CS_NAMESPACE + +Print &operator<<(Print &os, Channel c) { + return os << F("Channel ") << c.getOneBased(); +} + +END_CS_NAMESPACE \ No newline at end of file diff --git a/src/Def/Channel.hpp b/src/Def/Channel.hpp index d63d30277d..39d1317ac3 100644 --- a/src/Def/Channel.hpp +++ b/src/Def/Channel.hpp @@ -2,8 +2,8 @@ #pragma once +#include // Print #include -#include // uint8_t BEGIN_CS_NAMESPACE @@ -132,4 +132,6 @@ constexpr Channel CHANNEL_14 = Channel::createChannel(14); constexpr Channel CHANNEL_15 = Channel::createChannel(15); constexpr Channel CHANNEL_16 = Channel::createChannel(16); +Print &operator<<(Print &, Channel); + END_CS_NAMESPACE \ No newline at end of file diff --git a/src/MIDI_Interfaces/BluetoothMIDI_Interface.cpp b/src/MIDI_Interfaces/BluetoothMIDI_Interface.cpp index ae9318e31a..ab814c0417 100644 --- a/src/MIDI_Interfaces/BluetoothMIDI_Interface.cpp +++ b/src/MIDI_Interfaces/BluetoothMIDI_Interface.cpp @@ -169,6 +169,10 @@ void BluetoothMIDI_Interface::sendSysExImpl(SysExMessage msg) { cv.notify_one(); } +void BluetoothMIDI_Interface::sendSysCommonImpl(SysCommonMessage) { + // TODO +} + // -------------------------------------------------------------------------- // void BluetoothMIDI_Interface::parse(const uint8_t *const data, diff --git a/src/MIDI_Interfaces/BluetoothMIDI_Interface.hpp b/src/MIDI_Interfaces/BluetoothMIDI_Interface.hpp index 4cfa1862bd..b99031187b 100644 --- a/src/MIDI_Interfaces/BluetoothMIDI_Interface.hpp +++ b/src/MIDI_Interfaces/BluetoothMIDI_Interface.hpp @@ -79,8 +79,7 @@ class BluetoothMIDI_Interface : public MIDI_Interface { protected: // MIDI send implementations void sendChannelMessageImpl(ChannelMessage) override; - void sendSysCommonImpl(SysCommonMessage) override { /* TODO */ - } + void sendSysCommonImpl(SysCommonMessage) override; void sendSysExImpl(SysExMessage) override; void sendRealTimeImpl(RealTimeMessage) override; void sendNowImpl() override { flush(); } diff --git a/src/MIDI_Interfaces/MIDI_Callbacks.hpp b/src/MIDI_Interfaces/MIDI_Callbacks.hpp index 38057221c0..b1dddc2ab0 100644 --- a/src/MIDI_Interfaces/MIDI_Callbacks.hpp +++ b/src/MIDI_Interfaces/MIDI_Callbacks.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include BEGIN_CS_NAMESPACE @@ -18,6 +19,8 @@ class MIDI_Callbacks { virtual void onChannelMessage(MIDI_Interface &, ChannelMessage) {} /// Callback for incoming MIDI System Exclusive Messages. virtual void onSysExMessage(MIDI_Interface &, SysExMessage) {} + /// Callback for incoming MIDI System Common Messages. + virtual void onSysCommonMessage(MIDI_Interface &, SysCommonMessage) {} /// Callback for incoming MIDI Real-Time Messages. virtual void onRealTimeMessage(MIDI_Interface &, RealTimeMessage) {} @@ -27,4 +30,221 @@ class MIDI_Callbacks { // LCOV_EXCL_STOP +template +class FineGrainedMIDI_Callbacks : public MIDI_Callbacks { + protected: + /// @anchor FineGrainedMIDI_Callbacks_MIDI_Callback_Functions + /// @name MIDI Callback Functions + /// @{ + // clang-format off + void onNoteOff(Channel channel, uint8_t note, uint8_t velocity, Cable cable); + void onNoteOn(Channel channel, uint8_t note, uint8_t velocity, Cable cable); + void onKeyPressure(Channel channel, uint8_t note, uint8_t pressure, Cable cable); + void onControlChange(Channel channel, uint8_t controller, uint8_t value, Cable cable); + void onProgramChange(Channel channel, uint8_t program, Cable cable); + void onChannelPressure(Channel channel, uint8_t pressure, Cable cable); + void onPitchBend(Channel channel, uint16_t bend, Cable cable); + void onSystemExclusive(SysExMessage message); + void onTimeCodeQuarterFrame(uint8_t data, Cable cable); + void onSongPosition(uint16_t beats, Cable cable); + void onSongSelect(uint8_t songnumber, Cable cable); + void onTuneRequest(Cable cable); + void onClock(Cable cable); + void onStart(Cable cable); + void onContinue(Cable cable); + void onStop(Cable cable); + void onActiveSensing(Cable cable); + void onReset(Cable cable); + // clang-format on + /// @} + + void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override { + using MMT = MIDIMessageType; + switch (msg.getMessageType()) { + case MMT::NOTE_OFF: + CRTP(Derived).onNoteOff(msg.getChannel(), msg.getData1(), + msg.getData2(), msg.getCable()); + break; + case MMT::NOTE_ON: + CRTP(Derived).onNoteOn(msg.getChannel(), msg.getData1(), + msg.getData2(), msg.getCable()); + break; + case MMT::KEY_PRESSURE: + CRTP(Derived).onKeyPressure(msg.getChannel(), msg.getData1(), + msg.getData2(), msg.getCable()); + break; + case MMT::CONTROL_CHANGE: + CRTP(Derived).onControlChange(msg.getChannel(), msg.getData1(), + msg.getData2(), msg.getCable()); + break; + case MMT::PROGRAM_CHANGE: + CRTP(Derived).onProgramChange(msg.getChannel(), msg.getData1(), + msg.getCable()); + break; + case MMT::CHANNEL_PRESSURE: + CRTP(Derived).onChannelPressure(msg.getChannel(), + msg.getData1(), msg.getCable()); + break; + case MMT::PITCH_BEND: + CRTP(Derived).onPitchBend(msg.getChannel(), msg.getData14bit(), + msg.getCable()); + break; + case MMT::SYSEX_START: + case MMT::MTC_QUARTER_FRAME: + case MMT::SONG_POSITION_POINTER: + case MMT::SONG_SELECT: + case MMT::UNDEFINED_SYSCOMMON_1: + case MMT::UNDEFINED_SYSCOMMON_2: + case MMT::TUNE_REQUEST: + case MMT::SYSEX_END: + case MMT::TIMING_CLOCK: + case MMT::UNDEFINED_REALTIME_1: + case MMT::START: + case MMT::CONTINUE: + case MMT::STOP: + case MMT::UNDEFINED_REALTIME_2: + case MMT::ACTIVE_SENSING: + case MMT::RESET: + default: break; + } + } + + void onSysExMessage(MIDI_Interface &, SysExMessage msg) override { + CRTP(Derived).onSystemExclusive(msg); + } + + void onSysCommonMessage(MIDI_Interface &, SysCommonMessage msg) override { + using MMT = MIDIMessageType; + switch (msg.getMessageType()) { + case MMT::NOTE_OFF: + case MMT::NOTE_ON: + case MMT::KEY_PRESSURE: + case MMT::CONTROL_CHANGE: + case MMT::PROGRAM_CHANGE: + case MMT::CHANNEL_PRESSURE: + case MMT::PITCH_BEND: + case MMT::SYSEX_START: break; + case MMT::MTC_QUARTER_FRAME: + CRTP(Derived).onTimeCodeQuarterFrame(msg.getData1(), + msg.getCable()); + break; + case MMT::SONG_POSITION_POINTER: + CRTP(Derived).onSongPosition(msg.getData14bit(), + msg.getCable()); + break; + case MMT::SONG_SELECT: + CRTP(Derived).onSongSelect(msg.getData1(), msg.getCable()); + break; + case MMT::UNDEFINED_SYSCOMMON_1: break; + case MMT::UNDEFINED_SYSCOMMON_2: break; + case MMT::TUNE_REQUEST: + CRTP(Derived).onTuneRequest(msg.getCable()); + break; + case MMT::SYSEX_END: + case MMT::TIMING_CLOCK: + case MMT::UNDEFINED_REALTIME_1: + case MMT::START: + case MMT::CONTINUE: + case MMT::STOP: + case MMT::UNDEFINED_REALTIME_2: + case MMT::ACTIVE_SENSING: + case MMT::RESET: + default: break; + } + } + + void onRealTimeMessage(MIDI_Interface &, RealTimeMessage msg) override { + using MMT = MIDIMessageType; + switch (msg.getMessageType()) { + case MMT::NOTE_OFF: + case MMT::NOTE_ON: + case MMT::KEY_PRESSURE: + case MMT::CONTROL_CHANGE: + case MMT::PROGRAM_CHANGE: + case MMT::CHANNEL_PRESSURE: + case MMT::PITCH_BEND: + case MMT::SYSEX_START: + case MMT::MTC_QUARTER_FRAME: + case MMT::SONG_POSITION_POINTER: + case MMT::SONG_SELECT: + case MMT::UNDEFINED_SYSCOMMON_1: + case MMT::UNDEFINED_SYSCOMMON_2: + case MMT::TUNE_REQUEST: + case MMT::SYSEX_END: break; + case MMT::TIMING_CLOCK: + CRTP(Derived).onClock(msg.getCable()); + break; + case MMT::UNDEFINED_REALTIME_1: break; + case MMT::START: CRTP(Derived).onStart(msg.getCable()); break; + case MMT::CONTINUE: CRTP(Derived).onContinue(msg.getCable()); break; + case MMT::STOP: CRTP(Derived).onStop(msg.getCable()); break; + case MMT::UNDEFINED_REALTIME_2: break; + case MMT::ACTIVE_SENSING: + CRTP(Derived).onActiveSensing(msg.getCable()); + break; + case MMT::RESET: CRTP(Derived).onReset(msg.getCable()); break; + default: break; + } + } + + /// @cond + + template + struct Dummy {}; + + template + static constexpr bool same_return_type_and_arguments(R1 (T1::*)(Args1...), + R2 (T2::*)(Args2...)) { + return std::is_same, Dummy>::value; + } + + public: + FineGrainedMIDI_Callbacks() { + // clang-format off + static_assert(same_return_type_and_arguments(&Derived::onNoteOff, &FineGrainedMIDI_Callbacks::onNoteOff), "Incorrect signature for onNoteOff"); + static_assert(same_return_type_and_arguments(&Derived::onNoteOn, &FineGrainedMIDI_Callbacks::onNoteOn), "Incorrect signature for onNoteOn"); + static_assert(same_return_type_and_arguments(&Derived::onKeyPressure, &FineGrainedMIDI_Callbacks::onKeyPressure), "Incorrect signature for onKeyPressure"); + static_assert(same_return_type_and_arguments(&Derived::onControlChange, &FineGrainedMIDI_Callbacks::onControlChange), "Incorrect signature for onControlChange"); + static_assert(same_return_type_and_arguments(&Derived::onProgramChange, &FineGrainedMIDI_Callbacks::onProgramChange), "Incorrect signature for onProgramChange"); + static_assert(same_return_type_and_arguments(&Derived::onChannelPressure, &FineGrainedMIDI_Callbacks::onChannelPressure), "Incorrect signature for onChannelPressure"); + static_assert(same_return_type_and_arguments(&Derived::onPitchBend, &FineGrainedMIDI_Callbacks::onPitchBend), "Incorrect signature for onPitchBend"); + static_assert(same_return_type_and_arguments(&Derived::onSystemExclusive, &FineGrainedMIDI_Callbacks::onSystemExclusive), "Incorrect signature for onSystemExclusive"); + static_assert(same_return_type_and_arguments(&Derived::onTimeCodeQuarterFrame, &FineGrainedMIDI_Callbacks::onTimeCodeQuarterFrame), "Incorrect signature for onTimeCodeQuarterFrame"); + static_assert(same_return_type_and_arguments(&Derived::onSongPosition, &FineGrainedMIDI_Callbacks::onSongPosition), "Incorrect signature for onSongPosition"); + static_assert(same_return_type_and_arguments(&Derived::onSongSelect, &FineGrainedMIDI_Callbacks::onSongSelect), "Incorrect signature for onSongSelect"); + static_assert(same_return_type_and_arguments(&Derived::onTuneRequest, &FineGrainedMIDI_Callbacks::onTuneRequest), "Incorrect signature for onTuneRequest"); + static_assert(same_return_type_and_arguments(&Derived::onClock, &FineGrainedMIDI_Callbacks::onClock), "Incorrect signature for onClock"); + static_assert(same_return_type_and_arguments(&Derived::onStart, &FineGrainedMIDI_Callbacks::onStart), "Incorrect signature for onStart"); + static_assert(same_return_type_and_arguments(&Derived::onContinue, &FineGrainedMIDI_Callbacks::onContinue), "Incorrect signature for onContinue"); + static_assert(same_return_type_and_arguments(&Derived::onStop, &FineGrainedMIDI_Callbacks::onStop), "Incorrect signature for onStop"); + static_assert(same_return_type_and_arguments(&Derived::onActiveSensing, &FineGrainedMIDI_Callbacks::onActiveSensing), "Incorrect signature for onActiveSensing"); + static_assert(same_return_type_and_arguments(&Derived::onReset, &FineGrainedMIDI_Callbacks::onReset), "Incorrect signature for onReset"); + // clang-format on + } + + /// @endcond +}; + +// clang-format off +template inline void FineGrainedMIDI_Callbacks::onNoteOff(Channel, uint8_t, uint8_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onNoteOn(Channel, uint8_t, uint8_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onKeyPressure(Channel, uint8_t, uint8_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onControlChange(Channel, uint8_t, uint8_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onProgramChange(Channel, uint8_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onChannelPressure(Channel, uint8_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onPitchBend(Channel, uint16_t, Cable) {} +template inline void FineGrainedMIDI_Callbacks::onSystemExclusive(SysExMessage) {} +template inline void FineGrainedMIDI_Callbacks::onTimeCodeQuarterFrame(uint8_t,Cable) {} +template inline void FineGrainedMIDI_Callbacks::onSongPosition(uint16_t,Cable) {} +template inline void FineGrainedMIDI_Callbacks::onSongSelect(uint8_t,Cable) {} +template inline void FineGrainedMIDI_Callbacks::onTuneRequest(Cable) {} +template inline void FineGrainedMIDI_Callbacks::onClock(Cable) {} +template inline void FineGrainedMIDI_Callbacks::onStart(Cable) {} +template inline void FineGrainedMIDI_Callbacks::onContinue(Cable) {} +template inline void FineGrainedMIDI_Callbacks::onStop(Cable) {} +template inline void FineGrainedMIDI_Callbacks::onActiveSensing(Cable) {} +template inline void FineGrainedMIDI_Callbacks::onReset(Cable) {} +// clang-format on + END_CS_NAMESPACE diff --git a/src/MIDI_Interfaces/MIDI_Interface.cpp b/src/MIDI_Interfaces/MIDI_Interface.cpp index ecc85f9a1e..e615a6cd28 100644 --- a/src/MIDI_Interfaces/MIDI_Interface.cpp +++ b/src/MIDI_Interfaces/MIDI_Interface.cpp @@ -44,6 +44,12 @@ void MIDI_Interface::onSysExMessage(SysExMessage message) { callbacks->onSysExMessage(*this, message); } +void MIDI_Interface::onSysCommonMessage(SysCommonMessage message) { + sourceMIDItoPipe(message); + if (callbacks) + callbacks->onSysCommonMessage(*this, message); +} + void MIDI_Interface::onRealTimeMessage(RealTimeMessage message) { sourceMIDItoPipe(message); if (callbacks) diff --git a/src/MIDI_Interfaces/MIDI_Interface.hpp b/src/MIDI_Interfaces/MIDI_Interface.hpp index 7c6b80b382..eba3042f46 100644 --- a/src/MIDI_Interfaces/MIDI_Interface.hpp +++ b/src/MIDI_Interfaces/MIDI_Interface.hpp @@ -81,6 +81,8 @@ class MIDI_Interface : public TrueMIDI_SinkSource, void sinkMIDIfromPipe(ChannelMessage msg) override { send(msg); } /// Accept an incoming MIDI System Exclusive message from the source pipe. void sinkMIDIfromPipe(SysExMessage msg) override { send(msg); } + /// Accept an incoming MIDI System Common message from the source pipe. + void sinkMIDIfromPipe(SysCommonMessage msg) override { send(msg); } /// Accept an incoming MIDI Real-Time message from the source pipe. void sinkMIDIfromPipe(RealTimeMessage msg) override { send(msg); } @@ -89,6 +91,9 @@ class MIDI_Interface : public TrueMIDI_SinkSource, void onChannelMessage(ChannelMessage message); /// Call the SysEx message callback and send the message to the sink pipe. void onSysExMessage(SysExMessage message); + /// Call the System Common message callback and send the message to the sink + /// pipe. + void onSysCommonMessage(SysCommonMessage message); /// Call the real-time message callback and send the message to the sink /// pipe. void onRealTimeMessage(RealTimeMessage message); @@ -100,8 +105,9 @@ class MIDI_Interface : public TrueMIDI_SinkSource, /// Dispatch the given type of MIDI message from the given interface. template static void dispatchIncoming(MIDIInterface_t *iface, MIDIReadEvent event); - /// Un-stall the given MIDI interface. Assumes the interface has been - /// stalled because of a chunked SysEx messages. Waits + /// Un-stall the given MIDI interface. Assumes the interface has been + /// stalled because of a chunked SysEx messages. Waits untill that message + /// is finished. template static void handleStall(MIDIInterface_t *iface); @@ -144,12 +150,14 @@ void MIDI_Interface::dispatchIncoming(MIDIInterface_t *iface, case MIDIReadEvent::SYSEX_MESSAGE: iface->onSysExMessage(iface->getSysExMessage()); break; + case MIDIReadEvent::SYSCOMMON_MESSAGE: + iface->onSysCommonMessage(iface->getSysCommonMessage()); + break; case MIDIReadEvent::REALTIME_MESSAGE: iface->onRealTimeMessage(iface->getRealTimeMessage()); break; - case MIDIReadEvent::SYSCOMMON_MESSAGE: break; // TODO - case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE - default: break; // LCOV_EXCL_LINE + case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE + default: break; // LCOV_EXCL_LINE } } diff --git a/src/MIDI_Interfaces/MIDI_Pipes.cpp b/src/MIDI_Interfaces/MIDI_Pipes.cpp index 73ce620964..90769b5218 100644 --- a/src/MIDI_Interfaces/MIDI_Pipes.cpp +++ b/src/MIDI_Interfaces/MIDI_Pipes.cpp @@ -141,6 +141,12 @@ void MIDI_Source::sourceMIDItoPipe(SysExMessage msg) { sinkPipe->acceptMIDIfromSource(msg); } } +void MIDI_Source::sourceMIDItoPipe(SysCommonMessage msg) { + if (sinkPipe != nullptr) { + handleStallers(); + sinkPipe->acceptMIDIfromSource(msg); + } +} void MIDI_Source::sourceMIDItoPipe(RealTimeMessage msg) { if (sinkPipe != nullptr) { // Always send write to pipe, don't check if it's stalled or not diff --git a/src/MIDI_Interfaces/MIDI_Pipes.hpp b/src/MIDI_Interfaces/MIDI_Pipes.hpp index 09ab724d25..85901b16c4 100644 --- a/src/MIDI_Interfaces/MIDI_Pipes.hpp +++ b/src/MIDI_Interfaces/MIDI_Pipes.hpp @@ -92,6 +92,8 @@ class MIDI_Sink { virtual void sinkMIDIfromPipe(ChannelMessage) = 0; /// Accept an incoming MIDI System Exclusive message. virtual void sinkMIDIfromPipe(SysExMessage) = 0; + /// Accept an incoming MIDI System Common message. + virtual void sinkMIDIfromPipe(SysCommonMessage) = 0; /// Accept an incoming MIDI Real-Time message. virtual void sinkMIDIfromPipe(RealTimeMessage) = 0; @@ -173,6 +175,8 @@ class MIDI_Source { void sourceMIDItoPipe(ChannelMessage); /// Send a MIDI System Exclusive message down the pipe. void sourceMIDItoPipe(SysExMessage); + /// Send a MIDI System Common message down the pipe. + void sourceMIDItoPipe(SysCommonMessage); /// Send a MIDI Real-Time message down the pipe. void sourceMIDItoPipe(RealTimeMessage); @@ -351,6 +355,8 @@ class MIDI_Pipe : private MIDI_Sink, private MIDI_Source { /// @copydoc mapForwardMIDI virtual void mapForwardMIDI(SysExMessage msg) { sourceMIDItoSink(msg); } /// @copydoc mapForwardMIDI + virtual void mapForwardMIDI(SysCommonMessage msg) { sourceMIDItoSink(msg); } + /// @copydoc mapForwardMIDI virtual void mapForwardMIDI(RealTimeMessage msg) { sourceMIDItoSink(msg); } /// @} @@ -530,6 +536,10 @@ class MIDI_Pipe : private MIDI_Sink, private MIDI_Source { sourceMIDItoSink(msg); } /// @copydoc sinkMIDIfromPipe + void sinkMIDIfromPipe(SysCommonMessage msg) override { + sourceMIDItoSink(msg); + } + /// @copydoc sinkMIDIfromPipe void sinkMIDIfromPipe(RealTimeMessage msg) override { sourceMIDItoSink(msg); } diff --git a/src/MIDI_Interfaces/Wrappers/FortySevenEffects.hpp b/src/MIDI_Interfaces/Wrappers/FortySevenEffects.hpp index 1fa7b7cf5d..21d5ce1f7a 100644 --- a/src/MIDI_Interfaces/Wrappers/FortySevenEffects.hpp +++ b/src/MIDI_Interfaces/Wrappers/FortySevenEffects.hpp @@ -39,6 +39,15 @@ class FortySevenEffectsMIDI_Parser : public MIDI_Parser { this->sysex.cable = CABLE_1; } + /// Get the latest system common message from the given MIDI interface. + template + void updateSysCommonMessage(const MidiInterface &interface) { + this->midimsg.header = interface.getType(); + this->midimsg.data1 = interface.getData1(); + this->midimsg.data2 = interface.getData2(); + this->midimsg.cable = CABLE_1; + } + /// Get the latest real-time message from the given MIDI interface. template void updateRealTimeMessage(const MidiInterface &interface) { @@ -86,15 +95,18 @@ class FortySevenEffectsMIDI_Interface : public MIDI_Interface { if (!midi.read()) // Update the MIDI input and check if there's return MIDIReadEvent::NO_MESSAGE; // a new message available auto type = midi.getType(); - if (midi.isChannelMessage(type)) { // Channel + if (type <= uint8_t(MIDIMessageType::PITCH_BEND)) { // Channel parser.updateChannelMessage(midi); return MIDIReadEvent::CHANNEL_MESSAGE; - } - if (type == uint8_t(MIDIMessageType::SYSEX_START)) { // SysEx + } else if (type == uint8_t(MIDIMessageType::SYSEX_START)) { // SysEx parser.updateSysExMessage(midi); return MIDIReadEvent::SYSEX_MESSAGE; - } - if (type >= uint8_t(MIDIMessageType::TIMING_CLOCK)) { // Real-Time + } else if (type <= uint8_t(MIDIMessageType::TUNE_REQUEST)) { // SysComm + parser.updateSysCommonMessage(midi); + return MIDIReadEvent::SYSCOMMON_MESSAGE; + } else if (type == uint8_t(MIDIMessageType::SYSEX_END)) { // SysEx + // ignore + } else { // Real-Time parser.updateRealTimeMessage(midi); return MIDIReadEvent::REALTIME_MESSAGE; } @@ -109,6 +121,10 @@ class FortySevenEffectsMIDI_Interface : public MIDI_Interface { RealTimeMessage getRealTimeMessage() const { return parser.getRealTimeMessage(); } + /// Return the received system common message. + SysCommonMessage getSysCommonMessage() const { + return parser.getSysCommonMessage(); + } /// Return the received system exclusive message. SysExMessage getSysExMessage() const { return parser.getSysExMessage(); } @@ -135,6 +151,9 @@ class FortySevenEffectsMIDI_Interface : public MIDI_Interface { case MIDIReadEvent::REALTIME_MESSAGE: onRealTimeMessage(getRealTimeMessage()); break; + case MIDIReadEvent::SYSCOMMON_MESSAGE: + onSysCommonMessage(getSysCommonMessage()); + break; default: break; } } @@ -146,7 +165,8 @@ class FortySevenEffectsMIDI_Interface : public MIDI_Interface { msg.getChannel().getOneBased()); // channel is zero-based in Control Surface, one-based in MIDI 47 Fx } - void sendSysCommonImpl(SysCommonMessage) override { /* TODO */ } + void sendSysCommonImpl(SysCommonMessage) override { /* TODO */ + } void sendSysExImpl(SysExMessage msg) { midi.sendSysEx(msg.length, msg.data, true); // true indicates that the array contains the SysEx start and stop bytes @@ -154,7 +174,8 @@ class FortySevenEffectsMIDI_Interface : public MIDI_Interface { void sendRealTimeImpl(RealTimeMessage msg) override { midi.sendRealTime(static_cast(msg.message)); } - void sendNowImpl() override { /* TODO */ } + void sendNowImpl() override { /* TODO */ + } private: void handleStall() override { diff --git a/src/MIDI_Parsers/MIDI_MessageTypes.cpp b/src/MIDI_Parsers/MIDI_MessageTypes.cpp new file mode 100644 index 0000000000..7dc235895d --- /dev/null +++ b/src/MIDI_Parsers/MIDI_MessageTypes.cpp @@ -0,0 +1,36 @@ +#include "MIDI_MessageTypes.hpp" +#include "Settings/NamespaceSettings.hpp" + +BEGIN_CS_NAMESPACE + +FlashString_t enum_to_string(MIDIMessageType m) { + using M = MIDIMessageType; + switch (m) { + case M::NOTE_OFF: return F("NOTE_OFF"); + case M::NOTE_ON: return F("NOTE_ON"); + case M::KEY_PRESSURE: return F("KEY_PRESSURE"); + case M::CONTROL_CHANGE: return F("CONTROL_CHANGE"); + case M::PROGRAM_CHANGE: return F("PROGRAM_CHANGE"); + case M::CHANNEL_PRESSURE: return F("CHANNEL_PRESSURE"); + case M::PITCH_BEND: return F("PITCH_BEND"); + case M::SYSEX_START: return F("SYSEX_START"); + case M::MTC_QUARTER_FRAME: return F("MTC_QUARTER_FRAME"); + case M::SONG_POSITION_POINTER: return F("SONG_POSITION_POINTER"); + case M::SONG_SELECT: return F("SONG_SELECT"); + case M::UNDEFINED_SYSCOMMON_1: return F("UNDEFINED_SYSCOMMON_1"); + case M::UNDEFINED_SYSCOMMON_2: return F("UNDEFINED_SYSCOMMON_2"); + case M::TUNE_REQUEST: return F("TUNE_REQUEST"); + case M::SYSEX_END: return F("SYSEX_END"); + case M::TIMING_CLOCK: return F("TIMING_CLOCK"); + case M::UNDEFINED_REALTIME_1: return F("UNDEFINED_REALTIME_1"); + case M::START: return F("START"); + case M::CONTINUE: return F("CONTINUE"); + case M::STOP: return F("STOP"); + case M::UNDEFINED_REALTIME_2: return F("UNDEFINED_REALTIME_2"); + case M::ACTIVE_SENSING: return F("ACTIVE_SENSING"); + case M::RESET: return F("RESET"); + default: return F(""); + } +} + +END_CS_NAMESPACE \ No newline at end of file diff --git a/src/MIDI_Parsers/MIDI_MessageTypes.hpp b/src/MIDI_Parsers/MIDI_MessageTypes.hpp index c4d8816cf8..82d2204bf6 100644 --- a/src/MIDI_Parsers/MIDI_MessageTypes.hpp +++ b/src/MIDI_Parsers/MIDI_MessageTypes.hpp @@ -1,7 +1,7 @@ #pragma once +#include // Print #include // size_t -#include // uint8_t #include #include #include @@ -152,7 +152,7 @@ struct MIDIMessage { /// If Data 1 and Data 2 represent a single 14-bit number, you can use this /// method to retrieve that number. - uint16_t getValue14bit() const { + uint16_t getData14bit() const { return data1 | (uint16_t(data2) << uint16_t(7)); } @@ -213,6 +213,15 @@ struct ChannelMessage : MIDIMessage { return type <= MIDIMessageType::CONTROL_CHANGE || type == MIDIMessageType::PITCH_BEND; } + + constexpr static auto NOTE_OFF = MIDIMessageType::NOTE_OFF; + constexpr static auto NOTE_ON = MIDIMessageType::NOTE_ON; + constexpr static auto KEY_PRESSURE = MIDIMessageType::KEY_PRESSURE; + constexpr static auto CC = MIDIMessageType::CC; + constexpr static auto CONTROL_CHANGE = MIDIMessageType::CONTROL_CHANGE; + constexpr static auto PROGRAM_CHANGE = MIDIMessageType::PROGRAM_CHANGE; + constexpr static auto CHANNEL_PRESSURE = MIDIMessageType::CHANNEL_PRESSURE; + constexpr static auto PITCH_BEND = MIDIMessageType::PITCH_BEND; }; struct SysCommonMessage : MIDIMessage { @@ -239,6 +248,13 @@ struct SysCommonMessage : MIDIMessage { else return 0; } + + constexpr static auto MTC_QUARTER_FRAME = MIDIMessageType::MTC_QUARTER_FRAME; + constexpr static auto SONG_POSITION_POINTER = MIDIMessageType::SONG_POSITION_POINTER; + constexpr static auto SONG_SELECT = MIDIMessageType::SONG_SELECT; + constexpr static auto UNDEFINED_SYSCOMMON_1 = MIDIMessageType::UNDEFINED_SYSCOMMON_1; + constexpr static auto UNDEFINED_SYSCOMMON_2 = MIDIMessageType::UNDEFINED_SYSCOMMON_2; + constexpr static auto TUNE_REQUEST = MIDIMessageType::TUNE_REQUEST; }; struct SysExMessage { @@ -285,6 +301,9 @@ struct SysExMessage { } bool isCompleteMessage() const { return isFirstChunk() && isLastChunk(); } + + constexpr static auto SYSEX_START = MIDIMessageType::SYSEX_START; + constexpr static auto SYSEX_END = MIDIMessageType::SYSEX_END; }; struct RealTimeMessage { @@ -320,6 +339,15 @@ struct RealTimeMessage { /// Check whether the header is a valid header for a Real-Time message. bool isValid() const { return message >= 0xF8; } + + constexpr static auto TIMING_CLOCK = MIDIMessageType::TIMING_CLOCK; + constexpr static auto UNDEFINED_REALTIME_1 = MIDIMessageType::UNDEFINED_REALTIME_1; + constexpr static auto START = MIDIMessageType::START; + constexpr static auto CONTINUE = MIDIMessageType::CONTINUE; + constexpr static auto STOP = MIDIMessageType::STOP; + constexpr static auto UNDEFINED_REALTIME_2 = MIDIMessageType::UNDEFINED_REALTIME_2; + constexpr static auto ACTIVE_SENSING = MIDIMessageType::ACTIVE_SENSING; + constexpr static auto RESET = MIDIMessageType::RESET; }; #ifndef ARDUINO @@ -336,6 +364,11 @@ inline Print &operator<<(Print &os, SysExMessage m) { return os; } +FlashString_t enum_to_string(MIDIMessageType); +inline Print &operator<<(Print &os, MIDIMessageType m) { + return os << enum_to_string(m); +} + END_CS_NAMESPACE AH_DIAGNOSTIC_POP() \ No newline at end of file diff --git a/test/MIDI_Interfaces/test-MIDI_Pipes.cpp b/test/MIDI_Interfaces/test-MIDI_Pipes.cpp index 8809594724..f475dbf45a 100644 --- a/test/MIDI_Interfaces/test-MIDI_Pipes.cpp +++ b/test/MIDI_Interfaces/test-MIDI_Pipes.cpp @@ -11,18 +11,21 @@ using ::testing::StrictMock; struct MockMIDI_Sink : TrueMIDI_Sink { MOCK_METHOD(void, sinkMIDIfromPipe, (ChannelMessage), (override)); MOCK_METHOD(void, sinkMIDIfromPipe, (SysExMessage), (override)); + MOCK_METHOD(void, sinkMIDIfromPipe, (SysCommonMessage), (override)); MOCK_METHOD(void, sinkMIDIfromPipe, (RealTimeMessage), (override)); }; struct MockMIDI_SinkSource : TrueMIDI_SinkSource { MOCK_METHOD(void, sinkMIDIfromPipe, (ChannelMessage), (override)); MOCK_METHOD(void, sinkMIDIfromPipe, (SysExMessage), (override)); + MOCK_METHOD(void, sinkMIDIfromPipe, (SysCommonMessage), (override)); MOCK_METHOD(void, sinkMIDIfromPipe, (RealTimeMessage), (override)); }; struct DummyMIDI_Sink : TrueMIDI_Sink { void sinkMIDIfromPipe(ChannelMessage) override {} void sinkMIDIfromPipe(SysExMessage) override {} + void sinkMIDIfromPipe(SysCommonMessage) override {} void sinkMIDIfromPipe(RealTimeMessage) override {} };