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()");
71 if (sendKeepAlive) send({
72 0xf0, 0x00, 0x40, 0x05,
73 0x00, 0x00, 0x00, model,
74 0x00, 0x50, 0x01, 0xf7
76 std::this_thread::sleep_for(chro::milliseconds(keepAliveInterval));
78 if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0)
80 "Still sending periodic keep-alive messages "
81 "(each " + std::to_string(keepAliveInterval) + " ms).");
85 void send(const Message& msg) {
86 logger->log(L::FINE, "<!-- Sent message: -->" + msg.toString());
87 send(codec.encode(msg));
90 void send(const MidiMessage& midiMessage) {
91 std::lock_guard<std::recursive_mutex> lock(midiMutex);
92 midiSender->send(midiMessage);
95 std::string toString(const Bytes& midiMessage) {
96 std::stringstream result;
97 for (uint8_t b : midiMessage)
98 result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
102 Bytes normalize(const Bytes& data) {
103 if (data.size() % 2) throw std::invalid_argument(
104 "Data before normalization must have even number of bytes.");
106 result.reserve(data.size() / 2);
107 for (size_t i = 0; i < data.size() / 2; i++) result.push_back(
108 (data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F));
112 Bytes denormalize(const Bytes& data) {
114 result.reserve(data.size()*2);
115 for (size_t i = 0; i < data.size(); i++) {
116 result.push_back(data[i] >> 4);
117 result.push_back(data[i] & 0x0F);
122 uint32_t fnv32hash(const Bytes& buff) {
123 uint32_t hash = 0x811c9dc5;
124 for (uint8_t b : buff) hash = ((b^hash) * 0x1000193);
128 Bytes toBytes(const uint32_t value) {
131 result.push_back(value >> 24);
132 result.push_back(value >> 16);
133 result.push_back(value >> 8);
134 result.push_back(value >> 0);
138 bool equals(Bytes a, Bytes b) {
139 if (a.size() != b.size()) return false;
140 for (size_t i = 0; i < a.size(); i++) if (a[i] != b[i]) return false;
144 template<typename T> std::vector<T> concat(
145 const std::vector<T>& a,
146 const std::vector<T>& b,
147 const std::vector<T>& c = {}) //
149 std::vector<T> result;
150 result.reserve(a.size() + b.size() + c.size());
151 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i]);
152 for (size_t i = 0; i < b.size(); i++) result.push_back(b[i]);
153 for (size_t i = 0; i < c.size(); i++) result.push_back(c[i]);
158 std::vector<T> xOR(const std::vector<T>& a, const std::vector<T>& b) {
159 if (a.size() != b.size()) throw std::invalid_argument(
160 "Both must be the same length when doing XOR.");
161 std::vector<T> result;
162 result.reserve(a.size());
163 for (size_t i = 0; i < a.size(); i++) result.push_back(a[i] ^ b[i]);
169 DJMFixImpl(djmfix::logging::Logger* logger)
170 : logger(logger ? logger : djmfix::logging::blackhole()) {
173 virtual ~DJMFixImpl() override {
174 logger->log(L::FINER, "~DJMFixImpl()");
178 void setDeviceName(std::string name) override {
179 logger->log(L::FINE, "DJMFixImpl::setDeviceName(" + name + ")");
181 std::regex djm250pattern("Pioneer DJ Corporation DJM-250MK2.*");
182 std::regex djm450pattern("Pioneer DJ Corporation DJM-450.*");
184 if (std::regex_match(name, djm250pattern)) {
188 0x59, 0xb5, 0x4b, 0xfe, 0xe4,
189 0x4a, 0x5a, 0xc8, 0xe4, 0xc5
191 logger->log(L::FINE, "Switched to DJM-250MK2 mode");
192 } else if (std::regex_match(name, djm450pattern)) {
194 // DJM-450 - not tested yet:
196 seed0 = {0x8c, 0x5b, 0x3f, 0x5d};
198 0x08, 0xef, 0x3f, 0x2f, 0x1e,
199 0x7a, 0x90, 0x17, 0xf6, 0xaf
201 logger->log(L::FINE, "Switched to DJM-450 mode");
206 0x70, 0x01, 0x4d, 0x05, 0xbe,
207 0xf2, 0xe4, 0xde, 0x60, 0xd6
209 logger->log(L::FINE, "Switched to DJM-V10 mode");
213 void setMidiSender(MidiSender* midiSender) override {
214 logger->log(L::FINER, "DJMFixImpl::setMidiSender()");
215 this->midiSender = midiSender;
218 virtual void receive(const MidiMessage& msg) override {
219 // TODO: remove try/catch - there should be no unknown messages
222 } catch (const std::exception& e) {
223 logger->log(L::SEVERE,
224 std::string("Message receiving failed: ") + e.what());
228 virtual void receive0(const MidiMessage& msg) {
229 logger->log(L::FINE, "Received a message: "
230 "size = " + std::to_string(msg.size()) + " "
231 "data = " + toString(msg));
232 std::lock_guard<std::recursive_mutex> lock(midiMutex);
233 Message msgIn = codec.decode(msg);
235 logger->log(L::FINE, "<!-- Received message: -->" + msgIn.toString());
237 if (msgIn.type == MessageType::D11_GREETING && !greetingReceived) {
238 logger->log(L::INFO, "Received greeting message.");
239 Message msgOut(MessageType::H12_SEED1, model,{
240 {FieldType::F01, name1},
241 {FieldType::F02, name2},
242 {FieldType::F03, denormalize(seed1)}
245 greetingReceived = true;
246 logger->log(L::INFO, "Sent message with seed1.");
247 } else if (msgIn.type == MessageType::D13_HASH1_SEED2) {
248 std::vector<Field> hash1F = msgIn.findFields(FieldType::F04);
249 std::vector<Field> seed2F = msgIn.findFields(FieldType::F03);
251 if (hash1F.empty()) throw std::logic_error("hash1 not found");
252 if (seed2F.empty()) throw std::logic_error("seed2 not found");
254 hash1 = normalize(hash1F[0].data);
255 seed2 = normalize(seed2F[0].data);
257 logger->log(L::INFO, "Received message with "
258 "hash1 = " + toString(hash1) + " and "
259 "seed2 = " + toString(seed2));
262 toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
264 if (equals(hash1, hash1check)) {
265 logger->log(L::INFO, "Verification of hash1 was successful.");
266 hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
268 Message msgOut(MessageType::H14_HASH2, model,{
269 {FieldType::F01, name1},
270 {FieldType::F02, name2},
271 {FieldType::F04, denormalize(hash2)},
272 {FieldType::F05, denormalize(seed3)}
275 logger->log(L::INFO, "Sent message with hash2.");
277 std::stringstream logMessage;
279 << "Verification of hash1 failed: "
280 << " midiMessage = " << toString(msg)
281 << " seed0 = " << toString(seed0)
282 << " seed1 = " << toString(seed1)
283 << " seed2 = " << toString(seed2)
284 << " hash1 = " << toString(hash1)
285 << " hash1check = " << toString(hash1check);
286 logger->log(L::SEVERE, logMessage.str());
287 // TODO: graceful death
289 } else if (msgIn.type == MessageType::D15_CONFIRMATION) {
290 sendKeepAlive = true;
291 logger->log(L::INFO, "Received acknowledgment message. "
292 "Started sending keep-alive messages. "
293 "LINE/PHONO channels should work now.");
295 logger->log(L::SEVERE, "Received unexpected message type.");
300 void start() override {
301 logger->log(L::FINE, "DJMFixImpl::start()");
302 if (midiSender == nullptr)
303 throw std::logic_error("Need a midiSender when starting DJMFix");
306 0xf0, 0x00, 0x40, 0x05,
307 0x00, 0x00, 0x00, model,
308 0x00, 0x50, 0x01, 0xf7
311 // TODO: check whether this second message is neccessary for V10:
313 0xf0, 0x00, 0x40, 0x05,
314 0x00, 0x00, 0x00, model,
315 0x00, 0x03, 0x01, 0xf7
317 logger->log(L::INFO, "Sent greeting message.");
319 keepAliveThread = std::thread(&DJMFixImpl::run, this);
324 void stop() override {
326 keepAliveThread.join();
328 logger->log(L::FINE, "DJMFixImpl::stop()");
332 DJMFix* create(djmfix::logging::Logger* logger) {
333 return new DJMFixImpl(logger);