1.1 --- a/AlsaBridge.cpp Fri May 09 23:17:36 2025 +0200
1.2 +++ b/AlsaBridge.cpp Sun May 11 00:30:03 2025 +0200
1.3 @@ -43,9 +43,14 @@
1.4 std::recursive_mutex midiMutex;
1.5 std::atomic<bool> stopped{false};
1.6
1.7 - std::string findDeviceName(std::regex cardNamePattern) {
1.8 + struct FoundDevice {
1.9 + std::string id;
1.10 + std::string name;
1.11 + };
1.12
1.13 + FoundDevice findDeviceName(std::regex cardNamePattern) {
1.14 std::vector<int> cardNumbers;
1.15 + std::vector<std::string> cardNames;
1.16
1.17 logger->log(L::INFO, "Looking for available cards:");
1.18
1.19 @@ -58,6 +63,7 @@
1.20
1.21 if (std::regex_match(longName, cardNamePattern)) {
1.22 cardNumbers.push_back(card);
1.23 + cardNames.push_back(longName);
1.24 logMessage << " [matches]";
1.25 }
1.26
1.27 @@ -69,7 +75,7 @@
1.28 if (cardNumbers.size() == 1) {
1.29 const auto n = std::to_string(cardNumbers[0]);
1.30 logger->log(L::INFO, "Going to fix card #" + n);
1.31 - return "hw:" + n;
1.32 + return {"hw:" + n, cardNames[0]};
1.33 } else if (cardNumbers.empty()) {
1.34 throw std::invalid_argument(
1.35 "No card with matching name found. Is the card connected? "
1.36 @@ -89,6 +95,7 @@
1.37 }
1.38
1.39 void run() {
1.40 + MidiMessage msg;
1.41 while (!stopped) {
1.42 {
1.43 std::lock_guard<std::recursive_mutex> lock(midiMutex);
1.44 @@ -96,8 +103,32 @@
1.45 uint8_t buf[256];
1.46 ssize_t length = snd_rawmidi_read(input, buf, sizeof (buf));
1.47 if (length > 0 && length <= sizeof (buf)) {
1.48 - // TODO: multiple messages combined together?
1.49 - djmFix->receive(MidiMessage(buf, buf + length));
1.50 + // Parse MIDI messages and ignore/skip unwanted data.
1.51 + // Needed for DJM-V10 that sends annoying amounts of 0xF8.
1.52 + for (int i = 0; i < length; i++) {
1.53 + uint8_t b = buf[i];
1.54 + if (b == MIDI_CMD_COMMON_SYSEX) {
1.55 + // start of MIDI SysEx message
1.56 + msg.clear();
1.57 + msg.push_back(b);
1.58 + } else if (b == MIDI_CMD_COMMON_SYSEX_END) {
1.59 + // end of MIDI SysEx message
1.60 + msg.push_back(b);
1.61 + djmFix->receive(msg);
1.62 + msg.clear();
1.63 + } else if (b == MIDI_CMD_COMMON_CLOCK) {
1.64 + logger->log(L::FINEST, "timing clock ignored");
1.65 + } else if (b == MIDI_CMD_COMMON_SENSING) {
1.66 + logger->log(L::FINEST, "active sensing ignored");
1.67 + } else if (b & 0x80) {
1.68 + // unknown status, drop previous data
1.69 + msg.clear();
1.70 + logger->log(L::FINER, "unknown message ignored");
1.71 + } else {
1.72 + // message data
1.73 + msg.push_back(b);
1.74 + }
1.75 + }
1.76 }
1.77 }
1.78 std::this_thread::sleep_for(std::chrono::milliseconds(100));
1.79 @@ -114,12 +145,13 @@
1.80 if (djmFix == nullptr)
1.81 throw std::invalid_argument("Need a djmFix for AlsaBridge.");
1.82
1.83 - std::string deviceName = findDeviceName(std::regex(cardNamePattern));
1.84 + FoundDevice found = findDeviceName(std::regex(cardNamePattern));
1.85
1.86 int mode = SND_RAWMIDI_NONBLOCK;
1.87 - int error = snd_rawmidi_open(&input, &output, deviceName.c_str(), mode);
1.88 + int error = snd_rawmidi_open(&input, &output, found.id.c_str(), mode);
1.89 if (error) throw std::invalid_argument("Unable to open ALSA device.");
1.90
1.91 + djmFix->setDeviceName(found.name);
1.92 djmFix->setMidiSender(this);
1.93 }
1.94
2.1 --- a/DJMFix.cpp Fri May 09 23:17:36 2025 +0200
2.2 +++ b/DJMFix.cpp Sun May 11 00:30:03 2025 +0200
2.3 @@ -23,8 +23,10 @@
2.4 #include <chrono>
2.5 #include <stdexcept>
2.6 #include <vector>
2.7 +#include <regex>
2.8
2.9 #include "DJMFix.h"
2.10 +#include "MessageCodec.h"
2.11
2.12 namespace djmfix {
2.13
2.14 @@ -36,6 +38,7 @@
2.15 private:
2.16 MidiSender* midiSender;
2.17 djmfix::logging::Logger* logger;
2.18 + MessageCodec codec;
2.19 const int keepAliveInterval = 200;
2.20 int keepAliveCounter = 0;
2.21 std::thread keepAliveThread;
2.22 @@ -43,14 +46,31 @@
2.23 std::atomic<bool> running{false};
2.24 std::atomic<bool> stopped{false};
2.25 std::atomic<bool> sendKeepAlive{false};
2.26 + /**
2.27 + * Device (V10) may send multiple greeting messages.
2.28 + * It works even if we respond multiple times. But one response is enough.
2.29 + */
2.30 + std::atomic<bool> greetingReceived{false};
2.31 +
2.32 + Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
2.33 + Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
2.34 Bytes seed2;
2.35 + Bytes seed3;
2.36 +
2.37 + Bytes name1 = {0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a};
2.38 + Bytes name2 = {0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78};
2.39 +
2.40 + Bytes hash1;
2.41 + Bytes hash2;
2.42 +
2.43 + uint8_t version = 0x17;
2.44
2.45 void run() {
2.46 while (!stopped) {
2.47 logger->log(L::FINE, "DJMFixImpl::run()");
2.48 if (sendKeepAlive) send({
2.49 0xf0, 0x00, 0x40, 0x05,
2.50 - 0x00, 0x00, 0x00, 0x17,
2.51 + 0x00, 0x00, 0x00, version,
2.52 0x00, 0x50, 0x01, 0xf7
2.53 });
2.54 std::this_thread::sleep_for(chro::milliseconds(keepAliveInterval));
2.55 @@ -62,6 +82,11 @@
2.56 }
2.57 }
2.58
2.59 + void send(const Message& msg) {
2.60 + logger->log(L::FINE, "<!-- Sent message: -->" + msg.toString());
2.61 + send(codec.encode(msg));
2.62 + }
2.63 +
2.64 void send(const MidiMessage& midiMessage) {
2.65 std::lock_guard<std::recursive_mutex> lock(midiMutex);
2.66 midiSender->send(midiMessage);
2.67 @@ -150,77 +175,103 @@
2.68 if (running) stop();
2.69 }
2.70
2.71 - void setMidiSender(MidiSender* midiSender) {
2.72 + void setDeviceName(std::string name) override {
2.73 + logger->log(L::FINE, "DJMFixImpl::setDeviceName(" + name + ")");
2.74 +
2.75 + std::regex djm250pattern("Pioneer DJ Corporation DJM-250MK2.*");
2.76 + std::regex djm450pattern(".*450.*"); // TODO: correct pattern
2.77 +
2.78 + if (std::regex_match(name, djm250pattern)) {
2.79 + // DJM-250MK2:
2.80 + version = 0x17;
2.81 + seed3 = {
2.82 + 0x59, 0xb5, 0x4b, 0xfe, 0xe4,
2.83 + 0x4a, 0x5a, 0xc8, 0xe4, 0xc5
2.84 + };
2.85 + logger->log(L::FINE, "Switched to DJM-250MK2 mode");
2.86 + } else if (std::regex_match(name, djm450pattern)) {
2.87 + // DJM-450:
2.88 + // DJM-450 - not tested yet:
2.89 + version = 0x13;
2.90 + seed0 = {0x8c, 0x5b, 0x3f, 0x5d};
2.91 + seed3 = {
2.92 + 0x08, 0xef, 0x3f, 0x2f, 0x1e,
2.93 + 0x7a, 0x90, 0x17, 0xf6, 0xaf
2.94 + };
2.95 + logger->log(L::FINE, "Switched to DJM-450 mode");
2.96 + } else {
2.97 + // DJM-V10:
2.98 + version = 0x34;
2.99 + seed3 = {
2.100 + 0x70, 0x01, 0x4d, 0x05, 0xbe,
2.101 + 0xf2, 0xe4, 0xde, 0x60, 0xd6
2.102 + };
2.103 + logger->log(L::FINE, "Switched to DJM-V10 mode");
2.104 + }
2.105 + }
2.106 +
2.107 + void setMidiSender(MidiSender* midiSender) override {
2.108 logger->log(L::FINER, "DJMFixImpl::setMidiSender()");
2.109 this->midiSender = midiSender;
2.110 }
2.111
2.112 virtual void receive(const MidiMessage& msg) override {
2.113 + // TODO: remove try/catch - there should be no unknown messages
2.114 + try {
2.115 + receive0(msg);
2.116 + } catch (const std::exception& e) {
2.117 + logger->log(L::SEVERE,
2.118 + std::string("Message receiving failed: ") + e.what());
2.119 + }
2.120 + }
2.121 +
2.122 + virtual void receive0(const MidiMessage& msg) {
2.123 logger->log(L::FINE, "Received a message: "
2.124 "size = " + std::to_string(msg.size()) + " "
2.125 "data = " + toString(msg));
2.126 std::lock_guard<std::recursive_mutex> lock(midiMutex);
2.127 + Message msgIn = codec.decode(msg);
2.128
2.129 + logger->log(L::FINE, "<!-- Received message: -->" + msgIn.toString());
2.130
2.131 - if (msg.size() == 12 && msg[9] == 0x11) {
2.132 + if (msgIn.type == MessageType::D11_GREETING && !greetingReceived) {
2.133 logger->log(L::INFO, "Received greeting message.");
2.134 - send({
2.135 - 0xf0, 0x00, 0x40, 0x05,
2.136 - 0x00, 0x00, 0x00, 0x17,
2.137 - 0x00, 0x12, 0x2a, 0x01,
2.138 - 0x0b, 0x50, 0x69, 0x6f,
2.139 - 0x6e, 0x65, 0x65, 0x72,
2.140 - 0x44, 0x4a, 0x02, 0x0b,
2.141 - 0x72, 0x65, 0x6b, 0x6f,
2.142 - 0x72, 0x64, 0x62, 0x6f,
2.143 - 0x78, 0x03, 0x12, 0x02,
2.144 - 0x09, 0x00, 0x00, 0x00,
2.145 - 0x00, 0x00, 0x00, 0x02,
2.146 - 0x03, 0x04, 0x08, 0x00,
2.147 - 0x00, 0x00, 0x00, 0xf7
2.148 + Message msgOut(MessageType::H12_SEED1, version,{
2.149 + {FieldType::F01, name1},
2.150 + {FieldType::F02, name2},
2.151 + {FieldType::F03, denormalize(seed1)}
2.152 });
2.153 + send(msgOut);
2.154 + greetingReceived = true;
2.155 logger->log(L::INFO, "Sent message with seed1.");
2.156 - } else if (msg.size() == 54
2.157 - && msg[9] == 0x13
2.158 - && msg[33] == 0x04
2.159 - && msg[43] == 0x03) //
2.160 - {
2.161 - Bytes hash1(msg.begin() + 35, msg.begin() + 35 + 8);
2.162 - seed2 = Bytes(msg.begin() + 45, msg.begin() + 45 + 8);
2.163 - hash1 = normalize(hash1);
2.164 - seed2 = normalize(seed2);
2.165 + } else if (msgIn.type == MessageType::D13_HASH1_SEED2) {
2.166 + std::vector<Field> hash1F = msgIn.findFields(FieldType::F04);
2.167 + std::vector<Field> seed2F = msgIn.findFields(FieldType::F03);
2.168 +
2.169 + if (hash1F.empty()) throw std::logic_error("hash1 not found");
2.170 + if (seed2F.empty()) throw std::logic_error("seed2 not found");
2.171 +
2.172 + hash1 = normalize(hash1F[0].data);
2.173 + seed2 = normalize(seed2F[0].data);
2.174 +
2.175 logger->log(L::INFO, "Received message with "
2.176 "hash1 = " + toString(hash1) + " and "
2.177 "seed2 = " + toString(seed2));
2.178
2.179 - Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
2.180 - Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
2.181 -
2.182 Bytes hash1check =
2.183 toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
2.184
2.185 if (equals(hash1, hash1check)) {
2.186 logger->log(L::INFO, "Verification of hash1 was successful.");
2.187 - Bytes hash2 =
2.188 - toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
2.189 - send(concat({
2.190 - 0xf0, 0x00, 0x40, 0x05,
2.191 - 0x00, 0x00, 0x00, 0x17,
2.192 - 0x00, 0x14, 0x38, 0x01,
2.193 - 0x0b, 0x50, 0x69, 0x6f,
2.194 - 0x6e, 0x65, 0x65, 0x72,
2.195 - 0x44, 0x4a, 0x02, 0x0b,
2.196 - 0x72, 0x65, 0x6b, 0x6f,
2.197 - 0x72, 0x64, 0x62, 0x6f,
2.198 - 0x78, 0x04, 0x0a
2.199 - }, concat(denormalize(hash2),{
2.200 - 0x05, 0x16, 0x05, 0x09,
2.201 - 0x0b, 0x05, 0x04, 0x0b,
2.202 - 0x0f, 0x0e, 0x0e, 0x04,
2.203 - 0x04, 0x0a, 0x05, 0x0a,
2.204 - 0x0c, 0x08, 0x0e, 0x04,
2.205 - 0x0c, 0x05, 0xf7
2.206 - })));
2.207 + hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
2.208 +
2.209 + Message msgOut(MessageType::H14_HASH2, version,{
2.210 + {FieldType::F01, name1},
2.211 + {FieldType::F02, name2},
2.212 + {FieldType::F04, denormalize(hash2)},
2.213 + {FieldType::F05, denormalize(seed3)}
2.214 + });
2.215 + send(msgOut);
2.216 logger->log(L::INFO, "Sent message with hash2.");
2.217 } else {
2.218 std::stringstream logMessage;
2.219 @@ -235,11 +286,13 @@
2.220 logger->log(L::SEVERE, logMessage.str());
2.221 // TODO: graceful death
2.222 }
2.223 - } else if (msg.size() == 12 && msg[9] == 0x15) {
2.224 + } else if (msgIn.type == MessageType::D15_CONFIRMATION) {
2.225 sendKeepAlive = true;
2.226 logger->log(L::INFO, "Received acknowledgment message. "
2.227 "Started sending keep-alive messages. "
2.228 "LINE/PHONO channels should work now.");
2.229 + } else {
2.230 + logger->log(L::SEVERE, "Received unexpected message type.");
2.231 }
2.232
2.233 }
2.234 @@ -249,12 +302,18 @@
2.235 if (midiSender == nullptr)
2.236 throw std::logic_error("Need a midiSender when starting DJMFix");
2.237
2.238 - // TODO: methods for parsing and constructing messages from parts (TLV)
2.239 send({
2.240 0xf0, 0x00, 0x40, 0x05,
2.241 - 0x00, 0x00, 0x00, 0x17,
2.242 + 0x00, 0x00, 0x00, version,
2.243 0x00, 0x50, 0x01, 0xf7
2.244 });
2.245 +
2.246 + // TODO: check whether this second message is neccessary for V10:
2.247 + send({
2.248 + 0xf0, 0x00, 0x40, 0x05,
2.249 + 0x00, 0x00, 0x00, version,
2.250 + 0x00, 0x03, 0x01, 0xf7
2.251 + });
2.252 logger->log(L::INFO, "Sent greeting message.");
2.253
2.254 keepAliveThread = std::thread(&DJMFixImpl::run, this);
3.1 --- a/DJMFix.h Fri May 09 23:17:36 2025 +0200
3.2 +++ b/DJMFix.h Sun May 11 00:30:03 2025 +0200
3.3 @@ -17,6 +17,7 @@
3.4 #pragma once
3.5
3.6 #include <vector>
3.7 +#include <string>
3.8
3.9 #include "Logger.h"
3.10
3.11 @@ -33,6 +34,7 @@
3.12 class DJMFix {
3.13 public:
3.14 virtual ~DJMFix() = default;
3.15 + virtual void setDeviceName(std::string name) = 0;
3.16 virtual void setMidiSender(MidiSender* midiSender) = 0;
3.17 virtual void receive(const MidiMessage& midiMessage) = 0;
3.18 virtual void start() = 0;
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/Message.cpp Sun May 11 00:30:03 2025 +0200
4.3 @@ -0,0 +1,44 @@
4.4 +/**
4.5 + * djm-fix
4.6 + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
4.7 + *
4.8 + * This program is free software: you can redistribute it and/or modify
4.9 + * it under the terms of the GNU General Public License as published by
4.10 + * the Free Software Foundation, version 3 of the License.
4.11 + *
4.12 + * This program is distributed in the hope that it will be useful,
4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4.15 + * GNU General Public License for more details.
4.16 + *
4.17 + * You should have received a copy of the GNU General Public License
4.18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
4.19 + */
4.20 +
4.21 +#include <sstream>
4.22 +#include <iomanip>
4.23 +
4.24 +#include "Message.h"
4.25 +
4.26 +std::string Message::toString() const {
4.27 + std::stringstream s;
4.28 + s << "<message";
4.29 + s << " type='" << std::hex << ((int) type) << "'";
4.30 + s << " version='" << std::hex << ((int) version) << "'";
4.31 + s << ">";
4.32 + for (const auto& field : fields) {
4.33 + s << "<field type='" << std::hex << ((int) field.type) << "'>";
4.34 + for (uint8_t b : field.data) {
4.35 + s << std::hex << std::setw(2) << std::setfill('0') << (int) b;
4.36 + }
4.37 + s << "</field>";
4.38 + }
4.39 + s << "</message>";
4.40 + return s.str();
4.41 +}
4.42 +
4.43 +std::vector<Field> Message::findFields(FieldType type) {
4.44 + std::vector<Field> found;
4.45 + for (Field f : fields) if (f.type == type) found.push_back(f);
4.46 + return found;
4.47 +}
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/Message.h Sun May 11 00:30:03 2025 +0200
5.3 @@ -0,0 +1,88 @@
5.4 +/**
5.5 + * djm-fix
5.6 + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
5.7 + *
5.8 + * This program is free software: you can redistribute it and/or modify
5.9 + * it under the terms of the GNU General Public License as published by
5.10 + * the Free Software Foundation, version 3 of the License.
5.11 + *
5.12 + * This program is distributed in the hope that it will be useful,
5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.15 + * GNU General Public License for more details.
5.16 + *
5.17 + * You should have received a copy of the GNU General Public License
5.18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
5.19 + */
5.20 +
5.21 +#pragma once
5.22 +
5.23 +#include <stdint.h>
5.24 +#include <vector>
5.25 +#include <string>
5.26 +#include <ostream>
5.27 +
5.28 +enum class MessageType : uint8_t {
5.29 + /** device sends: greeting message */
5.30 + D11_GREETING = 0x11,
5.31 +
5.32 + /** host sends: seed1 */
5.33 + H12_SEED1 = 0x12,
5.34 +
5.35 + /** device sends: hash1 and seed2 */
5.36 + D13_HASH1_SEED2 = 0x13,
5.37 +
5.38 + /** host sends: hash2 */
5.39 + H14_HASH2 = 0x14,
5.40 +
5.41 + /** device sends: confirmation of successful handshake */
5.42 + D15_CONFIRMATION = 0x15,
5.43 +};
5.44 +
5.45 +enum class FieldType : uint8_t {
5.46 + /** manufacturer name */
5.47 + F01 = 0x01,
5.48 + /** product name */
5.49 + F02 = 0x02,
5.50 + /** seed1 from host | seed2 from device */
5.51 + F03 = 0x03,
5.52 + /** hash2 from host | hash1 from device */
5.53 + F04 = 0x04,
5.54 + /** seed3 from device */
5.55 + F05 = 0x05,
5.56 +};
5.57 +
5.58 +class Field {
5.59 +public:
5.60 + FieldType type;
5.61 + std::vector<uint8_t> data;
5.62 +
5.63 + Field(FieldType type, std::vector<uint8_t> data) : type(type), data(data) {
5.64 + }
5.65 +
5.66 + virtual ~Field() = default;
5.67 +};
5.68 +
5.69 +/**
5.70 + * Object representation of a raw MIDI message.
5.71 + * Is either a result of parsing a raw message by MessageCodec, or constructed
5.72 + * in the application to be serialized in MessageCodec to a raw message.
5.73 + */
5.74 +class Message {
5.75 +public:
5.76 + MessageType type;
5.77 + /** 0x17 for DJM-250MK2 and 0x34 for V10 (maybe not a version) */
5.78 + uint8_t version;
5.79 + std::vector<Field> fields;
5.80 +
5.81 + Message(MessageType type, uint8_t version, std::vector<Field> fields) :
5.82 + type(type), version(version), fields(fields) {
5.83 + }
5.84 +
5.85 + virtual ~Message() = default;
5.86 +
5.87 + std::string toString() const;
5.88 +
5.89 + std::vector<Field> findFields(FieldType type);
5.90 +
5.91 +};
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/MessageCodec.cpp Sun May 11 00:30:03 2025 +0200
6.3 @@ -0,0 +1,118 @@
6.4 +/**
6.5 + * djm-fix
6.6 + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
6.7 + *
6.8 + * This program is free software: you can redistribute it and/or modify
6.9 + * it under the terms of the GNU General Public License as published by
6.10 + * the Free Software Foundation, version 3 of the License.
6.11 + *
6.12 + * This program is distributed in the hope that it will be useful,
6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6.15 + * GNU General Public License for more details.
6.16 + *
6.17 + * You should have received a copy of the GNU General Public License
6.18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
6.19 + */
6.20 +
6.21 +#include <iostream>
6.22 +#include <iomanip>
6.23 +
6.24 +#include "MessageCodec.h"
6.25 +
6.26 +Message MessageCodec::decode(std::vector<uint8_t> data) {
6.27 + using ERR = std::invalid_argument;
6.28 +
6.29 + if (data.empty() || data.front() != 0xf0 || data.back() != 0xf7)
6.30 + throw ERR("not a MIDI SysEx message");
6.31 +
6.32 + // Manufacturer MIDI SysEx ID Numbers:
6.33 + // 00H 40H 05H = AlphaTheta Corporation
6.34 + // 00H 40H 06H = Pioneer Corporation
6.35 + if (data.size() < 4
6.36 + || data[1] != 0x00
6.37 + || data[2] != 0x40
6.38 + || data[3] != 0x05)
6.39 + throw ERR("wrong message Manufacturer MIDI SysEx ID");
6.40 +
6.41 + if (data.size() < 8) throw ERR("missing message version");
6.42 +
6.43 + uint8_t msgVersion = data[7];
6.44 +
6.45 + if (data.size() < 10) throw ERR("missing message type");
6.46 +
6.47 + MessageType msgType;
6.48 + if /**/ (data[9] == 0x11) msgType = MessageType::D11_GREETING;
6.49 + else if (data[9] == 0x12) msgType = MessageType::H12_SEED1;
6.50 + else if (data[9] == 0x13) msgType = MessageType::D13_HASH1_SEED2;
6.51 + else if (data[9] == 0x14) msgType = MessageType::H14_HASH2;
6.52 + else if (data[9] == 0x15) msgType = MessageType::D15_CONFIRMATION;
6.53 + else throw ERR("unsupported message type");
6.54 +
6.55 + if (data.size() < 11) throw ERR("missing message length");
6.56 + if (data[10] != data.size() - 10) throw ERR("wrong message length");
6.57 +
6.58 + std::vector<Field> fields;
6.59 +
6.60 + for (size_t start = 11; data.size() > start + 1;) {
6.61 + FieldType fieldType;
6.62 + if /**/ (data[start] == 0x01) fieldType = FieldType::F01;
6.63 + else if (data[start] == 0x02) fieldType = FieldType::F02;
6.64 + else if (data[start] == 0x03) fieldType = FieldType::F03;
6.65 + else if (data[start] == 0x04) fieldType = FieldType::F04;
6.66 + else if (data[start] == 0x05) fieldType = FieldType::F05;
6.67 + else throw ERR("unsupported field type");
6.68 +
6.69 + size_t fieldSize = data[start + 1];
6.70 +
6.71 + if (data.size() < start + fieldSize + 1) throw ERR("field overflow");
6.72 + // ^ 0xf7 messsage end
6.73 +
6.74 + std::vector<uint8_t> fieldData(
6.75 + data.begin() + start + 2,
6.76 + data.begin() + start + fieldSize);
6.77 +
6.78 + fields.push_back({fieldType, fieldData});
6.79 +
6.80 + start += fieldSize;
6.81 + }
6.82 +
6.83 + return Message(msgType, msgVersion, fields);
6.84 +}
6.85 +
6.86 +std::vector<uint8_t> MessageCodec::encode(Message msg) {
6.87 + using ERR = std::invalid_argument;
6.88 +
6.89 + std::vector<uint8_t> data;
6.90 +
6.91 + data.push_back(0xf0);
6.92 +
6.93 + data.push_back(0x00);
6.94 + data.push_back(0x40);
6.95 + data.push_back(0x05);
6.96 +
6.97 + data.push_back(0x00);
6.98 + data.push_back(0x00);
6.99 + data.push_back(0x00);
6.100 +
6.101 + data.push_back(msg.version);
6.102 + data.push_back(0x00);
6.103 + data.push_back((uint8_t) msg.type);
6.104 +
6.105 + uint8_t msgLength = 2; // 2 = type + lenght
6.106 + for (const auto& field : msg.fields) msgLength += field.data.size() + 2;
6.107 +
6.108 + data.push_back(msgLength);
6.109 +
6.110 + for (const auto& field : msg.fields) {
6.111 + size_t fieldSize = field.data.size() + 2;
6.112 + if (fieldSize > 256) throw ERR("message field too long");
6.113 + data.push_back((uint8_t) field.type);
6.114 + data.push_back((uint8_t) (fieldSize));
6.115 + data.insert(data.end(), field.data.begin(), field.data.end());
6.116 + }
6.117 +
6.118 + data.push_back(0xf7);
6.119 +
6.120 + return data;
6.121 +}
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/MessageCodec.h Sun May 11 00:30:03 2025 +0200
7.3 @@ -0,0 +1,29 @@
7.4 +/**
7.5 + * djm-fix
7.6 + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
7.7 + *
7.8 + * This program is free software: you can redistribute it and/or modify
7.9 + * it under the terms of the GNU General Public License as published by
7.10 + * the Free Software Foundation, version 3 of the License.
7.11 + *
7.12 + * This program is distributed in the hope that it will be useful,
7.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
7.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7.15 + * GNU General Public License for more details.
7.16 + *
7.17 + * You should have received a copy of the GNU General Public License
7.18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
7.19 + */
7.20 +
7.21 +#pragma once
7.22 +
7.23 +#include <vector>
7.24 +#include <stdint.h>
7.25 +
7.26 +#include "Message.h"
7.27 +
7.28 +class MessageCodec {
7.29 +public:
7.30 + Message decode(std::vector<uint8_t> data);
7.31 + std::vector<uint8_t> encode(Message msg);
7.32 +};
7.33 \ No newline at end of file
8.1 --- a/djm-fix.cpp Fri May 09 23:17:36 2025 +0200
8.2 +++ b/djm-fix.cpp Sun May 11 00:30:03 2025 +0200
8.3 @@ -56,7 +56,7 @@
8.4 * channels. And this is the purpose of the djm-fix utility and it is done by
8.5 * sending some magic packet to the mixer.
8.6 *
8.7 - * Implementation of this magic in the AlsaBridge.cpp file is based on publicly
8.8 + * Implementation of this magic in the DJMFix.cpp file is based on publicly
8.9 * available documentation that can be found at:
8.10 * - https://swiftb0y.github.io/CDJHidProtocol/hid-analysis/handshake.html
8.11 * - https://mixb.me/CDJHidProtocol/hid-analysis/handshake.html (formerly).
8.12 @@ -90,6 +90,7 @@
8.13 *
8.14 * Look for updates in the Mercurial repositories and at:
8.15 * - https://blog.frantovo.cz/c/387/
8.16 + * - https://blog.frantovo.cz/c/396/
8.17 */
8.18
8.19 int main(int argc, char**argv) {
8.20 @@ -98,7 +99,9 @@
8.21 logger(djmfix::logging::create(std::cerr, L::INFO));
8.22 try {
8.23 logger->log(L::INFO, "DJM-Fix started.");
8.24 - std::string cardNamePattern = argc == 2 ? argv[1] : "Pioneer DJ.*";
8.25 + std::string cardNamePattern = argc == 2
8.26 + ? argv[1]
8.27 + : "(Pioneer DJ|AlphaTheta).*";
8.28
8.29 signal(SIGINT, interrupt);
8.30 std::unique_ptr<djmfix::DJMFix> djmFix(djmfix::create(logger.get()));
9.1 --- a/nbproject/configurations.xml Fri May 09 23:17:36 2025 +0200
9.2 +++ b/nbproject/configurations.xml Sun May 11 00:30:03 2025 +0200
9.3 @@ -5,6 +5,8 @@
9.4 <in>AlsaBridge.cpp</in>
9.5 <in>DJMFix.cpp</in>
9.6 <in>Logger.cpp</in>
9.7 + <in>Message.cpp</in>
9.8 + <in>MessageCodec.cpp</in>
9.9 <in>djm-fix.cpp</in>
9.10 </df>
9.11 <logicalFolder name="ExternalFiles"
9.12 @@ -28,7 +30,7 @@
9.13 <rebuildPropChanged>false</rebuildPropChanged>
9.14 </toolsSet>
9.15 <flagsDictionary>
9.16 - <element flagsID="0" commonFlags="-fsanitize=address"/>
9.17 + <element flagsID="0" commonFlags="-O2 -std=c++20"/>
9.18 </flagsDictionary>
9.19 <codeAssistance>
9.20 </codeAssistance>
9.21 @@ -56,6 +58,14 @@
9.22 <ccTool flags="0">
9.23 </ccTool>
9.24 </item>
9.25 + <item path="Message.cpp" ex="false" tool="1" flavor2="0">
9.26 + <ccTool flags="0">
9.27 + </ccTool>
9.28 + </item>
9.29 + <item path="MessageCodec.cpp" ex="false" tool="1" flavor2="0">
9.30 + <ccTool flags="0">
9.31 + </ccTool>
9.32 + </item>
9.33 <item path="djm-fix.cpp" ex="false" tool="1" flavor2="0">
9.34 <ccTool flags="0">
9.35 </ccTool>