+ receiver ActiveSensing, + error Callback

1) Active Sensing:
Once an ActiveSensing message is received, the system checks for timeouts: if no message is received within the specified 300ms (see in _Defs), an error is set and the checks for timeout stop.

2) added a callback for errors. 2 errors are defined: parse error and ActiveSensing timeout
This commit is contained in:
lathoub 2020-03-22 15:33:42 +01:00
parent 874b44e6f3
commit d6ac0f6b82
6 changed files with 125 additions and 14 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ src/.DS_Store
examples/.DS_Store
.DS_Store
test/xcode
.development

View File

@ -0,0 +1,36 @@
#include <MIDI.h>
// Simple tutorial on how to receive and send MIDI messages.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.
MIDI_CREATE_DEFAULT_INSTANCE();
USING_NAMESPACE_MIDI
void handleError(int8_t err)
{
if (bitRead(err, ErrorActiveSensingTimeout))
Serial.println("ActiveSensing Timeout");
else
Serial.println("ActiveSensing OK");
digitalWrite(LED_BUILTIN, (err == 0)? LOW : HIGH);
}
void setup()
{
Serial.begin(115200);
while (!Serial) {}
Serial.println("booting");
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
MIDI.setHandleError(handleError);
MIDI.begin(1);
}
void loop()
{
MIDI.read();
}

View File

@ -102,6 +102,7 @@ public:
inline void sendSongPosition(unsigned inBeats);
inline void sendSongSelect(DataByte inSongNumber);
inline void sendTuneRequest();
inline void sendRealTime(MidiType inType);
inline void beginRpn(unsigned inNumber,
@ -168,6 +169,7 @@ public:
public:
inline void setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; };
inline void setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; }
inline void setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; }
inline void setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; }
inline void setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; }
@ -193,6 +195,7 @@ private:
void launchCallback();
void (*mMessageCallback)(const MidiMessage& message) = nullptr;
ErrorCallback mErrorCallback = nullptr;
NoteOffCallback mNoteOffCallback = nullptr;
NoteOnCallback mNoteOnCallback = nullptr;
AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr;
@ -251,12 +254,23 @@ private:
Thru::Mode mThruFilterMode : 7;
MidiMessage mMessage;
int8_t mLastError;
unsigned long mLastMessageSentTime;
unsigned long mLastMessageReceivedTime;
unsigned long mSenderActiveSensingPeriodicity;
bool mReceiverActiveSensingActivated;
private:
inline StatusByte getStatus(MidiType inType,
Channel inChannel) const;
inline void UpdateLastSentTime()
{
if (mSenderActiveSensingPeriodicity)
mLastMessageSentTime = Platform::now();
};
};
// -----------------------------------------------------------------------------

View File

