LCOV - code coverage report
Current view: top level - muse/framework/midiremote/internal - mmcdecoder.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 87.3 % 150 131
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "mmcdecoder.h"
      24              : 
      25              : #include "midi/midievent.h"
      26              : #include "log.h"
      27              : 
      28              : using namespace muse::midi;
      29              : using namespace muse::midiremote;
      30              : 
      31            4 : static bool extractSysExBytes(const Event& event, std::vector<uint8_t>& out)
      32              : {
      33            4 :     const size_t wordCount = event.midi20WordCount();
      34            4 :     if (wordCount == 0) {
      35            0 :         return false;
      36              :     }
      37              : 
      38            4 :     const uint32_t* words = event.midi20Words();
      39            4 :     const uint32_t w0 = words[0];
      40            4 :     const uint8_t count = (w0 >> 16) & 0xF; // number of valid bytes (0-6)
      41              : 
      42            4 :     if (count == 0) {
      43            0 :         return true;
      44              :     }
      45              : 
      46            4 :     out.reserve(out.size() + count);
      47              : 
      48              :     // Word 0: [header | byte0 | byte1]
      49              :     // Word 1: [byte2 | byte3 | byte4 | byte5]
      50              : 
      51              :     // Extract first 2 bytes from word 0
      52            4 :     if (count >= 1) {
      53            4 :         out.push_back((w0 >> 8) & 0xFF);
      54              :     }
      55            4 :     if (count >= 2) {
      56            4 :         out.push_back((w0 >> 0) & 0xFF);
      57              :     }
      58              : 
      59              :     // Extract remaining bytes from word 1 (if present)
      60            4 :     if (wordCount > 1) {
      61            4 :         const uint32_t w1 = words[1];
      62              : 
      63              :         // Bytes are stored MSB -> LSB
      64              :         // i = 0 -> highest byte, i = 3 -> lowest byte
      65           15 :         for (int i = 0; i < 4; ++i) {
      66           14 :             if (out.size() >= count) {
      67            3 :                 break;
      68              :             }
      69              : 
      70           11 :             out.push_back((w1 >> (24 - i * 8)) & 0xFF);
      71              :         }
      72              :     }
      73              : 
      74            4 :     return true;
      75              : }
      76              : 
      77            7 : static bool isMMC(const std::vector<uint8_t>& sysex)
      78              : {
      79            7 :     return sysex.size() >= 4
      80            7 :            && sysex[0] == 0x7F // Device
      81           14 :            && sysex[2] == 0x06; // Command
      82              : }
      83              : 
      84            5 : static MMCMessage parseMMC(const std::vector<uint8_t>& sysex)
      85              : {
      86            5 :     MMCMessage msg;
      87            5 :     msg.deviceId = sysex[1];
      88            5 :     msg.command = static_cast<MMCCommand>(sysex[3]);
      89              : 
      90            5 :     if (sysex.size() > 4) {
      91            2 :         msg.data.assign(sysex.begin() + 4, sysex.end());
      92              :     }
      93              : 
      94            5 :     return msg;
      95            0 : }
      96              : 
      97              : class MMCDecoder::SysExAssembler
      98              : {
      99              : public:
     100            4 :     bool process(const Event& event, std::vector<uint8_t>& result)
     101              :     {
     102            4 :         std::vector<uint8_t> bytes;
     103            4 :         if (!extractSysExBytes(event, bytes)) {
     104            0 :             return false;
     105              :         }
     106              : 
     107            4 :         const uint32_t* words = event.midi20Words();
     108            4 :         const Status status = static_cast<Status>((words[0] >> 20) & 0xF);
     109              : 
     110            4 :         return processChunk(bytes.data(), bytes.size(), status, result);
     111            4 :     }
     112              : 
     113            6 :     bool process(const uint8_t* data, size_t size, std::vector<uint8_t>& result)
     114              :     {
     115            6 :         const bool hasStartByte = (data[0] == 0xF0);
     116            6 :         const bool hasEndByte = (data[size - 1] == 0xF7);
     117              : 
     118              :         Status status;
     119              : 
     120            6 :         if (hasStartByte && hasEndByte) {
     121            1 :             status = Status::Complete;
     122            5 :         } else if (hasStartByte) {
     123            1 :             status = Status::Start;
     124            4 :         } else if (hasEndByte) {
     125            1 :             status = Status::End;
     126              :         } else {
     127            3 :             status = m_inProgress ? Status::Continue : Status::Complete;
     128              :         }
     129              : 
     130              :         // Skip start & end bytes if present
     131            6 :         const uint8_t* payload = data;
     132            6 :         size_t payloadSize = size;
     133              : 
     134            6 :         if (hasStartByte) {
     135            2 :             payload += 1;
     136            2 :             payloadSize -= 1;
     137              :         }
     138              : 
     139            6 :         if (payloadSize > 0 && hasEndByte) {
     140            2 :             payloadSize -= 1;
     141              :         }
     142              : 
     143            6 :         return processChunk(payload, payloadSize, status, result);
     144              :     }
     145              : 
     146              : private:
     147              :     enum class Status : uint8_t {
     148              :         Complete = 0,
     149              :         Start = 1,
     150              :         Continue = 2,
     151              :         End = 3,
     152              :     };
     153              : 
     154           10 :     bool processChunk(const uint8_t* data, size_t size, Status status, std::vector<uint8_t>& result)
     155              :     {
     156           10 :         switch (status) {
     157            5 :         case Status::Complete: {
     158            5 :             m_buffer.clear();
     159            5 :             m_inProgress = false;
     160            5 :             result.assign(data, data + size);
     161            5 :             return true;
     162              :         }
     163            2 :         case Status::Start: {
     164            2 :             m_buffer.assign(data, data + size);
     165            2 :             m_inProgress = true;
     166            2 :         } break;
     167            1 :         case Status::Continue: {
     168            1 :             if (!m_inProgress) {
     169            0 :                 return false;
     170              :             }
     171            1 :             m_buffer.insert(m_buffer.end(), data, data + size);
     172            1 :         } break;
     173            2 :         case Status::End: {
     174            2 :             if (!m_inProgress) {
     175            0 :                 return false;
     176              :             }
     177            2 :             m_buffer.insert(m_buffer.end(), data, data + size);
     178            2 :             result = m_buffer;
     179            2 :             m_buffer.clear();
     180            2 :             m_inProgress = false;
     181            2 :             return true;
     182              :         }
     183              :         }
     184              : 
     185            3 :         return false;
     186              :     }
     187              : 
     188              :     std::vector<uint8_t> m_buffer;
     189              :     bool m_inProgress = false;
     190              : };
     191              : 
     192           10 : MMCDecoder::~MMCDecoder()
     193              : {
     194           10 :     if (m_assembler) {
     195            7 :         delete m_assembler;
     196              :     }
     197           10 : }
     198              : 
     199            4 : std::optional<MMCMessage> MMCDecoder::decode(const Event& event)
     200              : {
     201            4 :     if (event.messageType() != Event::MessageType::SystemExclusiveData) {
     202            0 :         return std::nullopt;
     203              :     }
     204              : 
     205            4 :     if (!m_assembler) {
     206            3 :         m_assembler = new SysExAssembler();
     207              :     }
     208              : 
     209            4 :     std::vector<uint8_t> sysex;
     210            4 :     if (!m_assembler->process(event, sysex)) {
     211            1 :         return std::nullopt;
     212              :     }
     213              : 
     214            3 :     if (!isMMC(sysex)) {
     215            1 :         return std::nullopt;
     216              :     }
     217              : 
     218            2 :     return parseMMC(sysex);
     219            4 : }
     220              : 
     221            6 : std::optional<MMCMessage> MMCDecoder::decode(const uint8_t* data, size_t size)
     222              : {
     223            6 :     if (!data || size == 0) {
     224            0 :         return std::nullopt;
     225              :     }
     226              : 
     227            6 :     if (!m_assembler) {
     228            4 :         m_assembler = new SysExAssembler();
     229              :     }
     230              : 
     231            6 :     std::vector<uint8_t> sysex;
     232            6 :     if (!m_assembler->process(data, size, sysex)) {
     233            2 :         return std::nullopt;
     234              :     }
     235              : 
     236            4 :     if (!isMMC(sysex)) {
     237            1 :         return std::nullopt;
     238              :     }
     239              : 
     240            3 :     return parseMMC(sysex);
     241            6 : }
     242              : 
     243            3 : std::optional<double> MMCDecoder::locateToSeconds(const MMCMessage& msg) const
     244              : {
     245            3 :     IF_ASSERT_FAILED(msg.command == MMCCommand::Locate) {
     246            0 :         return std::nullopt;
     247              :     }
     248              : 
     249            3 :     if (msg.data.size() < 6) {
     250            0 :         return std::nullopt;
     251              :     }
     252              : 
     253            3 :     const bool hasFormat = (msg.data.size() >= 7);
     254            3 :     const uint8_t format = hasFormat ? msg.data[1] : 0x01;
     255              : 
     256              :     // Only support SMPTE
     257            3 :     if (format != 0x01) {
     258            1 :         return std::nullopt;
     259              :     }
     260              : 
     261            2 :     const size_t offset = hasFormat ? 1 : 0;
     262            2 :     const uint8_t hr_byte = msg.data[1 + offset];
     263            2 :     const uint8_t min = msg.data[2 + offset];
     264            2 :     const uint8_t sec = msg.data[3 + offset];
     265            2 :     const uint8_t frame = msg.data[4 + offset];
     266            2 :     const uint8_t subf = msg.data[5 + offset];
     267            2 :     const uint8_t fpsCode = (hr_byte >> 5) & 0x03;
     268            2 :     const uint8_t hr = hr_byte & 0x1F;
     269              : 
     270            2 :     double fps = 25.0;
     271            2 :     switch (fpsCode) {
     272            0 :     case 0:
     273            0 :         fps = 24.0;
     274            0 :         break;
     275            0 :     case 1:
     276            0 :         fps = 25.0;
     277            0 :         break;
     278            0 :     case 2:
     279            0 :         fps = 29.97;
     280            0 :         break;
     281            2 :     case 3:
     282            2 :         fps = 30.0;
     283            2 :         break;
     284              :     }
     285              : 
     286            2 :     const double total
     287            2 :         = hr * 3600.0
     288            2 :           + min * 60.0
     289            2 :           + sec
     290            2 :           + (frame / fps)
     291            2 :           + (subf / (fps * 100.0));
     292              : 
     293            2 :     return total;
     294              : }
        

Generated by: LCOV version 2.0-1