LCOV - code coverage report
Current view: top level - src/MIDI_Parsers - SerialMIDI_Parser.cpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 100.0 % 98 98
Test Date: 2026-06-06 17:44:35 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #include "SerialMIDI_Parser.hpp"
       2              : 
       3              : BEGIN_CS_NAMESPACE
       4              : 
       5            9 : MIDIReadEvent SerialMIDI_Parser::handleRealTime(uint8_t midiByte) {
       6            9 :     rtmsg.message = midiByte;
       7            9 :     return MIDIReadEvent::REALTIME_MESSAGE;
       8              : }
       9              : 
      10          131 : MIDIReadEvent SerialMIDI_Parser::handleNonRealTimeStatus(uint8_t midiByte) {
      11              : #if !IGNORE_SYSEX
      12              :     // If a SysEx message was being received, and now we receive another
      13              :     // status byte, the status byte should terminate the SysEx message
      14              :     // first, and then we can handle the new status byte later.
      15          131 :     bool untermSysEx = currentHeader == uint8_t(MIDIMessageType::SysExStart);
      16          131 :     if (untermSysEx) {
      17              :         // Handle this new status byte later (unless it's just a SysEx End
      18              :         // byte, in which case we can just terminate it now).
      19           31 :         if (midiByte != uint8_t(MIDIMessageType::SysExEnd))
      20            7 :             storeByte(midiByte);
      21              :         // Terminate the SysEx message.
      22              :         // Check if the SysEx buffer has enough space to store the end byte.
      23           31 :         if (!hasSysExSpace()) {
      24              :             // If not store the new status byte to handle it later, and
      25              :             // return the chunk we have saved up to now.
      26            1 :             storeByte(midiByte);
      27            1 :             return MIDIReadEvent::SYSEX_CHUNK;
      28              :         }
      29              :         // Enough space is available in buffer, store the end byte and
      30              :         // terminate the message.
      31           30 :         addSysExByte(uint8_t(MIDIMessageType::SysExEnd));
      32           30 :         endSysEx();
      33           30 :         currentHeader = 0;
      34           30 :         runningHeader = 0;
      35           30 :         return MIDIReadEvent::SYSEX_MESSAGE;
      36              :     } else
      37              : #endif
      38              :     {
      39              :         // Tune Request is a special System Common message of 1 byte.
      40          100 :         if (midiByte == uint8_t(MIDIMessageType::TuneRequest)) {
      41            5 :             midimsg.header = midiByte;
      42            5 :             midimsg.data1 = 0;
      43            5 :             midimsg.data2 = 0;
      44            5 :             if (sysCommonCancelsRunningStatus)
      45            4 :                 runningHeader = 0;
      46            5 :             currentHeader = 0;
      47            5 :             return MIDIReadEvent::SYSCOMMON_MESSAGE;
      48              :         }
      49              : #if !IGNORE_SYSEX
      50              :         // If the new status byte is a SysExStart, reset the SysEx buffer
      51              :         // and store the start byte.
      52           95 :         else if (midiByte == uint8_t(MIDIMessageType::SysExStart)) {
      53           30 :             startSysEx();
      54           30 :             addSysExByte(uint8_t(MIDIMessageType::SysExStart));
      55           30 :             runningHeader = 0;
      56           30 :             currentHeader = midiByte;
      57           30 :             return MIDIReadEvent::NO_MESSAGE;
      58              :         }
      59              :         // This should already have been handled by the if (untermSysEx) above.
      60           65 :         else if (midiByte == uint8_t(MIDIMessageType::SysExEnd)) {
      61              :             DEBUGREF(F("Unexpected SysEx End"));
      62           11 :             return MIDIReadEvent::NO_MESSAGE;
      63              :         }
      64              : #endif
      65              :         // Otherwise, start a System Common or Channel message.
      66              :         else {
      67              :             // Save the newly received status byte.
      68           54 :             currentHeader = midiByte;
      69              :             // A new message starts, so we haven't received the second byte
      70              :             // yet.
      71           54 :             thirdByte = false;
      72           54 :             return MIDIReadEvent::NO_MESSAGE;
      73              :         }
      74              :     }
      75              : }
      76              : 
      77              : /*
      78              :  * Relevant sources about MIDI running status:
      79              :  *  - MIDI 1.0 Detailed Specification: A-2 (pdf p.65):
      80              :  *    “cleared when a System Exclusive or Common status message is received”
      81              :  *  - BLE-MIDI: p.4 (pdf p.7):
      82              :  *    “System Common and System Real-Time messages do not cancel Running Status”
      83              :  */
      84              : 
      85          140 : MIDIReadEvent SerialMIDI_Parser::handleStatus(uint8_t midiByte) {
      86              :     // If it's a Real-Time message
      87          140 :     if (midiByte >= uint8_t(MIDIMessageType::TimingClock)) {
      88            9 :         return handleRealTime(midiByte);
      89              :     }
      90              :     // Normal header (channel message, system exclusive, system common):
      91              :     else {
      92          131 :         return handleNonRealTimeStatus(midiByte);
      93              :     }
      94              : }
      95              : 
      96          870 : MIDIReadEvent SerialMIDI_Parser::handleData(uint8_t midiByte) {
      97          870 :     if (currentHeader == 0) {
      98              :         // If we didn't receive a header, we can't do anything with this data
      99           52 :         if (runningHeader == 0) {
     100              :             DEBUGREF(F("Data byte ignored"));
     101           32 :             return MIDIReadEvent::NO_MESSAGE;
     102              :         }
     103              :         // If we have an active running status, use that as the current header
     104           20 :         currentHeader = runningHeader;
     105              :     }
     106              : 
     107          838 :     midimsg.header = currentHeader;
     108              : 
     109              :     // If this is the third byte of three (second data byte)
     110          838 :     if (thirdByte) {
     111              :         // If it's a channel message
     112           57 :         if (midimsg.hasValidChannelMessageHeader()) {
     113           51 :             midimsg.data2 = midiByte;
     114              :             // Next byte is either a header or the first data byte of the next
     115              :             // message, so clear the thirdByte flag
     116           51 :             thirdByte = false;
     117           51 :             runningHeader = midimsg.header;
     118           51 :             currentHeader = 0;
     119           51 :             return MIDIReadEvent::CHANNEL_MESSAGE;
     120              :         }
     121              :         // If it's a system common message
     122            6 :         else if (midimsg.hasValidSystemCommonHeader()) {
     123            6 :             midimsg.data2 = midiByte;
     124            6 :             thirdByte = false;
     125            6 :             if (sysCommonCancelsRunningStatus)
     126            4 :                 runningHeader = 0;
     127            6 :             currentHeader = 0;
     128            6 :             return MIDIReadEvent::SYSCOMMON_MESSAGE;
     129              :         }
     130              :     }
     131              : 
     132              :     // If this is not the third byte of three, it's either the second byte
     133              :     // (first data byte) of a channel or system common message,
     134              :     // or a SysEx data byte
     135              : 
     136              :     // If it's a channel message
     137          781 :     else if (midimsg.hasValidChannelMessageHeader()) {
     138              :         // If it's a channel message with two data bytes
     139           62 :         if (ChannelMessage(midimsg).hasTwoDataBytes()) {
     140           51 :             midimsg.data1 = midiByte;
     141              :             // We've received the second byte, expect the third byte next
     142           51 :             thirdByte = true;
     143           51 :             return MIDIReadEvent::NO_MESSAGE;
     144              :         }
     145              :         // If it's a channel message with one data byte
     146              :         else {
     147           11 :             midimsg.data1 = midiByte;
     148           11 :             midimsg.data2 = 0;
     149           11 :             runningHeader = midimsg.header;
     150           11 :             currentHeader = 0;
     151              :             // The message is finished
     152           11 :             return MIDIReadEvent::CHANNEL_MESSAGE;
     153              :         }
     154              :     }
     155              : 
     156              :     // If it's a system common message
     157          719 :     else if (midimsg.hasValidSystemCommonHeader()) {
     158              :         // If it's a system common message with two data bytes
     159           12 :         if (SysCommonMessage(midimsg).getNumberOfDataBytes() == 2) {
     160            6 :             midimsg.data1 = midiByte;
     161              :             // We've received the second byte, expect the third byte next
     162            6 :             thirdByte = true;
     163            6 :             return MIDIReadEvent::NO_MESSAGE;
     164              :         }
     165              :         // If it's a system common message with one data byte
     166            6 :         else if (SysCommonMessage(midimsg).getNumberOfDataBytes() == 1) {
     167            6 :             midimsg.data1 = midiByte;
     168            6 :             midimsg.data2 = 0;
     169            6 :             if (sysCommonCancelsRunningStatus)
     170            5 :                 runningHeader = 0;
     171            6 :             currentHeader = 0;
     172              :             // The message is finished
     173            6 :             return MIDIReadEvent::SYSCOMMON_MESSAGE;
     174              :         }
     175              :     }
     176              : 
     177              :     // Otherwise, it's not a channel message
     178              : 
     179              : #if !IGNORE_SYSEX
     180              :     // If we're receiving a SysEx message, it's a SysEx data byte
     181          707 :     else if (currentHeader == uint8_t(MIDIMessageType::SysExStart)) {
     182              :         // Check if the SysEx buffer has enough space to store the data
     183          707 :         if (!hasSysExSpace()) {
     184            3 :             storeByte(midiByte); // Remember to add it next time
     185            3 :             return MIDIReadEvent::SYSEX_CHUNK;
     186              :         }
     187              : 
     188          704 :         addSysExByte(midiByte);
     189          704 :         return MIDIReadEvent::NO_MESSAGE;
     190              :     }
     191              : #endif // IGNORE_SYSEX
     192              : 
     193              :     DEBUGREF(F("Data byte after invalid header")); // LCOV_EXCL_LINE
     194              :     runningHeader = 0;                             // LCOV_EXCL_LINE
     195              :     currentHeader = 0;                             // LCOV_EXCL_LINE
     196              :     return MIDIReadEvent::NO_MESSAGE;              // LCOV_EXCL_LINE
     197              : }
     198              : 
     199         1010 : MIDIReadEvent SerialMIDI_Parser::feed(uint8_t midiByte) {
     200              :     // DEBUGREF(hex << NAMEDVALUE(midiByte) << dec);
     201              : 
     202              :     // If it's a status byte (first byte of a message)
     203         1010 :     if (isStatus(midiByte)) {
     204          140 :         return handleStatus(midiByte);
     205              :     }
     206              :     // If it's a data byte
     207              :     else {
     208          870 :         return handleData(midiByte);
     209              :     }
     210              : }
     211              : 
     212          200 : MIDIReadEvent SerialMIDI_Parser::resume() {
     213          200 :     if (!hasStoredByte())
     214          189 :         return MIDIReadEvent::NO_MESSAGE;
     215              : 
     216           11 :     uint8_t midiByte = popStoredByte();
     217              : 
     218              : #if !IGNORE_SYSEX
     219              :     // If a SysEx message was in progress
     220           11 :     if (currentHeader == uint8_t(MIDIMessageType::SysExStart)) {
     221              :         // Reset the buffer for the next chunk
     222            4 :         startSysEx();
     223              :     }
     224              : #endif
     225              : 
     226           11 :     return feed(midiByte);
     227              : }
     228              : 
     229              : END_CS_NAMESPACE
        

Generated by: LCOV version 2.4-beta