3 * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 #include "MessageCodec.h"
33 using L = djmfix::logging::Level;
34 using Bytes = std::vector<uint8_t>;
35 namespace chro = std::chrono;
37 class DJMFixImpl : public DJMFix {
39 MidiSender* midiSender;
40 djmfix::logging::Logger* logger;
42 const int keepAliveInterval = 200;
43 int keepAliveCounter = 0;
44 std::thread keepAliveThread;
45 std::recursive_mutex midiMutex;
46 std::atomic<bool> running{false};
47 std::atomic<bool> stopped{false};
48 std::atomic<bool> sendKeepAlive{false};
50 * Device (V10) may send multiple greeting messages.
51 * It works even if we respond multiple times. But one response is enough.
53 std::atomic<bool> greetingReceived{false};
55 Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
56 Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
60 Bytes name1 = {0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a};
61 Bytes name2 = {0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78};
70 logger->log(L::FINE, "DJMFixImpl::run()");
73 0xf0, 0x00, 0x40, 0x05,
74 0x00, 0x00, 0x00, model,
75 0x00, 0x50, 0x01, 0xf7
78 if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0)
80 "Still sending periodic keep-alive messages "
81 "(each " + std::to_string(keepAliveInterval) + " ms).");
83 std::this_thread::sleep_for(chro::milliseconds(keepAliveInterval));
87 void send(const Message& msg) {
88 logger->log(L::FINE, "<!-- Sent message: -->" + msg.toString());
89 send(codec.encode(msg));
92 void send(const MidiMessage& midiMessage) {
93 std::lock_guard<std::recursive_mutex> lock(midiMutex);
94 midiSender->send(midiMessage);
97 std::string toString(const Bytes& midiMessage) {
98 std::stringstream result;
99 for (uint8_t b : midiMessage)
100 result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
104 Bytes normalize(const Bytes& data) {
105 if (data.size() % 2) throw std::invalid_argument(
106 "Data before normalization must have even number of bytes.");
108 result.reserve(data.size() / 2);
109 for (size_t i = 0; i < data.size() / 2; i++) result.push_back(
110 (data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F));
114 Bytes denormalize(const Bytes& data) {
116 result.reserve(data.size()*2);
117 for (size_t i = 0; i < data.size(); i++) {
118 result.push_back(data[i] >> 4);
119 result.push_back(data[i] & 0x0F);
124 uint32_t fnv32hash(const Bytes& buff) {
125 uint32_t hash = 0x811c9dc5;
126 for (uint8_t b : buff) hash = ((b^hash) * 0x1000193);
130 Bytes toBytes(const uint32_t value) {
133 result.push_back(value >> 24);
134 result.push_back(value >> 16);
135 result.push_back(value >> 8);
136 result.push_back(value >> 0);
140 bool equals(Bytes a, Bytes b) {
141 if (a.size() != b.size()) return false;
142 for (size_t i = 0; i < a.size(); i++) if (a[i] != b[i]) return false;
146 template<typename T> std::vector<T> concat(
147 const std::vector<T>& a,
148 const std::vector<T>& b,
149 const std::vector<T>& c = {}) //
151 std::vector<T> result;
152 result.reserve(a.size() + b.size() + c.size());
153 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i]);
154 for (size_t i = 0; i < b.size(); i++) result.push_back(b[i]);
155 for (size_t i = 0; i < c.size(); i++) result.push_back(c[i]);
160 std::vector<T> xOR(const std::vector<T>& a, const std::vector<T>& b) {
161 if (a.size() != b.size()) throw std::invalid_argument(
162 "Both must be the same length when doing XOR.");
163 std::vector<T> result;
164 result.reserve(a.size());
165 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i] ^ b[i]);
171 DJMFixImpl(djmfix::logging::Logger* logger)
172 : logger(logger ? logger : djmfix::logging::blackhole()) {
175 virtual ~DJMFixImpl() override {
176 logger->log(L::FINER, "~DJMFixImpl()");
180 void setDeviceName(std::string name) override {
181 logger->log(L::FINE, "DJMFixImpl::setDeviceName(" + name + ")");
183 std::regex djm250pattern("Pioneer DJ Corporation DJM-250MK2.*");
184 std::regex djm450pattern("Pioneer DJ Corporation DJM-450.*");
186 if (std::regex_match(name, djm250pattern)) {
190 0x59, 0xb5, 0x4b, 0xfe, 0xe4,
191 0x4a, 0x5a, 0xc8, 0xe4, 0xc5
193 logger->log(L::FINE, "Switched to DJM-250MK2 mode");
194 } else if (std::regex_match(name, djm450pattern)) {
196 // DJM-450 - not tested yet:
199 0x99, 0xd5, 0x55, 0x43, 0x2c,
200 0x70, 0x53, 0x7a, 0x6f, 0x02
202 logger->log(L::FINE, "Switched to DJM-450 mode");
207 0x70, 0x01, 0x4d, 0x05, 0xbe,
208 0xf2, 0xe4, 0xde, 0x60, 0xd6
210 logger->log(L::FINE, "Switched to DJM-V10 mode");
214 void setMidiSender(MidiSender* midiSender) override {
215 logger->log(L::FINER, "DJMFixImpl::setMidiSender()");
216 this->midiSender = midiSender;
219 virtual void receive(const MidiMessage& msg) override {
220 // TODO: remove try/catch - there should be no unknown messages
223 } catch (const std::exception& e) {
224 logger->log(L::SEVERE,
225 std::string("Message receiving failed: ") + e.what());
229 virtual void receive0(const MidiMessage& msg) {
230 logger->log(L::FINE, "Received a message: "
231 "size = " + std::to_string(msg.size()) + " "
232 "data = " + toString(msg));
233 std::lock_guard<std::recursive_mutex> lock(midiMutex);
234 Message msgIn = codec.decode(msg);
236 logger->log(L::FINE, "<!-- Received message: -->" + msgIn.toString());
238 if (msgIn.type == MessageType::D11_GREETING && !greetingReceived) {
239 logger->log(L::INFO, "Received greeting message.");
240 Message msgOut(MessageType::H12_SEED1, model,{
241 {FieldType::F01, name1},
242 {FieldType::F02, name2},
243 {FieldType::F03, denormalize(seed1)}
246 greetingReceived = true;
247 logger->log(L::INFO, "Sent message with seed1.");
248 } else if (msgIn.type == MessageType::D13_HASH1_SEED2) {
249 std::vector<Field> hash1F = msgIn.findFields(FieldType::F04);
250 std::vector<Field> seed2F = msgIn.findFields(FieldType::F03);
252 if (hash1F.empty()) throw std::logic_error("hash1 not found");
253 if (seed2F.empty()) throw std::logic_error("seed2 not found");
255 hash1 = normalize(hash1F[0].data);
256 seed2 = normalize(seed2F[0].data);
258 logger->log(L::INFO, "Received message with "
259 "hash1 = " + toString(hash1) + " and "
260 "seed2 = " + toString(seed2));
263 toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
265 if (equals(hash1, hash1check)) {
266 logger->log(L::INFO, "Verification of hash1 was successful.");
267 hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
269 Message msgOut(MessageType::H14_HASH2, model,{
270 {FieldType::F01, name1},
271 {FieldType::F02, name2},
272 {FieldType::F04, denormalize(hash2)},
273 {FieldType::F05, denormalize(seed3)}
276 logger->log(L::INFO, "Sent message with hash2.");
278 std::stringstream logMessage;
280 << "Verification of hash1 failed: "
281 << " midiMessage = " << toString(msg)
282 << " seed0 = " << toString(seed0)
283 << " seed1 = " << toString(seed1)
284 << " seed2 = " << toString(seed2)
285 << " hash1 = " << toString(hash1)
286 << " hash1check = " << toString(hash1check);
287 logger->log(L::SEVERE, logMessage.str());
288 // TODO: graceful death
290 } else if (msgIn.type == MessageType::D15_CONFIRMATION) {
291 sendKeepAlive = true;
292 logger->log(L::INFO, "Received acknowledgment message. "
293 "Started sending keep-alive messages. "
294 "LINE/PHONO channels should work now.");
296 logger->log(L::SEVERE, "Received unexpected message type.");
301 void start() override {
302 logger->log(L::FINE, "DJMFixImpl::start()");
303 if (midiSender == nullptr)
304 throw std::logic_error("Need a midiSender when starting DJMFix");
307 0xf0, 0x00, 0x40, 0x05,
308 0x00, 0x00, 0x00, model,
309 0x00, 0x50, 0x01, 0xf7
312 // TODO: check whether this second message is neccessary for V10:
314 0xf0, 0x00, 0x40, 0x05,
315 0x00, 0x00, 0x00, model,
316 0x00, 0x03, 0x01, 0xf7
318 logger->log(L::INFO, "Sent greeting message.");
320 keepAliveThread = std::thread(&DJMFixImpl::run, this);
325 void stop() override {
327 keepAliveThread.join();
329 logger->log(L::FINE, "DJMFixImpl::stop()");
333 DJMFix* create(djmfix::logging::Logger* logger) {
334 return new DJMFixImpl(logger);