+ 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:
parent
874b44e6f3
commit
d6ac0f6b82
|
|
@ -6,4 +6,5 @@ build/
|
|||
src/.DS_Store
|
||||
examples/.DS_Store
|
||||
.DS_Store
|
||||
test/xcode
|
||||
test/xcode
|
||||
.development
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
14
src/MIDI.h
14
src/MIDI.h
|
|
@ -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();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
|||
65
src/MIDI.hpp
65
src/MIDI.hpp
|
|
@ -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,18 +704,49 @@ 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);
|
||||
if (channelMatch)
|
||||
launchCallback();
|
||||
|
|
@ -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.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0;
|
||||
mMessage.length = mPendingMessageExpectedLength;
|
||||
|
||||
// Reset local variables
|
||||
|
|
@ -1239,7 +1282,7 @@ template<class Transport, class Settings, class Platform>
|
|||
void MidiInterface<Transport, Settings, Platform>::launchCallback()
|
||||
{
|
||||
if (mMessageCallback != 0) mMessageCallback(mMessage);
|
||||
|
||||
|
||||
// The order is mixed to allow frequent messages to trigger their callback faster.
|
||||
switch (mMessage.type)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue