djm-fix.cpp
author František Kučera <franta-hg@frantovo.cz>
Sun, 01 Jun 2025 13:18:10 +0200
branchv_0
changeset 20 a08e30243b95
parent 18 358a601bfe81
permissions -rw-r--r--
Added tag v0.1 for changeset 334b727f7516
franta-hg@0
     1
/**
franta-hg@0
     2
 * DJM-Fix
franta-hg@16
     3
 * Copyright © 2025 František Kučera (Frantovo.cz, GlobalCode.info)
franta-hg@0
     4
 *
franta-hg@0
     5
 * This program is free software: you can redistribute it and/or modify
franta-hg@0
     6
 * it under the terms of the GNU General Public License as published by
franta-hg@0
     7
 * the Free Software Foundation, version 3 of the License.
franta-hg@0
     8
 *
franta-hg@0
     9
 * This program is distributed in the hope that it will be useful,
franta-hg@0
    10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
franta-hg@0
    11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
franta-hg@0
    12
 * GNU General Public License for more details.
franta-hg@0
    13
 *
franta-hg@0
    14
 * You should have received a copy of the GNU General Public License
franta-hg@0
    15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
franta-hg@0
    16
 */
franta-hg@0
    17
franta-hg@1
    18
#include <memory>
franta-hg@1
    19
#include <iostream>
franta-hg@2
    20
#include <chrono>
franta-hg@2
    21
#include <thread>
franta-hg@2
    22
#include <csignal>
franta-hg@5
    23
#include <atomic>
franta-hg@1
    24
franta-hg@1
    25
#include "DJMFix.h"
franta-hg@2
    26
#include "AlsaBridge.h"
franta-hg@12
    27
#include "Logger.h"
franta-hg@2
    28
franta-hg@5
    29
static std::atomic<bool> run{true};
franta-hg@2
    30
franta-hg@2
    31
void interrupt(int signal) {
franta-hg@2
    32
	run = false;
franta-hg@2
    33
}
franta-hg@1
    34
franta-hg@10
    35
