support Pioneer DJ DJM-250MK2 and DJM-V10 v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 11 May 2025 00:30:03 +0200
branchv_0
changeset 18358a601bfe81
parent 17 aa7dc7faf1bb
child 19 4ed672cecc25
support Pioneer DJ DJM-250MK2 and DJM-V10
AlsaBridge.cpp
DJMFix.cpp
DJMFix.h
Message.cpp
Message.h
MessageCodec.cpp
MessageCodec.h
djm-fix.cpp
nbproject/configurations.xml
     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>