diff --git a/examples/Hairless/Hairless.ino b/examples/Hairless/Hairless.ino new file mode 100644 index 00000000..f03937de --- /dev/null +++ b/examples/Hairless/Hairless.ino @@ -0,0 +1,30 @@ +#include +USING_NAMESPACE_MIDI + +struct MySerialSettings : public MIDI_NAMESPACE::DefaultSerialSettings +{ + static const long BaudRate = 115200; +}; + +unsigned long t1 = millis(); + +MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial1); +MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); + +void setup() +{ + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); + + // send a note every second + if ((millis() - t1) > 1000) + { + t1 = millis(); + + MIDI.sendNoteOn(random(1, 127), 55, 1); + } +} diff --git a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino new file mode 100644 index 00000000..6d91822b --- /dev/null +++ b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino @@ -0,0 +1,51 @@ +#include +USING_NAMESPACE_MIDI + +struct MyMIDISettings : public MIDI_NAMESPACE::DefaultSettings +{ + // When setting UseReceiverActiveSensing to true, MIDI.read() *must* be called + // as often as possible (1000 / SenderActiveSensingPeriodicity per second). + // + // setting UseReceiverActiveSensing to true, adds 174 bytes of code. + // + // (Taken from a Roland MIDI Implementation Owner's manual) + // Once an Active Sensing message is received, the unit will begin monitoring + // the interval between all subsequent messages. If there is an interval of 420 ms + // or longer between messages while monitoring is active, the same processing + // as when All Sound Off, All Notes Off,and Reset All Controllers messages are + // received will be carried out. The unit will then stopmonitoring the message interval. + + static const bool UseReceiverActiveSensing = true; + + static const uint16_t ReceiverActiveSensingTimeout = 420; +}; + +MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, MyMIDISettings); + +void activeSensingTimeoutExceptionHandler(bool active) +{ + if (!active) + { + MIDI.sendControlChange(AllSoundOff, 0, 1); + MIDI.sendControlChange(AllNotesOff, 0, 1); + MIDI.sendControlChange(ResetAllControllers, 0, 1); + + digitalWrite(LED_BUILTIN, HIGH); + } + else + digitalWrite(LED_BUILTIN, LOW); +} + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + MIDI.setHandleActiveSensingTimeout(activeSensingTimeoutExceptionHandler); + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); +} diff --git a/examples/SenderActiveSensing/SenderActiveSensing.ino b/examples/SenderActiveSensing/SenderActiveSensing.ino new file mode 100644 index 00000000..c9c77182 --- /dev/null +++ b/examples/SenderActiveSensing/SenderActiveSensing.ino @@ -0,0 +1,58 @@ +#include +USING_NAMESPACE_MIDI + +struct MyMIDISettings : public MIDI_NAMESPACE::DefaultSettings +{ + // When setting UseSenderActiveSensing to true, MIDI.read() *must* be called + // as often as possible (1000 / SenderActiveSensingPeriodicity per second). + // + // setting UseSenderActiveSensing to true, adds 34 bytes of code. + // + // When using Active Sensing, call MIDI.read(); in the Arduino loop() + // + // from 'a' MIDI implementation manual: "Sent periodically" + // In the example here, a NoteOn is send every 1000ms (1s), ActiveSensing is + // send every 250ms after the last command. + // Logging the command will look like this: + // + // ... + // A.Sense FE + // A.Sense FE + // A.Sense FE + // NoteOn 90 04 37 [E-2] + // A.Sense FE + // A.Sense FE + // A.Sense FE + // NoteOn 90 04 37 [E-2] + // A.Sense FE + // A.Sense FE + // A.Sense FE + // NoteOn 90 04 37 [E-2] + // ... + + static const bool UseSenderActiveSensing = true; + + static const uint16_t SenderActiveSensingPeriodicity = 250; +}; + +unsigned long t1 = millis(); + +MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, MyMIDISettings); + +void setup() +{ + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); + + // send a note every second + if ((millis() - t1) > 1000) + { + t1 = millis(); + + MIDI.sendNoteOn(random(1, 127), 55, 1); + } +} diff --git a/src/MIDI.h b/src/MIDI.h index d15888fc..a5bb5c7c 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -185,6 +185,7 @@ class MidiInterface public: inline void setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; }; inline void setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; } + inline void setHandleActiveSensingTimeout(ActiveSensingTimeoutCallback fptr) { mActiveSensingTimeoutCallback = fptr; } inline void setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; } inline void setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; } inline void setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; } @@ -212,6 +213,7 @@ class MidiInterface void (*mMessageCallback)(const MidiMessage& message) = nullptr; ErrorCallback mErrorCallback = nullptr; + ActiveSensingTimeoutCallback mActiveSensingTimeoutCallback = nullptr; NoteOffCallback mNoteOffCallback = nullptr; NoteOnCallback mNoteOnCallback = nullptr; AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr; @@ -282,8 +284,7 @@ class MidiInterface MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; - unsigned long mSenderActiveSensingPeriodicity; - bool mReceiverActiveSensingActivated; + bool mReceiverActiveSensingActive; int8_t mLastError; private: diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 17319163..d304fa8a 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -44,11 +44,10 @@ inline MidiInterface::MidiInterface(Transport& in , mThruFilterMode(Thru::Full) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) - , mSenderActiveSensingPeriodicity(0) - , mReceiverActiveSensingActivated(false) + , mReceiverActiveSensingActive(false) , mLastError(0) { - mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; + static_assert(!(Settings::UseSenderActiveSensing && Settings::UseReceiverActiveSensing), "UseSenderActiveSensing and UseReceiverActiveSensing can't be both set to true."); } /*! \brief Destructor for MidiInterface. @@ -84,7 +83,8 @@ void MidiInterface::begin(Channel inChannel) mCurrentRpnNumber = 0xffff; mCurrentNrpnNumber = 0xffff; - mLastMessageSentTime = Platform::now(); + mLastMessageSentTime = + mLastMessageReceivedTime = Platform::now(); mMessage.valid = false; mMessage.type = InvalidType; @@ -712,27 +712,32 @@ template inline bool MidiInterface::read(Channel inChannel) { #ifndef RegionActiveSending + // Active Sensing. This message is intended to be sent // repeatedly to tell the receiver that a connection is alive. Use - // of this message is optional. When initially received, the - // receiver will expect to receive another Active Sensing - // message each 300ms (max), and if it does not then it will - // assume that the connection has been terminated. At - // termination, the receiver will turn off all voices and return to - // normal (non- active sensing) operation. - if (Settings::UseSenderActiveSensing && (mSenderActiveSensingPeriodicity > 0) && (Platform::now() - mLastMessageSentTime) > mSenderActiveSensingPeriodicity) + // of this message is optional. + if (Settings::UseSenderActiveSensing) { - sendActiveSensing(); - mLastMessageSentTime = Platform::now(); + // Send ActiveSensing ms after the last command + if ((Platform::now() - mLastMessageSentTime) > Settings::SenderActiveSensingPeriodicity) + sendActiveSensing(); } - if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now())) + // Once an Active Sensing message is received, the unit will begin monitoring + // the intervalbetween all subsequent messages. If there is an interval of 420 ms + // or longer betweenmessages while monitoring is active, the same processing + // as when All Sound Off, All Notes Off,and Reset All Controllers messages are + // received will be carried out. The unit will then stopmonitoring the message interval. + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActive) { - mReceiverActiveSensingActivated = false; + if ((Platform::now() - mLastMessageReceivedTime > Settings::ReceiverActiveSensingTimeout)) + { + mReceiverActiveSensingActive = false; - mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + // its up to the handler to send the stop processing messages + // (also, no clue what the channel is on which to send them) + mActiveSensingTimeoutCallback(mReceiverActiveSensingActive); + } } #endif @@ -744,25 +749,18 @@ inline bool MidiInterface::read(Channel inChannel #ifndef RegionActiveSending - if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing) + if (Settings::UseReceiverActiveSensing) { - // When an ActiveSensing message is received, the time keeping is activated. - // When a timeout occurs, an error message is send and time keeping ends. - mReceiverActiveSensingActivated = true; + mLastMessageReceivedTime = Platform::now(); - // is ErrorActiveSensingTimeout bit in mLastError on - if (mLastError & (1 << (ErrorActiveSensingTimeout - 1))) + if (mMessage.type == ActiveSensing && !mReceiverActiveSensingActive) { - mLastError &= ~(1UL << ErrorActiveSensingTimeout); // clear the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + mReceiverActiveSensingActive = true; + + mActiveSensingTimeoutCallback(mReceiverActiveSensingActive); } } - // Keep the time of the last received message, so we can check for the timeout - if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated) - mLastMessageReceivedTime = Platform::now(); - #endif handleNullVelocityNoteOnAsNoteOff(); @@ -1384,7 +1382,7 @@ inline void MidiInterface::turnThruOff() template inline void MidiInterface::UpdateLastSentTime() { - if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity) + if (Settings::UseSenderActiveSensing) mLastMessageSentTime = Platform::now(); } diff --git a/src/midi_Defs.h b/src/midi_Defs.h index ef74621c..4ee9ec48 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -46,10 +46,6 @@ BEGIN_MIDI_NAMESPACE #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 -/*! Receiving Active Sensing -*/ -static const uint16_t ActiveSensingTimeout = 300; - // ----------------------------------------------------------------------------- // Type definitions @@ -61,13 +57,13 @@ typedef byte FilterMode; // ----------------------------------------------------------------------------- // Errors static const uint8_t ErrorParse = 0; -static const uint8_t ErrorActiveSensingTimeout = 1; static const uint8_t WarningSplitSysEx = 2; // ----------------------------------------------------------------------------- // Aliasing using ErrorCallback = void (*)(int8_t); +using ActiveSensingTimeoutCallback = void (*)(bool); using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity); using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity); using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity); diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 179b773e..6c40922c 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -72,17 +72,14 @@ struct DefaultSettings */ static const unsigned SysExMaxSize = 128; - /*! Global switch to turn on/off sender ActiveSensing - Set to true to send ActiveSensing - Set to false will not send ActiveSensing message (will also save memory) - */ - static const bool UseSenderActiveSensing = false; + /*! Global switch to turn on/off sending and receiving ActiveSensing + Set to true to activate ActiveSensing + Set to false will not send/receive ActiveSensing message (will also save 236 bytes of memory) - /*! Global switch to turn on/off receiver ActiveSensing - Set to true to check for message timeouts (via ErrorCallback) - Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory) + When setting UseActiveSensing to true, MIDI.read() *must* be called + as often as possible (1000 / ActiveSensingPeriodicity per second). */ - static const bool UseReceiverActiveSensing = false; + static const bool UseSenderActiveSensing = false; /*! Active Sensing is intended to be sent repeatedly by the sender to tell the receiver that a connection is alive. Use @@ -94,11 +91,20 @@ struct DefaultSettings normal (non- active sensing) operation. Typical value is 250 (ms) - an Active Sensing command is send every 250ms. - (All Roland devices send Active Sensing every 250ms) + (Most Roland devices send Active Sensing every 250ms) + */ + static const uint16_t SenderActiveSensingPeriodicity = 300; - Setting this field to 0 will disable sending MIDI active sensing. + /*! Once an Active Sensing message is received, the unit will begin monitoring + the intervalbetween all subsequent messages. If there is an interval of ActiveSensingPeriodicity ms + or longer betweenmessages while monitoring is active, the same processing + as when All Sound Off, All Notes Off,and Reset All Controllers messages are + received will be carried out. The unit will then stopmonitoring the message interval. */ - static const uint16_t SenderActiveSensingPeriodicity = 0; + static const bool UseReceiverActiveSensing = false; + + static const uint16_t ReceiverActiveSensingTimeout = 300; + }; END_MIDI_NAMESPACE diff --git a/src/serialMIDI.h b/src/serialMIDI.h index 9bd96694..40c36d7e 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -116,10 +116,11 @@ END_MIDI_NAMESPACE #endif /*! \brief Create an instance of the library attached to a serial port with - custom settings. + custom MIDI settings (not to be confused with modified Serial Settings, like BaudRate) @see DefaultSettings @see MIDI_CREATE_INSTANCE */ #define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ MIDI_NAMESPACE::MidiInterface, Settings> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); +