113 lines
3.7 KiB
C++
113 lines
3.7 KiB
C++
#pragma once
|
|
|
|
#include "MIDI_Parser.hpp"
|
|
#include "SysExBuffer.hpp"
|
|
|
|
BEGIN_CS_NAMESPACE
|
|
|
|
/**
|
|
* @brief Parser for Serial MIDI streams (and BLE-MIDI).
|
|
*
|
|
* @ingroup MIDIParsers
|
|
*/
|
|
class SerialMIDI_Parser : public MIDI_Parser {
|
|
public:
|
|
SerialMIDI_Parser(bool sysCommonCancelsRunningStatus = true)
|
|
: sysCommonCancelsRunningStatus(sysCommonCancelsRunningStatus) {}
|
|
|
|
/**
|
|
* @brief Parse one incoming MIDI message.
|
|
* @param puller
|
|
* The source of MIDI bytes.
|
|
* @return The type of MIDI message available, or
|
|
* `MIDIReadEvent::NO_MESSAGE` if `puller` ran out of bytes before
|
|
* a complete message was parsed.
|
|
*/
|
|
template <class BytePuller>
|
|
MIDIReadEvent pull(BytePuller &&puller);
|
|
|
|
protected:
|
|
/// Feed a new byte to the parser.
|
|
MIDIReadEvent feed(uint8_t midibyte);
|
|
/// Resume the parser with the previously stored and unhandled byte.
|
|
MIDIReadEvent resume();
|
|
|
|
#if !IGNORE_SYSEX
|
|
public:
|
|
/// Get the latest SysEx message.
|
|
SysExMessage getSysExMessage() const {
|
|
return {sysexbuffer.getBuffer(), sysexbuffer.getLength()};
|
|
}
|
|
|
|
protected:
|
|
void addSysExByte(uint8_t data) { sysexbuffer.add(data); }
|
|
bool hasSysExSpace() const { return sysexbuffer.hasSpaceLeft(); }
|
|
void startSysEx() { sysexbuffer.start(); }
|
|
void endSysEx() { sysexbuffer.end(); }
|
|
|
|
SysExBuffer sysexbuffer;
|
|
#endif
|
|
|
|
protected:
|
|
MIDIReadEvent handleRealTime(uint8_t midiByte);
|
|
MIDIReadEvent handleNonRealTimeStatus(uint8_t midiByte);
|
|
MIDIReadEvent handleStatus(uint8_t midiByte);
|
|
MIDIReadEvent handleData(uint8_t midiByte);
|
|
|
|
protected:
|
|
/// Store a byte to parse later. This is used when the SysEx buffer is full,
|
|
/// for example. The byte cannot be added to the buffer now, so store it to
|
|
/// add it the next time the parser is updated.
|
|
void storeByte(uint8_t midiByte) { storedByte = midiByte; }
|
|
/// Check whether there's a stored byte. If this is the case, this byte
|
|
/// should be parsed before reading a new byte.
|
|
bool hasStoredByte() const { return storedByte != 0xFF; }
|
|
/// Get the stored byte. Afterwards, @ref hasStoredByte will return false.
|
|
uint8_t popStoredByte() {
|
|
uint8_t t = storedByte;
|
|
storedByte = 0xFF;
|
|
return t;
|
|
}
|
|
|
|
public:
|
|
/// Clear the running status header for MIDI Channel messages.
|
|
/// Internal method.
|
|
void cancelRunningStatus() { runningHeader = 0; }
|
|
|
|
private:
|
|
/// Accounts for running status differences between MIDI 1.0 and BLE-MIDI.
|
|
bool sysCommonCancelsRunningStatus;
|
|
/// Flag that remembers that the next data byte will be the third byte of
|
|
/// a message.
|
|
bool thirdByte = false;
|
|
/// Current header (not necessarily running), contains the header of the
|
|
/// message that's currently being received. As soon as the message is
|
|
/// complete, it is set to zero.
|
|
uint8_t currentHeader = 0;
|
|
/// Running status header.
|
|
uint8_t runningHeader = 0;
|
|
/// @see @ref storeByte
|
|
uint8_t storedByte = 0xFF;
|
|
};
|
|
|
|
template <class BytePuller>
|
|
inline MIDIReadEvent SerialMIDI_Parser::pull(BytePuller &&puller) {
|
|
// First try resuming the parser, we might have a stored byte that has to
|
|
// be parsed first.
|
|
MIDIReadEvent evt = resume();
|
|
if (evt != MIDIReadEvent::NO_MESSAGE)
|
|
return evt;
|
|
|
|
// If resumption didn't produce a message, read new bytes from the input and
|
|
// parse them until either we get a message, or until the input runs out of
|
|
// new bytes.
|
|
uint8_t midiByte;
|
|
while (puller.pull(midiByte)) {
|
|
evt = feed(midiByte);
|
|
if (evt != MIDIReadEvent::NO_MESSAGE)
|
|
return evt;
|
|
}
|
|
return MIDIReadEvent::NO_MESSAGE;
|
|
}
|
|
|
|
END_CS_NAMESPACE |