Line data Source code
1 : #pragma once 2 : 3 : #include "MIDI_Parser.hpp" 4 : #include "SysExBuffer.hpp" 5 : 6 : BEGIN_CS_NAMESPACE 7 : 8 : /** 9 : * @brief Parser for Serial MIDI streams (and BLE-MIDI). 10 : * 11 : * @ingroup MIDIParsers 12 : */ 13 : class SerialMIDI_Parser : public MIDI_Parser { 14 : public: 15 101 : SerialMIDI_Parser(bool sysCommonCancelsRunningStatus = true) 16 101 : : sysCommonCancelsRunningStatus(sysCommonCancelsRunningStatus) {} 17 : 18 : /** 19 : * @brief Parse one incoming MIDI message. 20 : * @param puller 21 : * The source of MIDI bytes. 22 : * @return The type of MIDI message available, or 23 : * `MIDIReadEvent::NO_MESSAGE` if `puller` ran out of bytes before 24 : * a complete message was parsed. 25 : */ 26 : template <class BytePuller> 27 : MIDIReadEvent pull(BytePuller &&puller); 28 : 29 : protected: 30 : /// Feed a new byte to the parser. 31 : MIDIReadEvent feed(uint8_t midibyte); 32 : /// Resume the parser with the previously stored and unhandled byte. 33 : MIDIReadEvent resume(); 34 : 35 : #if !IGNORE_SYSEX 36 : public: 37 : /// Get the latest SysEx message. 38 48 : SysExMessage getSysExMessage() const { 39 48 : return {sysexbuffer.getBuffer(), sysexbuffer.getLength()}; 40 : } 41 : 42 : protected: 43 764 : void addSysExByte(uint8_t data) { sysexbuffer.add(data); } 44 738 : bool hasSysExSpace() const { return sysexbuffer.hasSpaceLeft(); } 45 34 : void startSysEx() { sysexbuffer.start(); } 46 30 : void endSysEx() { sysexbuffer.end(); } 47 : 48 : SysExBuffer sysexbuffer; 49 : #endif 50 : 51 : protected: 52 : MIDIReadEvent handleRealTime(uint8_t midiByte); 53 : MIDIReadEvent handleNonRealTimeStatus(uint8_t midiByte); 54 : MIDIReadEvent handleStatus(uint8_t midiByte); 55 : MIDIReadEvent handleData(uint8_t midiByte); 56 : 57 : protected: 58 : /// Store a byte to parse later. This is used when the SysEx buffer is full, 59 : /// for example. The byte cannot be added to the buffer now, so store it to 60 : /// add it the next time the parser is updated. 61 11 : void storeByte(uint8_t midiByte) { storedByte = midiByte; } 62 : /// Check whether there's a stored byte. If this is the case, this byte 63 : /// should be parsed before reading a new byte. 64 200 : bool hasStoredByte() const { return storedByte != 0xFF; } 65 : /// Get the stored byte. Afterwards, @ref hasStoredByte will return false. 66 11 : uint8_t popStoredByte() { 67 11 : uint8_t t = storedByte; 68 11 : storedByte = 0xFF; 69 11 : return t; 70 : } 71 : 72 : public: 73 : /// Clear the running status header for MIDI Channel messages. 74 : /// Internal method. 75 : void cancelRunningStatus() { runningHeader = 0; } 76 : 77 : private: 78 : /// Accounts for running status differences between MIDI 1.0 and BLE-MIDI. 79 : bool sysCommonCancelsRunningStatus; 80 : /// Flag that remembers that the next data byte will be the third byte of 81 : /// a message. 82 : bool thirdByte = false; 83 : /// Current header (not necessarily running), contains the header of the 84 : /// message that's currently being received. As soon as the message is 85 : /// complete, it is set to zero. 86 : uint8_t currentHeader = 0; 87 : /// Running status header. 88 : uint8_t runningHeader = 0; 89 : /// @see @ref storeByte 90 : uint8_t storedByte = 0xFF; 91 : }; 92 : 93 : template <class BytePuller> 94 200 : inline MIDIReadEvent SerialMIDI_Parser::pull(BytePuller &&puller) { 95 : // First try resuming the parser, we might have a stored byte that has to 96 : // be parsed first. 97 200 : MIDIReadEvent evt = resume(); 98 200 : if (evt != MIDIReadEvent::NO_MESSAGE) 99 2 : return evt; 100 : 101 : // If resumption didn't produce a message, read new bytes from the input and 102 : // parse them until either we get a message, or until the input runs out of 103 : // new bytes. 104 198 : uint8_t midiByte; 105 1077 : while (puller.pull(midiByte)) { 106 999 : evt = feed(midiByte); 107 999 : if (evt != MIDIReadEvent::NO_MESSAGE) 108 120 : return evt; 109 : } 110 78 : return MIDIReadEvent::NO_MESSAGE; 111 : } 112 : 113 : END_CS_NAMESPACE