@ -42,8 +42,10 @@ inline MidiInterface<Transport, Settings, Platform>::MidiInterface(Transport& in
, mCurrentNrpnNumber(0xffff)
, mLastMessageSentTime(0)
, mSenderActiveSensingPeriodicity(0)
, mReceiverActiveSensingActivated(false)
, mThruActivated(false)
, mThruFilterMode(Thru::Full)
, mLastError(0)
{
}
@ -138,6 +140,7 @@ void MidiInterface<Transport, Settings, Platform>::send(const MidiMessage& inMes
}
}
mTransport.endTransmission();
UpdateLastSentTime();
}
@ -199,15 +202,13 @@ void MidiInterface<Transport, Settings, Platform>::send(MidiType inType,
}
mTransport.endTransmission();
UpdateLastSentTime();
}
}
else if (inType >= Clock && inType <= SystemReset)
{
sendRealTime(inType); // System Real-time and 1 byte.
}
if (mSenderActiveSensingPeriodicity)
mLastMessageSentTime = Platform::now();
}
// -----------------------------------------------------------------------------
@ -371,7 +372,8 @@ void MidiInterface<Transport, Settings, Platform>::sendSysEx(unsigned inLength,
mTransport.write(MidiType::SystemExclusiveEnd);
mTransport.endTransmission();
}
UpdateLastSentTime();
}
if (Settings::UseRunningStatus)
mRunningStatus_TX = InvalidType;
@ -486,6 +488,7 @@ void MidiInterface<Transport, Settings, Platform>::sendRealTime(MidiType inType)
{
mTransport.write((byte)inType);
mTransport.endTransmission();
UpdateLastSentTime();
}
break;
default:
@ -690,6 +693,7 @@ inline bool MidiInterface<Transport, Settings, Platform>::read()
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
@ -700,16 +704,47 @@ inline bool MidiInterface<Transport, Settings, Platform>::read(Channel inChannel
// normal (non- active sensing) operation.
if ((mSenderActiveSensingPeriodicity > 0) && (Platform::now() - mLastMessageSentTime) > mSenderActiveSensingPeriodicity)
{
sendRealTime(ActiveSensing);
sendActiveSensing();
mLastMessageSentTime = Platform::now();
}
if (mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now()))
{
mReceiverActiveSensingActivated = false;
bitSet(mLastError, ErrorActiveSensingTimeout);
if (mErrorCallback)
mErrorCallback(mLastError);
}
#endif
if (inChannel >= MIDI_CHANNEL_OFF)
return false; // MIDI Input disabled.
if (!parse())
return false;
#ifndef RegionActiveSending
if (mMessage.type == ActiveSensing)
{
// 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;
if (bitRead(mLastError, ErrorActiveSensingTimeout))
{
bitClear(mLastError, ErrorActiveSensingTimeout);
if (mErrorCallback)
mErrorCallback(mLastError);
}
}
// Keep the time of the last received message, so we can check for the timeout
if (mReceiverActiveSensingActivated)
mLastMessageReceivedTime = Platform::now();
#endif
handleNullVelocityNoteOnAsNoteOff();
const bool channelMatch = inputFilter(inChannel);
@ -730,6 +765,8 @@ bool MidiInterface<Transport, Settings, Platform>::parse()
if (mTransport.available() == 0)
return false; // No data available.
bitClear(mLastError, ErrorParse);
// Parsing algorithm:
// Get a byte from the serial buffer.
// If there is no pending message to be recomposed, start a new one.
@ -742,7 +779,7 @@ bool MidiInterface<Transport, Settings, Platform>::parse()
const byte extracted = mTransport.read();
// Ignore Undefined
if (extracted == 0xf9 || extracted == 0xfd)
if (extracted == Undefined_F9 || extracted == Undefined_FD)
return (Settings::Use1ByteParsing) ? false : parse();
if (mPendingMessageIndex == 0)
@ -825,6 +862,10 @@ bool MidiInterface<Transport, Settings, Platform>::parse()
case InvalidType:
default:
// This is obviously wrong. Let's get the hell out'a here.
bitSet(mLastError, ErrorParse);
if (mErrorCallback)
mErrorCallback(mLastError);
resetInput();
return false;
break;
@ -919,6 +960,10 @@ bool MidiInterface<Transport, Settings, Platform>::parse()
else
{
// Well well well.. error.
bitSet(mLastError, ErrorParse);
if (mErrorCallback)
mErrorCallback(mLastError);
resetInput();
return false;
}
@ -977,10 +1022,8 @@ bool MidiInterface<Transport, Settings, Platform>::parse()
mMessage.channel = 0;
mMessage.data1 = mPendingMessage[1];
// Save data2 only if applicable
mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0;
mMessage.length = mPendingMessageExpectedLength;
// Reset local variables

View File

@ -51,6 +51,10 @@ BEGIN_MIDI_NAMESPACE
#define MIDI_PITCHBEND_MIN -8192
#define MIDI_PITCHBEND_MAX 8191
/*! Receiving Active Sensing
*/
static const uint16_t ActiveSensingTimeout = 300;
// -----------------------------------------------------------------------------
// Type definitions
@ -59,9 +63,15 @@ typedef byte DataByte;
typedef byte Channel;
typedef byte FilterMode;
// -----------------------------------------------------------------------------
// Errors
static const uint8_t ErrorParse = 0;
static const uint8_t ErrorActiveSensingTimeout = 1;
// -----------------------------------------------------------------------------
// Aliasing
using ErrorCallback = void (*)(int8_t);
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);
@ -99,12 +109,17 @@ enum MidiType: uint8_t
TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame
SongPosition = 0xF2, ///< System Common - Song Position Pointer
SongSelect = 0xF3, ///< System Common - Song Select
Undefined_F4 = 0xF4,
Undefined_F5 = 0xF5,
TuneRequest = 0xF6, ///< System Common - Tune Request
SystemExclusiveEnd = 0xF7, ///< System Exclusive End
Clock = 0xF8, ///< System Real Time - Timing Clock
Undefined_F9 = 0xF9,
Tick = Undefined_F9,
Start = 0xFA, ///< System Real Time - Start
Continue = 0xFB, ///< System Real Time - Continue
Stop = 0xFC, ///< System Real Time - Stop
Undefined_FD = 0xFD,
ActiveSensing = 0xFE, ///< System Real Time - Active Sensing
SystemReset = 0xFF, ///< System Real Time - System Reset
};

View File

@ -70,8 +70,10 @@ struct DefaultSettings
termination, the receiver will turn off all voices and return to
normal (non- active sensing) operation.
Setting this field to 0 will disable MIDI active sensing.
Typical value is 300 (ms) - an Active Sensing command is send every 300ms.
Typical value is 250 (ms) - an Active Sensing command is send every 250ms.
(All Roland devices send Active Sensing every 250ms)
Setting this field to 0 will disable sending MIDI active sensing.
*/
static const uint16_t SenderActiveSensingPeriodicity = 0;