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".
/**
* DJM-Fix
* Copyright © 2020 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
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <stdexcept>
#include <thread>
#include <mutex>
#include <atomic>
#include <regex>
#include <alsa/asoundlib.h>
#include "AlsaBridge.h"
namespace djmfix {
namespace alsa {
class AlsaBridgeImpl : public AlsaBridge, private djmfix::MidiSender {
private:
djmfix::DJMFix* djmFix;
snd_rawmidi_t* input;
snd_rawmidi_t* output;
std::thread receivingThread;
std::recursive_mutex midiMutex;
std::atomic<bool> stopped{false};
std::string findDeviceName(std::regex cardNamePattern) {
std::vector<int> cardNumbers;
std::cerr << "Looking for available cards:" << std::endl; // TODO: do not mess STDIO
for (int card = -1; snd_card_next(&card) == 0 && card >= 0;) {
char* longName = nullptr;
snd_card_get_longname(card, &longName);
std::cerr << "card: #" << card << ": '" << longName << "'"; // TODO: do not mess STDIO
if (std::regex_match(longName, cardNamePattern)) {
cardNumbers.push_back(card);
std::cerr << " [matches]"; // TODO: do not mess STDIO
}
std::cerr << std::endl;
free(longName);
}
if (cardNumbers.size() == 1) {
std::cerr << "Going to fix card #" << cardNumbers[0] << std::endl; // TODO: do not mess STDIO
return "hw:" + std::to_string(cardNumbers[0]);
} 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.");
} else {
throw std::invalid_argument("Multiple cards with matching name found. Please provide a name pattern that matches only one card");
}
}
void run() {
while (!stopped) {
{
std::lock_guard<std::recursive_mutex> lock(midiMutex);
// TODO: poll
uint8_t buffer[256];
ssize_t length = snd_rawmidi_read(input, buffer, sizeof (buffer));
if (length > 0 && length <= sizeof (buffer)) {
// TODO: multiple messages combined together?
djmFix->receive(MidiMessage(buffer, buffer + length));
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
public:
AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern) : djmFix(djmFix) {
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);
if (error) throw std::invalid_argument("unable to open ALSA device");
djmFix->setMidiSender(this);
}
virtual ~AlsaBridgeImpl() {
// TODO: do not use raw/exclusive access to the device
snd_rawmidi_close(input);
snd_rawmidi_close(output);
std::cerr << "~AlsaBridgeImpl()" << std::endl; // TODO: do not mess STDIO
}
virtual void start() override {
djmFix->start();
receivingThread = std::thread(&AlsaBridgeImpl::run, this);
}
virtual void stop() override {
stopped = true;
receivingThread.join();
djmFix->stop();
}
virtual void send(MidiMessage midiMessage) override {
std::lock_guard<std::recursive_mutex> lock(midiMutex);
ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size());
std::cerr << "AlsaBridgeImpl::send(): length = " << length << std::endl; // TODO: do not mess STDIO
}
};
AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName) {
return new AlsaBridgeImpl(djmFix, deviceName);
}
}
}