reworked ActiveSensing
reworked ActiveSensing using input from a variety of device MIDI Implementation manuals (Roland, KORG, Yamaha) found on the internet. Receiving ActiveSensing: 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. Sending ActiveSensing: send x ms after the last sent command
This commit is contained in:
parent
2685bb458b
commit
5972ae5e24
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include <MIDI.h>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
#include <MIDI.h>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -282,7 +282,7 @@ private:
|
||||||
MidiMessage mMessage;
|
MidiMessage mMessage;
|
||||||
unsigned long mLastMessageSentTime;
|
unsigned long mLastMessageSentTime;
|
||||||
unsigned long mLastMessageReceivedTime;
|
unsigned long mLastMessageReceivedTime;
|
||||||
bool mReceiverActiveSensingActivated;
|
bool mReceiverActiveSensingActive;
|
||||||
int8_t mLastError;
|
int8_t mLastError;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
58
src/MIDI.hpp
58
src/MIDI.hpp
|
|
@ -44,9 +44,10 @@ inline MidiInterface<Transport, Settings, Platform>::MidiInterface(Transport& in
|
||||||
, mThruFilterMode(Thru::Full)
|
, mThruFilterMode(Thru::Full)
|
||||||
, mLastMessageSentTime(0)
|
, mLastMessageSentTime(0)
|
||||||
, mLastMessageReceivedTime(0)
|
, mLastMessageReceivedTime(0)
|
||||||
, mReceiverActiveSensingActivated(false)
|
, mReceiverActiveSensingActive(false)
|
||||||
, mLastError(0)
|
, mLastError(0)
|
||||||
{
|
{
|
||||||
|
static_assert(!(Settings::UseSenderActiveSensing && Settings::UseReceiverActiveSensing), "UseSenderActiveSensing and UseReceiverActiveSensing can't be both set to true.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \brief Destructor for MidiInterface.
|
/*! \brief Destructor for MidiInterface.
|
||||||
|
|
@ -82,7 +83,8 @@ void MidiInterface<Transport, Settings, Platform>::begin(Channel inChannel)
|
||||||
mCurrentRpnNumber = 0xffff;
|
mCurrentRpnNumber = 0xffff;
|
||||||
mCurrentNrpnNumber = 0xffff;
|
mCurrentNrpnNumber = 0xffff;
|
||||||
|
|
||||||
mLastMessageSentTime = Platform::now();
|
mLastMessageSentTime =
|
||||||
|
mLastMessageReceivedTime = Platform::now();
|
||||||
|
|
||||||
mMessage.valid = false;
|
mMessage.valid = false;
|
||||||
mMessage.type = InvalidType;
|
mMessage.type = InvalidType;
|
||||||
|
|
@ -710,28 +712,37 @@ template<class Transport, class Settings, class Platform>
|
||||||
inline bool MidiInterface<Transport, Settings, Platform>::read(Channel inChannel)
|
inline bool MidiInterface<Transport, Settings, Platform>::read(Channel inChannel)
|
||||||
{
|
{
|
||||||
#ifndef RegionActiveSending
|
#ifndef RegionActiveSending
|
||||||
|
|
||||||
// Active Sensing. This message is intended to be sent
|
// Active Sensing. This message is intended to be sent
|
||||||
// repeatedly to tell the receiver that a connection is alive. Use
|
// repeatedly to tell the receiver that a connection is alive. Use
|
||||||
// of this message is optional. When initially received, the
|
// of this message is optional.
|
||||||
// receiver will expect to receive another Active Sensing
|
if (Settings::UseSenderActiveSensing)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
|
// Send ActiveSensing <Settings::ActiveSensingPeriodicity> ms after the last command
|
||||||
|
if ((Platform::now() - mLastMessageSentTime) > Settings::SenderActiveSensingPeriodicity)
|
||||||
sendActiveSensing();
|
sendActiveSensing();
|
||||||
mLastMessageSentTime = Platform::now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
// 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
|
mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit
|
||||||
if (mErrorCallback)
|
if (mErrorCallback)
|
||||||
mErrorCallback(mLastError);
|
mErrorCallback(mLastError);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (inChannel >= MIDI_CHANNEL_OFF)
|
if (inChannel >= MIDI_CHANNEL_OFF)
|
||||||
|
|
@ -742,25 +753,26 @@ inline bool MidiInterface<Transport, Settings, Platform>::read(Channel inChannel
|
||||||
|
|
||||||
#ifndef RegionActiveSending
|
#ifndef RegionActiveSending
|
||||||
|
|
||||||
if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing)
|
if (Settings::UseReceiverActiveSensing)
|
||||||
{
|
{
|
||||||
// When an ActiveSensing message is received, the time keeping is activated.
|
mLastMessageReceivedTime = Platform::now();
|
||||||
// When a timeout occurs, an error message is send and time keeping ends.
|
|
||||||
mReceiverActiveSensingActivated = true;
|
|
||||||
|
|
||||||
// is ErrorActiveSensingTimeout bit in mLastError on
|
if (mMessage.type == ActiveSensing && !mReceiverActiveSensingActive)
|
||||||
if (mLastError & (1 << (ErrorActiveSensingTimeout - 1)))
|
|
||||||
{
|
{
|
||||||
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)
|
if (mErrorCallback)
|
||||||
mErrorCallback(mLastError);
|
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
|
#endif
|
||||||
|
|
||||||
handleNullVelocityNoteOnAsNoteOff();
|
handleNullVelocityNoteOnAsNoteOff();
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,6 @@ BEGIN_MIDI_NAMESPACE
|
||||||
#define MIDI_PITCHBEND_MIN -8192
|
#define MIDI_PITCHBEND_MIN -8192
|
||||||
#define MIDI_PITCHBEND_MAX 8191
|
#define MIDI_PITCHBEND_MAX 8191
|
||||||
|
|
||||||
/*! Receiving Active Sensing
|
|
||||||
*/
|
|
||||||
static const uint16_t ActiveSensingTimeout = 300;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Type definitions
|
// Type definitions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,17 +74,15 @@ struct DefaultSettings
|
||||||
|
|
||||||
/*! Global switch to turn on/off sender ActiveSensing
|
/*! Global switch to turn on/off sender ActiveSensing
|
||||||
Set to true to send ActiveSensing
|
Set to true to send ActiveSensing
|
||||||
Set to false will not send ActiveSensing message (will also save memory)
|
/*! Global switch to turn on/off sending and receiving ActiveSensing
|
||||||
as often as possible (1000 / SenderActiveSensingPeriodicity per second).
|
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;
|
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
|
/*! Active Sensing is intended to be sent
|
||||||
repeatedly by the sender to tell the receiver that a connection is alive. Use
|
repeatedly by the sender to tell the receiver that a connection is alive. Use
|
||||||
of this message is optional. When initially received, the
|
of this message is optional. When initially received, the
|
||||||
|
|
@ -95,9 +93,20 @@ struct DefaultSettings
|
||||||
normal (non- active sensing) operation.
|
normal (non- active sensing) operation.
|
||||||
|
|
||||||
Typical value is 250 (ms) - an Active Sensing command is send every 250ms.
|
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
|
END_MIDI_NAMESPACE
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue