diff --git a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino new file mode 100644 index 0000000..e8bcde5 --- /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 errorHandler(int8_t err) +{ + if (bitRead(err, ErrorActiveSensingTimeout)) + { + 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.setHandleError(errorHandler); + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); +} diff --git a/examples/SenderActiveSensing/SenderActiveSensing.ino b/examples/SenderActiveSensing/SenderActiveSensing.ino new file mode 100644 index 0000000..c9c7718 --- /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 d29eb37..ffc7e16 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -282,7 +282,7 @@ private: MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; - bool mReceiverActiveSensingActivated; + bool mReceiverActiveSensingActive; int8_t mLastError; private: diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 02a82b3..3b9e29b 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -44,9 +44,10 @@ inline MidiInterface::MidiInterface(Transport& in , mThruFilterMode(Thru::Full) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) - , mReceiverActiveSensingActivated(false) + , mReceiverActiveSensingActive(false) , mLastError(0) { + static_assert(!(Settings::UseSenderActiveSensing && Settings::UseReceiverActiveSensing), "UseSenderActiveSensing and UseReceiverActiveSensing can't be both set to true."); } /*! \brief Destructor for MidiInterface. @@ -82,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; @@ -710,27 +712,36 @@ 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 && (Platform::now() - mLastMessageSentTime) > Settings::SenderActiveSensingPeriodicity) + // 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())) + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActive) { - mReceiverActiveSensingActivated = false; + if ((Platform::now() - mLastMessageReceivedTime > Settings::ReceiverActiveSensingTimeout)) + { + // 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. + mReceiverActiveSensingActive = false; - mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + // its up to the error handler to send the stop processing messages + // (also, no clue what the channel is on which to send them) + + // no need to check if bit is already set, it is not (due to the mActiveSensingActive switch) + mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit + if (mErrorCallback) + mErrorCallback(mLastError); + } } #endif @@ -742,25 +753,26 @@ 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 + // 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. + mReceiverActiveSensingActive = true; + + // Clear the ErrorActiveSensingTimeout bit + mLastError &= ~(1UL << ErrorActiveSensingTimeout); if (mErrorCallback) mErrorCallback(mLastError); } } - // Keep the time of the last received message, so we can check for the timeout - if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated) - mLastMessageReceivedTime = Platform::now(); - #endif handleNullVelocityNoteOnAsNoteOff(); diff --git a/src/midi_Defs.h b/src/midi_Defs.h index ef74621..623d97e 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 diff --git a/src/midi_Settings.h b/src/midi_Settings.h index f142593..0accbac 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -74,17 +74,15 @@ struct DefaultSettings /*! 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) - as often as possible (1000 / SenderActiveSensingPeriodicity per second). + /*! 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) + + When setting UseActiveSensing to true, MIDI.read() *must* be called + as often as possible (1000 / ActiveSensingPeriodicity per second). */ static const bool UseSenderActiveSensing = false; - /*! 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) - */ - static const bool UseReceiverActiveSensing = false; - /*! Active Sensing is intended to be sent repeatedly by the sender to tell the receiver that a connection is alive. Use of this message is optional. When initially received, the @@ -95,9 +93,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 = 250; + static const uint16_t SenderActiveSensingPeriodicity = 300; + + /*! 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 bool UseReceiverActiveSensing = false; + + static const uint16_t ReceiverActiveSensingTimeout = 300; + }; END_MIDI_NAMESPACE