# HG changeset patch # User František Kučera # Date 1744749925 -7200 # Node ID 63154f9d24a2fab4b99cc1609ac0aeb19191568c # Parent 1c74985d4c4eac38b68c8a8704f8dc9b1a3115b0 code formatting diff -r 1c74985d4c4e -r 63154f9d24a2 AlsaBridge.cpp --- a/AlsaBridge.cpp Tue Apr 15 22:44:31 2025 +0200 +++ b/AlsaBridge.cpp Tue Apr 15 22:45:25 2025 +0200 @@ -1,6 +1,6 @@ /** * DJM-Fix - * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -67,18 +67,24 @@ } if (cardNumbers.size() == 1) { - logger->log(L::INFO, "Going to fix card #" + std::to_string(cardNumbers[0])); - return "hw:" + std::to_string(cardNumbers[0]); + const auto n = std::to_string(cardNumbers[0]); + logger->log(L::INFO, "Going to fix card #" + n); + return "hw:" + n; } else if (cardNumbers.empty()) { - throw std::invalid_argument("No card with matching name found. Is the card connected? Maybe try to provide different name pattern."); + throw std::invalid_argument( + "No card with matching name found. Is the card connected? " + "Maybe try to provide different name pattern."); } else { - throw std::invalid_argument("Multiple cards with matching name found. Please provide a name pattern that matches only one card"); + throw std::invalid_argument( + "Multiple cards with matching name found. " + "Please provide a name pattern that matches only one card"); } } std::string toString(const MidiMessage& midiMessage) { std::stringstream result; - for (uint8_t b : midiMessage) result << std::hex << std::setw(2) << std::setfill('0') << (int) b; + for (uint8_t b : midiMessage) + result << std::hex << std::setw(2) << std::setfill('0') << (int) b; return result.str(); } @@ -87,11 +93,11 @@ { std::lock_guard lock(midiMutex); // TODO: poll - uint8_t buffer[256]; - ssize_t length = snd_rawmidi_read(input, buffer, sizeof (buffer)); - if (length > 0 && length <= sizeof (buffer)) { + uint8_t buf[256]; + ssize_t length = snd_rawmidi_read(input, buf, sizeof (buf)); + if (length > 0 && length <= sizeof (buf)) { // TODO: multiple messages combined together? - djmFix->receive(MidiMessage(buffer, buffer + length)); + djmFix->receive(MidiMessage(buf, buf + length)); } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -99,15 +105,21 @@ } public: - AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern, djmfix::logging::Logger* logger) : djmFix(djmFix), logger(logger ? logger : djmfix::logging::blackhole()) { - if (djmFix == nullptr) throw std::invalid_argument("Need a djmFix for AlsaBridge."); + AlsaBridgeImpl( + djmfix::DJMFix* djmFix, + const std::string& cardNamePattern, + djmfix::logging::Logger* logger) + : djmFix(djmFix), logger(logger ? logger : djmfix::logging::blackhole()) // + { + if (djmFix == nullptr) + throw std::invalid_argument("Need a djmFix for AlsaBridge."); std::string deviceName = findDeviceName(std::regex(cardNamePattern)); - int error = snd_rawmidi_open(&input, &output, deviceName.c_str(), SND_RAWMIDI_NONBLOCK); + int mode = SND_RAWMIDI_NONBLOCK; + int error = snd_rawmidi_open(&input, &output, deviceName.c_str(), mode); if (error) throw std::invalid_argument("Unable to open ALSA device."); - djmFix->setMidiSender(this); } @@ -129,15 +141,21 @@ djmFix->stop(); } - virtual void send(MidiMessage midiMessage) override { + virtual void send(MidiMessage msg) override { std::lock_guard lock(midiMutex); - ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size()); - logger->log(L::FINE, "Sent message: length = " + std::to_string(length) + " data = " + toString(midiMessage)); + ssize_t length = snd_rawmidi_write(output, msg.data(), msg.size()); + logger->log(L::FINE, "Sent message:" + " length = " + std::to_string(length) + + " data = " + toString(msg)); } }; -AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName, djmfix::logging::Logger* logger) { +AlsaBridge* create( + djmfix::DJMFix* djmFix, + const std::string& deviceName, + djmfix::logging::Logger* logger) // +{ return new AlsaBridgeImpl(djmFix, deviceName, logger); } diff -r 1c74985d4c4e -r 63154f9d24a2 AlsaBridge.h --- a/AlsaBridge.h Tue Apr 15 22:44:31 2025 +0200 +++ b/AlsaBridge.h Tue Apr 15 22:45:25 2025 +0200 @@ -1,6 +1,6 @@ /** * DJM-Fix - * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,7 +32,10 @@ }; -AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& cardNamePattern, djmfix::logging::Logger* logger); +AlsaBridge* create( + djmfix::DJMFix* djmFix, + const std::string& cardNamePattern, + djmfix::logging::Logger* logger); } } diff -r 1c74985d4c4e -r 63154f9d24a2 DJMFix.cpp --- a/DJMFix.cpp Tue Apr 15 22:44:31 2025 +0200 +++ b/DJMFix.cpp Tue Apr 15 22:45:25 2025 +0200 @@ -1,6 +1,6 @@ /** * DJM-Fix - * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ using L = djmfix::logging::Level; using Bytes = std::vector; +namespace chro = std::chrono; class DJMFixImpl : public DJMFix { private: @@ -47,10 +48,17 @@ void run() { while (!stopped) { logger->log(L::FINE, "DJMFixImpl::run()"); - if (sendKeepAlive) send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7}); - std::this_thread::sleep_for(std::chrono::milliseconds(keepAliveInterval)); + if (sendKeepAlive) send({ + 0xf0, 0x00, 0x40, 0x05, + 0x00, 0x00, 0x00, 0x17, + 0x00, 0x50, 0x01, 0xf7 + }); + std::this_thread::sleep_for(chro::milliseconds(keepAliveInterval)); keepAliveCounter++; - if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0) logger->log(L::INFO, "Still sending periodic keep-alive messages (each " + std::to_string(keepAliveInterval) + " ms)."); + if (keepAliveCounter % (60 * 1000 / keepAliveInterval) == 0) + logger->log(L::INFO, + "Still sending periodic keep-alive messages " + "(each " + std::to_string(keepAliveInterval) + " ms)."); } } @@ -61,15 +69,18 @@ std::string toString(const Bytes& midiMessage) { std::stringstream result; - for (uint8_t b : midiMessage) result << std::hex << std::setw(2) << std::setfill('0') << (int) b; + for (uint8_t b : midiMessage) + result << std::hex << std::setw(2) << std::setfill('0') << (int) b; return result.str(); } Bytes normalize(const Bytes& data) { - if (data.size() % 2) throw std::invalid_argument("Data before normalization must have even number of bytes."); + if (data.size() % 2) throw std::invalid_argument( + "Data before normalization must have even number of bytes."); Bytes result; result.reserve(data.size() / 2); - for (size_t i = 0; i < data.size() / 2; i++) result.push_back((data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F)); + for (size_t i = 0; i < data.size() / 2; i++) result.push_back( + (data[i * 2] & 0x0F) << 4 | (data[i * 2 + 1] & 0x0F)); return result; } @@ -105,7 +116,11 @@ return true; } - template std::vector concat(const std::vector& a, const std::vector& b, const std::vector& c = {}) { + template std::vector concat( + const std::vector& a, + const std::vector& b, + const std::vector& c = {}) // + { std::vector result; result.reserve(a.size() + b.size() + c.size()); for (size_t i = 0; i < a.size(); i++) result.push_back(a[i]); @@ -114,8 +129,10 @@ return result; } - template std::vector xOR(const std::vector& a, const std::vector& b) { - if (a.size() != b.size()) throw std::invalid_argument("Both must be the same length when doing XOR."); + template + std::vector xOR(const std::vector& a, const std::vector& b) { + if (a.size() != b.size()) throw std::invalid_argument( + "Both must be the same length when doing XOR."); std::vector result; result.reserve(a.size()); for (size_t i = 0; i < a.size(); i++) result.push_back(a[i] ^ b[i]); @@ -124,7 +141,8 @@ public: - DJMFixImpl(djmfix::logging::Logger* logger) : logger(logger ? logger : djmfix::logging::blackhole()) { + DJMFixImpl(djmfix::logging::Logger* logger) + : logger(logger ? logger : djmfix::logging::blackhole()) { } virtual ~DJMFixImpl() override { @@ -137,37 +155,78 @@ this->midiSender = midiSender; } - virtual void receive(const MidiMessage& midiMessage) override { - logger->log(L::FINE, "Received a message: size = " + std::to_string(midiMessage.size()) + " data = " + toString(midiMessage)); + virtual void receive(const MidiMessage& msg) override { + logger->log(L::FINE, "Received a message: " + "size = " + std::to_string(msg.size()) + " " + "data = " + toString(msg)); std::lock_guard lock(midiMutex); - if (midiMessage.size() == 12 && midiMessage[9] == 0x11) { + if (msg.size() == 12 && msg[9] == 0x11) { logger->log(L::INFO, "Received greeting message."); - send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x12, 0x2a, 0x01, 0x0b, 0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a, 0x02, 0x0b, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x03, 0x12, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0xf7}); + send({ + 0xf0, 0x00, 0x40, 0x05, + 0x00, 0x00, 0x00, 0x17, + 0x00, 0x12, 0x2a, 0x01, + 0x0b, 0x50, 0x69, 0x6f, + 0x6e, 0x65, 0x65, 0x72, + 0x44, 0x4a, 0x02, 0x0b, + 0x72, 0x65, 0x6b, 0x6f, + 0x72, 0x64, 0x62, 0x6f, + 0x78, 0x03, 0x12, 0x02, + 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x03, 0x04, 0x08, 0x00, + 0x00, 0x00, 0x00, 0xf7 + }); logger->log(L::INFO, "Sent message with seed1."); - } else if (midiMessage.size() == 54 && midiMessage[9] == 0x13 && midiMessage[33] == 0x04 && midiMessage[43] == 0x03) { - Bytes hash1(midiMessage.begin() + 35, midiMessage.begin() + 35 + 8); - seed2 = Bytes(midiMessage.begin() + 45, midiMessage.begin() + 45 + 8); + } else if (msg.size() == 54 + && msg[9] == 0x13 + && msg[33] == 0x04 + && msg[43] == 0x03) // + { + Bytes hash1(msg.begin() + 35, msg.begin() + 35 + 8); + seed2 = Bytes(msg.begin() + 45, msg.begin() + 45 + 8); hash1 = normalize(hash1); seed2 = normalize(seed2); - logger->log(L::INFO, "Received message with hash1 = " + toString(hash1) + " and seed2 = " + toString(seed2)); + logger->log(L::INFO, "Received message with " + "hash1 = " + toString(hash1) + " and " + "seed2 = " + toString(seed2)); Bytes seed0 = {0x68, 0x01, 0x31, 0xFB}; Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00}; - Bytes hash1check = toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2)))); + Bytes hash1check = + toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2)))); if (equals(hash1, hash1check)) { logger->log(L::INFO, "Verification of hash1 was successful."); - Bytes hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2)))); - send(concat({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x14, 0x38, 0x01, 0x0b, 0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a, 0x02, 0x0b, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x04, 0x0a}, concat(denormalize(hash2),{0x05, 0x16, 0x05, 0x09, 0x0b, 0x05, 0x04, 0x0b, 0x0f, 0x0e, 0x0e, 0x04, 0x04, 0x0a, 0x05, 0x0a, 0x0c, 0x08, 0x0e, 0x04, 0x0c, 0x05, 0xf7}))); + Bytes hash2 = + toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2)))); + send(concat({ + 0xf0, 0x00, 0x40, 0x05, + 0x00, 0x00, 0x00, 0x17, + 0x00, 0x14, 0x38, 0x01, + 0x0b, 0x50, 0x69, 0x6f, + 0x6e, 0x65, 0x65, 0x72, + 0x44, 0x4a, 0x02, 0x0b, + 0x72, 0x65, 0x6b, 0x6f, + 0x72, 0x64, 0x62, 0x6f, + 0x78, 0x04, 0x0a + }, concat(denormalize(hash2),{ + 0x05, 0x16, 0x05, 0x09, + 0x0b, 0x05, 0x04, 0x0b, + 0x0f, 0x0e, 0x0e, 0x04, + 0x04, 0x0a, 0x05, 0x0a, + 0x0c, 0x08, 0x0e, 0x04, + 0x0c, 0x05, 0xf7 + }))); logger->log(L::INFO, "Sent message with hash2."); } else { std::stringstream logMessage; logMessage << "Verification of hash1 failed: " - << " midiMessage = " << toString(midiMessage) + << " midiMessage = " << toString(msg) << " seed0 = " << toString(seed0) << " seed1 = " << toString(seed1) << " seed2 = " << toString(seed2) @@ -176,19 +235,26 @@ logger->log(L::SEVERE, logMessage.str()); // TODO: graceful death } - } else if (midiMessage.size() == 12 && midiMessage[9] == 0x15) { + } else if (msg.size() == 12 && msg[9] == 0x15) { sendKeepAlive = true; - logger->log(L::INFO, "Received acknowledgment message. Started sending keep-alive messages. LINE/PHONO channels should work now."); + logger->log(L::INFO, "Received acknowledgment message. " + "Started sending keep-alive messages. " + "LINE/PHONO channels should work now."); } } void start() override { logger->log(L::FINE, "DJMFixImpl::start()"); - if (midiSender == nullptr) throw std::logic_error("Need a midiSender when starting DJMFix"); + if (midiSender == nullptr) + throw std::logic_error("Need a midiSender when starting DJMFix"); // TODO: methods for parsing and constructing messages from parts (TLV) - send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7}); + send({ + 0xf0, 0x00, 0x40, 0x05, + 0x00, 0x00, 0x00, 0x17, + 0x00, 0x50, 0x01, 0xf7 + }); logger->log(L::INFO, "Sent greeting message."); keepAliveThread = std::thread(&DJMFixImpl::run, this); diff -r 1c74985d4c4e -r 63154f9d24a2 Logger.cpp --- a/Logger.cpp Tue Apr 15 22:44:31 2025 +0200 +++ b/Logger.cpp Tue Apr 15 22:45:25 2025 +0200 @@ -1,6 +1,6 @@ /** * DJM-Fix - * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,12 +56,15 @@ public: - LoggerImpl(std::ostream& output, Level minLevel) : output(output), minLevel(minLevel) { + LoggerImpl(std::ostream& output, Level minLevel) + : output(output), minLevel(minLevel) { } virtual void log(Level level, const std::string& message) override { if (level <= minLevel) { - output << getTimestamp() << " " << std::setw(8) << toString(level) << ": " << message << std::endl; + output << getTimestamp() + << " " << std::setw(8) << toString(level) + << ": " << message << std::endl; } } }; diff -r 1c74985d4c4e -r 63154f9d24a2 djm-fix.cpp --- a/djm-fix.cpp Tue Apr 15 22:44:31 2025 +0200 +++ b/djm-fix.cpp Tue Apr 15 22:45:25 2025 +0200 @@ -1,6 +1,6 @@ /** * DJM-Fix - * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,69 +33,86 @@ } /** - * The support for Pioneer DJ DJM-250MK2 (an external USB sound card / mixer) was added to the Linux (kernel) by these patches: + * The support for Pioneer DJ DJM-250MK2 (an external USB sound card / mixer) + * was added to the Linux (kernel) by these patches: * - * - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/sound/usb?id=73d8c94084341e2895169a0462dbc18167f01683 (playback) - * - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/sound/usb?id=14335d8b9e1a2bf006f9d969a103f9731cabb210 (recording) - * - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/sound/usb?id=cdc01a1558dedcee3daee7e1802d0349a07edb87 (mixer setup) + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/... + * - ...sound/usb?id=73d8c94084341e2895169a0462dbc18167f01683 (playback) + * - ...sound/usb?id=14335d8b9e1a2bf006f9d969a103f9731cabb210 (recording) + * - ...sound/usb?id=cdc01a1558dedcee3daee7e1802d0349a07edb87 (mixer setup) * - * These patches are enough for playback and for recording from post CH faders. + * These patches are enough for playback and for recording from post CH faders. * - * However this mixer is somehow incapacitated and if we want to record the raw signal from the PHONO or LINE channels, - * we only get silence. This feature is important for DVS (Digital Vinyl Systems) setups where - * the timecode signal from special control vinyls flows from mixer to the computer - * where it is interpreted in a software like MIXXX and used for controlling the playback of files on our computer. - * The signal (usually music) from these files flows back to the mixer and then to speakers and headphones. + * However this mixer is somehow incapacitated and if we want to record the raw + * signal from the PHONO or LINE channels, we only get silence. This feature is + * important for DVS (Digital Vinyl Systems) setups where the timecode signal + * from special control vinyls flows from mixer to the computer where it is + * interpreted in a software like MIXXX and used for controlling the playback of + * files on our computer. The signal (usually music) from these files flows back + * to the mixer and then to speakers and headphones. * - * To make this work and enjoy all the features of the device we have bought, we need to tell the mixer that we - * want the signal instead of silence on given channels. And this is the purpose of the djm-fix utility and - * it is done by sending some magic packet to the mixer. + * To make this work and enjoy all the features of the device we have bought, we + * need to tell the mixer that we want the signal instead of silence on given + * channels. And this is the purpose of the djm-fix utility and it is done by + * sending some magic packet to the mixer. * - * Implementation of this magic in the AlsaBridge.cpp file is based on publicly available documentation - * that can be found at . - * This page pointed me to the proper hash function (according to the constants, it is bit uncommon but publicly known Fowler–Noll–Vo hash function, FNV) - * and some magic bits. I wrote this standalone C++ program that talks with the mixer over MIDI SysEx messages and does the magic. + * Implementation of this magic in the AlsaBridge.cpp file is based on publicly + * available documentation that can be found at: + * - https://swiftb0y.github.io/CDJHidProtocol/hid-analysis/handshake.html + * - https://mixb.me/CDJHidProtocol/hid-analysis/handshake.html (formerly). + * This page pointed me to the proper hash function (according to the constants, + * it is bit uncommon but publicly known Fowler–Noll–Vo hash function, FNV) and + * some magic bits. I wrote this standalone C++ program that talks with the + * mixer over MIDI SysEx messages and does the magic. * - * When this program is started, it finds the mixer and makes it fully working. - * It needs to be running all the time, otherwise we will get silence on the PHONO/LINE channels again. + * When this program is started, it finds the mixer and makes it fully working. + * It needs to be running all the time, otherwise we will get silence + * on the PHONO/LINE channels again. * * Install dependencies: - * apt install mercurial make pkg-config g++ libasound2-dev # in Debian or Ubuntu (it will be similar in other distributions) + * apt install mercurial make pkg-config g++ libasound2-dev + * (in Debian or Ubuntu - it will be similar in other distributions) * * Download djm-fix: - * hg clone https://hg.frantovo.cz/midi/djm-fix/ # primary source - * hg clone https://hg.globalcode.info/midi/djm-fix/ # or we can use this mirror + * hg clone https://hg.frantovo.cz/midi/djm-fix/ # primary source + * hg clone https://hg.globalcode.info/midi/djm-fix/ # mirror * * Compile: - * make # we can skip this step, it will be compiled on the first run + * make # can be skipped * * Run: - * make run # in most cases - * build/djm-fix 'Pioneer DJ.*' # or provide custom name pattern (regular expression) to select the proper card + * make run # in most cases + * build/djm-fix 'Pioneer DJ.*' # with custom name + * ^ regular expression to select desired card * * Stop: * press Ctrl+C * - * Look for updates in the Mercurial repositories and at . + * Look for updates in the Mercurial repositories and at: + * - https://blog.frantovo.cz/c/387/ */ int main(int argc, char**argv) { using L = djmfix::logging::Level; - std::unique_ptr logger(djmfix::logging::create(std::cerr, L::INFO)); + std::unique_ptr + logger(djmfix::logging::create(std::cerr, L::INFO)); try { logger->log(L::INFO, "DJM-Fix started."); std::string cardNamePattern = argc == 2 ? argv[1] : "Pioneer DJ.*"; signal(SIGINT, interrupt); std::unique_ptr djmFix(djmfix::create(logger.get())); - std::unique_ptr alsaBridge(djmfix::alsa::create(djmFix.get(), cardNamePattern, logger.get())); + std::unique_ptr alsaBridge( + djmfix::alsa::create(djmFix.get(), + cardNamePattern, + logger.get())); alsaBridge->start(); while (run) std::this_thread::sleep_for(std::chrono::milliseconds(100)); - + std::cerr << std::endl; logger->log(L::INFO, "DJM-Fix stopping."); - + alsaBridge->stop(); return 0;