Skip to content

Active sensingv2 #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions examples/Hairless/Hairless.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <MIDI.h>
USING_NAMESPACE_MIDI

struct MySerialSettings : public MIDI_NAMESPACE::DefaultSerialSettings
{
static const long BaudRate = 115200;
};

unsigned long t1 = millis();

MIDI_NAMESPACE::SerialMIDI<HardwareSerial, MySerialSettings> serialMIDI(Serial1);
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial, MySerialSettings>> MIDI((MIDI_NAMESPACE::SerialMIDI<HardwareSerial, MySerialSettings>&)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);
}
}
51 changes: 51 additions & 0 deletions examples/ReceiverActiveSensing/ReceiverActiveSensing.ino
Original file line number Diff line number Diff line change
@@ -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 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();
}
58 changes: 58 additions & 0 deletions examples/SenderActiveSensing/SenderActiveSensing.ino
Original file line number Diff line number Diff line change
@@ -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);
}
}
5 changes: 3 additions & 2 deletions src/MIDI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
62 changes: 30 additions & 32 deletions src/MIDI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ inline MidiInterface<Transport, Settings, Platform>::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.
Expand Down Expand Up @@ -84,7 +83,8 @@ void MidiInterface<Transport, Settings, Platform>::begin(Channel inChannel)
mCurrentRpnNumber = 0xffff;
mCurrentNrpnNumber = 0xffff;

mLastMessageSentTime = Platform::now();
mLastMessageSentTime =
mLastMessageReceivedTime = Platform::now();

mMessage.valid = false;
mMessage.type = InvalidType;
Expand Down Expand Up @@ -712,27 +712,32 @@ template<class Transport, class Settings, class Platform>
inline bool MidiInterface<Transport, Settings, Platform>::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 <Settings::ActiveSensingPeriodicity> 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

Expand All @@ -744,25 +749,18 @@ inline bool MidiInterface<Transport, Settings, Platform>::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();
Expand Down Expand Up @@ -1384,7 +1382,7 @@ inline void MidiInterface<Transport, Settings, Platform>::turnThruOff()
template<class Transport, class Settings, class Platform>
inline void MidiInterface<Transport, Settings, Platform>::UpdateLastSentTime()
{
if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity)
if (Settings::UseSenderActiveSensing)
mLastMessageSentTime = Platform::now();
}

Expand Down
6 changes: 1 addition & 5 deletions src/midi_Defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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);
Expand Down
30 changes: 18 additions & 12 deletions src/midi_Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 2 additions & 1 deletion src/serialMIDI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type> serial##Name(SerialPort);\
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<Type>, Settings> Name((MIDI_NAMESPACE::SerialMIDI<Type>&)serial##Name);