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 : }
|