cs-midi/MIDI_Parsers/USBMIDI_Parser.hpp

126 lines
4.0 KiB
C++

#include "MIDI_Parser.hpp"
#include "SysExBuffer.hpp"
#include <AH/Containers/Array.hpp>
#ifdef MIDI_NUM_CABLES
#define USB_MIDI_NUMBER_OF_CABLES MIDI_NUM_CABLES
#elif defined(USB_MIDI4_SERIAL) || defined(USB_MIDI4)
#define USB_MIDI_NUMBER_OF_CABLES 4
#elif defined(USB_MIDI16_AUDIO_SERIAL) || defined(USB_MIDI16_SERIAL) || \
defined(USB_MIDI16)
// TODO: || defined(USB_EVERYTHING)
#define USB_MIDI_NUMBER_OF_CABLES 16
#elif defined(ARDUINO_ARCH_RP2040)
#define USB_MIDI_NUMBER_OF_CABLES 16
#elif !defined(ARDUINO) || defined(DOXYGEN)
#define USB_MIDI_NUMBER_OF_CABLES 16
#else
#define USB_MIDI_NUMBER_OF_CABLES 1
#endif
BEGIN_CS_NAMESPACE
/**
* @brief Parser for MIDI over USB packets.
*
* @ingroup MIDIParsers
*/
class USBMIDI_Parser : public MIDI_Parser {
public:
using MIDIUSBPacket_t = AH::Array<uint8_t, 4>;
/**
* @brief Parse one incoming MIDI message.
* @param puller
* The source of MIDI USB packets.
* @return The type of MIDI message available, or
* `MIDIReadEvent::NO_MESSAGE` if `puller` ran out of packets
* before a complete message was parsed.
*/
template <class BytePuller>
MIDIReadEvent pull(BytePuller &&puller);
protected:
/// Feed a new packet to the parser.
MIDIReadEvent feed(MIDIUSBPacket_t packet);
/// Resume the parser with the previously stored and unhandled packet.
MIDIReadEvent resume();
public:
#if !IGNORE_SYSEX
/// Get the latest SysEx message.
SysExMessage getSysExMessage() const {
return {
sysexbuffers[activeCable.getRaw()].getBuffer(),
sysexbuffers[activeCable.getRaw()].getLength(),
activeCable,
};
}
#endif
protected:
MIDIReadEvent handleChannelMessage(MIDIUSBPacket_t packet, Cable cable);
MIDIReadEvent handleSingleByte(MIDIUSBPacket_t packet, Cable cable);
MIDIReadEvent handleSysExStartCont(MIDIUSBPacket_t packet, Cable cable);
template <uint8_t NumBytes>
MIDIReadEvent handleSysExEnd(MIDIUSBPacket_t packet, Cable cable);
MIDIReadEvent handleSysCommon(MIDIUSBPacket_t packet, Cable cable);
protected:
#if !IGNORE_SYSEX
void startSysEx(Cable cable) { sysexbuffers[cable.getRaw()].start(); }
void endSysEx(Cable cable) {
sysexbuffers[cable.getRaw()].end();
activeCable = cable;
}
void endSysExChunk(Cable cable) { activeCable = cable; }
bool hasSysExSpace(Cable cable, uint8_t amount) const {
return sysexbuffers[cable.getRaw()].hasSpaceLeft(amount);
}
void addSysExByte(Cable cable, uint8_t data) {
sysexbuffers[cable.getRaw()].add(data);
}
void addSysExBytes(Cable cable, const uint8_t *data, uint8_t len) {
sysexbuffers[cable.getRaw()].add(data, len);
}
bool receivingSysEx(Cable cable) const {
return sysexbuffers[cable.getRaw()].isReceiving();
}
void storePacket(MIDIUSBPacket_t packet) { storedPacket = packet; }
bool hasStoredPacket() const { return storedPacket[0] != 0x00; }
MIDIUSBPacket_t popStoredPacket() {
MIDIUSBPacket_t t = storedPacket;
storedPacket[0] = 0x00;
return t;
}
Cable activeCable = Cable_1;
private:
SysExBuffer sysexbuffers[USB_MIDI_NUMBER_OF_CABLES] = {};
MIDIUSBPacket_t storedPacket = {{ 0x00 }};
#endif
};
template <class BytePuller>
inline MIDIReadEvent USBMIDI_Parser::pull(BytePuller &&puller) {
// First try resuming the parser, we might have a stored packet 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 packets from the input
// and parse them until either we get a message, or until the input runs out
// of new packets.
MIDIUSBPacket_t midiPacket;
while (puller.pull(midiPacket)) {
evt = feed(midiPacket);
if (evt != MIDIReadEvent::NO_MESSAGE)
return evt;
}
return MIDIReadEvent::NO_MESSAGE;
}
END_CS_NAMESPACE