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
|