/**
franta-hg@16
    36
 * The support for Pioneer DJ DJM-250MK2 (an external  USB  sound  card / mixer)
franta-hg@16
    37
 * was added to the Linux (kernel) by these patches:
franta-hg@10
    38
 *
franta-hg@16
    39
 * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/...
franta-hg@16
    40
 *   - ...sound/usb?id=73d8c94084341e2895169a0462dbc18167f01683 (playback)
franta-hg@16
    41
 *   - ...sound/usb?id=14335d8b9e1a2bf006f9d969a103f9731cabb210 (recording)
franta-hg@16
    42
 *   - ...sound/usb?id=cdc01a1558dedcee3daee7e1802d0349a07edb87 (mixer setup)
franta-hg@10
    43
 *
franta-hg@16
    44
 * These patches are enough for playback and for recording from post CH  faders.
franta-hg@10
    45
 *
franta-hg@16
    46
 * However this mixer is somehow incapacitated and if we want to record the  raw
franta-hg@16
    47
 * signal  from the PHONO or LINE channels, we only get silence. This feature is
franta-hg@16
    48
 * important for DVS (Digital Vinyl Systems) setups where  the  timecode  signal
franta-hg@16
    49
 * from  special  control  vinyls  flows  from mixer to the computer where it is
franta-hg@16
    50
 * interpreted in a software like MIXXX and used for controlling the playback of
franta-hg@16
    51
 * files on our computer. The signal (usually music) from these files flows back
franta-hg@16
    52
 * to the mixer and then to speakers and headphones.
franta-hg@10
    53
 *
franta-hg@16
    54
 * To make this work and enjoy all the features of the device we have bought, we
franta-hg@16
    55
 * need  to  tell  the mixer that we want the signal instead of silence on given
franta-hg@16
    56
 * channels. And this is the purpose of the djm-fix utility and it  is  done  by
franta-hg@16
    57
 * sending some magic packet to the mixer.
franta-hg@10
    58
 *
franta-hg@18
    59
 * Implementation of this magic in the DJMFix.cpp file is based on  publicly
franta-hg@16
    60
 * available documentation that can be found at:
franta-hg@16
    61
 *   - https://swiftb0y.github.io/CDJHidProtocol/hid-analysis/handshake.html
franta-hg@16
    62
 *   - https://mixb.me/CDJHidProtocol/hid-analysis/handshake.html (formerly).
franta-hg@16
    63
 * This page pointed me to the proper hash function (according to the constants,
franta-hg@16
    64
 * it  is bit uncommon but publicly known Fowler–Noll–Vo hash function, FNV) and
franta-hg@16
    65
 * some magic bits. I wrote this standalone C++  program  that  talks  with  the
franta-hg@16
    66
 * mixer over MIDI SysEx messages and does the magic.
franta-hg@10
    67
 *
franta-hg@16
    68
 * When this program is started, it finds the mixer and makes it fully  working.
franta-hg@16
    69
 * It needs to be running all the time, otherwise we will get silence
franta-hg@16
    70
 * on the PHONO/LINE channels again.
franta-hg@10
    71
 *
franta-hg@10
    72
 * Install dependencies:
franta-hg@16
    73
 *   apt install mercurial make pkg-config g++ libasound2-dev
franta-hg@16
    74
 * (in Debian or Ubuntu - it will be similar in other distributions)
franta-hg@10
    75
 *
franta-hg@10
    76
 * Download djm-fix:
franta-hg@16
    77
 *   hg clone https://hg.frantovo.cz/midi/djm-fix/        # primary source
franta-hg@16
    78
 *   hg clone https://hg.globalcode.info/midi/djm-fix/    # mirror
franta-hg@10
    79
 *
franta-hg@10
    80
 * Compile:
franta-hg@16
    81
 *   make                                                 # can be skipped
franta-hg@10
    82
 *
franta-hg@10
    83
 * Run:
franta-hg@16
    84
 *   make run                                             # in most cases
franta-hg@16
    85
 *   build/djm-fix 'Pioneer DJ.*'                         # with custom name
franta-hg@16
    86
 *                 ^ regular expression to select desired card
franta-hg@10
    87
 *
franta-hg@10
    88
 * Stop:
franta-hg@10
    89
 *   press Ctrl+C
franta-hg@11
    90
 * 
franta-hg@16
    91
 * Look for updates in the Mercurial repositories and at:
franta-hg@16
    92
 *   - https://blog.frantovo.cz/c/387/
franta-hg@18
    93
 *   - https://blog.frantovo.cz/c/396/
franta-hg@10
    94
 */
franta-hg@10
    95
franta-hg@0
    96
int main(int argc, char**argv) {
franta-hg@12
    97
	using L = djmfix::logging::Level;
franta-hg@16
    98
	std::unique_ptr<djmfix::logging::Logger>
franta-hg@16
    99
			logger(djmfix::logging::create(std::cerr, L::INFO));
franta-hg@11
   100
	try {
franta-hg@13
   101
		logger->log(L::INFO, "DJM-Fix started.");
franta-hg@18
   102
		std::string cardNamePattern = argc == 2
franta-hg@18
   103
				? argv[1]
franta-hg@18
   104
				: "(Pioneer DJ|AlphaTheta).*";
franta-hg@10
   105
franta-hg@11
   106
		signal(SIGINT, interrupt);
franta-hg@12
   107
		std::unique_ptr<djmfix::DJMFix> djmFix(djmfix::create(logger.get()));
franta-hg@16
   108
		std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(
franta-hg@16
   109
				djmfix::alsa::create(djmFix.get(),
franta-hg@16
   110
				cardNamePattern,
franta-hg@16
   111
				logger.get()));
franta-hg@1
   112
franta-hg@11
   113
		alsaBridge->start();
franta-hg@11
   114
		while (run) std::this_thread::sleep_for(std::chrono::milliseconds(100));
franta-hg@16
   115
franta-hg@12
   116
		std::cerr << std::endl;
franta-hg@13
   117
		logger->log(L::INFO, "DJM-Fix stopping.");
franta-hg@16
   118
franta-hg@11
   119
		alsaBridge->stop();
franta-hg@1
   120
franta-hg@11
   121
		return 0;
franta-hg@11
   122
	} catch (const std::exception& e) {
franta-hg@12
   123
		logger->log(L::SEVERE, e.what());
franta-hg@11
   124
	}
franta-hg@0
   125
}