AlsaBridge.cpp
author František Kučera <franta-hg@frantovo.cz>
Mon, 04 Jan 2021 13:38:08 +0100
branchv_0
changeset 11 5b351628a377
parent 9 ee976a1d1f0a
child 12 15d87fdd6e6c
permissions -rw-r--r--
Find card by a name pattern (regular expression) instead using hardcoded name.
By default, we look for card with name matching the "Pioneer DJ.*" pattern and we expect exactly one card to be found.
Custom pattern can be provided as a command-line argument.

Whole name would look something like this: "Pioneer DJ Corporation DJM-250MK2 at usb-0000:01:00.0-10.1, high speed".
     1 /**
     2  * DJM-Fix
     3  * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
     4  *
     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.
     8  *
     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.
    13  *
    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/>.
    16  */
    17 #include <iostream>
    18 #include <stdexcept>
    19 #include <thread>
    20 #include <mutex>
    21 #include <atomic>
    22 #include <regex>
    23 
    24 #include <alsa/asoundlib.h>
    25 
    26 #include "AlsaBridge.h"
    27 
    28 namespace djmfix {
    29 namespace alsa {
    30 
    31 class AlsaBridgeImpl : public AlsaBridge, private djmfix::MidiSender {
    32 private:
    33 	djmfix::DJMFix* djmFix;
    34 	snd_rawmidi_t* input;
    35 	snd_rawmidi_t* output;
    36 	std::thread receivingThread;
    37 	std::recursive_mutex midiMutex;
    38 	std::atomic<bool> stopped{false};
    39 
    40 	std::string findDeviceName(std::regex cardNamePattern) {
    41 
    42 		std::vector<int> cardNumbers;
    43 
    44 		std::cerr << "Looking for available cards:" << std::endl; // TODO: do not mess STDIO
    45 
    46 		for (int card = -1; snd_card_next(&card) == 0 && card >= 0;) {
    47 			char* longName = nullptr;
    48 			snd_card_get_longname(card, &longName);
    49 			std::cerr << "card: #" << card << ": '" << longName << "'"; // TODO: do not mess STDIO
    50 			if (std::regex_match(longName, cardNamePattern)) {
    51 				cardNumbers.push_back(card);
    52 				std::cerr << " [matches]"; // TODO: do not mess STDIO
    53 			}
    54 			std::cerr << std::endl;
    55 			free(longName);
    56 		}
    57 
    58 		if (cardNumbers.size() == 1) {
    59 			std::cerr << "Going to fix card #" << cardNumbers[0] << std::endl; // TODO: do not mess STDIO
    60 			return "hw:" + std::to_string(cardNumbers[0]);
    61 		} else if (cardNumbers.empty()) {
    62 			throw std::invalid_argument("No card with matching name found. Is the card connected? Maybe try to provide different name pattern.");
    63 		} else {
    64 			throw std::invalid_argument("Multiple cards with matching name found. Please provide a name pattern that matches only one card");
    65 		}
    66 	}
    67 
    68 	void run() {
    69 		while (!stopped) {
    70 			{
    71 				std::lock_guard<std::recursive_mutex> lock(midiMutex);
    72 				// TODO: poll
    73 				uint8_t buffer[256];
    74 				ssize_t length = snd_rawmidi_read(input, buffer, sizeof (buffer));
    75 				if (length > 0 && length <= sizeof (buffer)) {
    76 					// TODO: multiple messages combined together?
    77 					djmFix->receive(MidiMessage(buffer, buffer + length));
    78 				}
    79 			}
    80 			std::this_thread::sleep_for(std::chrono::milliseconds(100));
    81 		}
    82 	}
    83 public:
    84 
    85 	AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern) : djmFix(djmFix) {
    86 		if (djmFix == nullptr) throw std::invalid_argument("need a djmFix for AlsaBridge");
    87 
    88 		std::string deviceName = findDeviceName(std::regex(cardNamePattern));
    89 
    90 		int error = snd_rawmidi_open(&input, &output, deviceName.c_str(), SND_RAWMIDI_NONBLOCK);
    91 		if (error) throw std::invalid_argument("unable to open ALSA device");
    92 
    93 
    94 		djmFix->setMidiSender(this);
    95 	}
    96 
    97 	virtual ~AlsaBridgeImpl() {
    98 		// TODO: do not use raw/exclusive access to the device
    99 		snd_rawmidi_close(input);
   100 		snd_rawmidi_close(output);
   101 		std::cerr << "~AlsaBridgeImpl()" << std::endl; // TODO: do not mess STDIO
   102 	}
   103 
   104 	virtual void start() override {
   105 		djmFix->start();
   106 		receivingThread = std::thread(&AlsaBridgeImpl::run, this);
   107 	}
   108 
   109 	virtual void stop() override {
   110 		stopped = true;
   111 		receivingThread.join();
   112 		djmFix->stop();
   113 	}
   114 
   115 	virtual void send(MidiMessage midiMessage) override {
   116 		std::lock_guard<std::recursive_mutex> lock(midiMutex);
   117 		ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size());
   118 		std::cerr << "AlsaBridgeImpl::send(): length = " << length << std::endl; // TODO: do not mess STDIO
   119 	}
   120 
   121 };
   122 
   123 AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName) {
   124 	return new AlsaBridgeImpl(djmFix, deviceName);
   125 }
   126 
   127 }
   128 }