2018-10-07 03:36:44 +00:00
|
|
|
#pragma once
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2019-08-19 23:08:54 +00:00
|
|
|
#include <csignal>
|
2018-05-06 22:44:31 +00:00
|
|
|
#include <thread>
|
2019-08-19 23:08:54 +00:00
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
#include "lib/audiodev/AudioVoiceEngine.hpp"
|
2018-05-06 22:44:31 +00:00
|
|
|
|
|
|
|
#include <alsa/asoundlib.h>
|
2019-08-19 23:08:54 +00:00
|
|
|
#include <logvisor/logvisor.hpp>
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
namespace boo {
|
2018-05-06 22:44:31 +00:00
|
|
|
extern logvisor::Module ALSALog;
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static inline double TimespecToDouble(struct timespec& ts) { return ts.tv_sec + ts.tv_nsec / 1.0e9; }
|
|
|
|
|
|
|
|
struct LinuxMidi : BaseAudioVoiceEngine {
|
|
|
|
std::unordered_map<std::string, IMIDIPort*> m_openHandles;
|
|
|
|
void _addOpenHandle(const char* name, IMIDIPort* port) { m_openHandles[name] = port; }
|
|
|
|
void _removeOpenHandle(IMIDIPort* port) {
|
|
|
|
for (auto it = m_openHandles.begin(); it != m_openHandles.end();) {
|
|
|
|
if (it->second == port) {
|
|
|
|
it = m_openHandles.erase(it);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
++it;
|
2018-08-19 00:28:00 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~LinuxMidi() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
for (auto& p : m_openHandles)
|
|
|
|
p.second->_disown();
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> ret;
|
|
|
|
int status;
|
|
|
|
int card = -1; /* use -1 to prime the pump of iterating through card list */
|
|
|
|
|
|
|
|
if ((status = snd_card_next(&card)) < 0)
|
|
|
|
return {};
|
|
|
|
if (card < 0)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
snd_rawmidi_info_t* info;
|
|
|
|
snd_rawmidi_info_malloc(&info);
|
|
|
|
|
|
|
|
while (card >= 0) {
|
|
|
|
snd_ctl_t* ctl;
|
|
|
|
int device = -1;
|
|
|
|
int status;
|
2019-07-20 04:22:36 +00:00
|
|
|
std::string name = fmt::format(fmt("hw:{}"), card);
|
|
|
|
if ((status = snd_ctl_open(&ctl, name.c_str(), 0)) < 0)
|
2018-12-08 05:17:51 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
do {
|
|
|
|
status = snd_ctl_rawmidi_next_device(ctl, &device);
|
|
|
|
if (status < 0)
|
|
|
|
break;
|
|
|
|
if (device >= 0) {
|
2019-07-20 04:22:36 +00:00
|
|
|
name += fmt::format(fmt(",{}"), device);
|
2018-12-08 05:17:51 +00:00
|
|
|
auto search = m_openHandles.find(name);
|
|
|
|
if (search != m_openHandles.cend()) {
|
|
|
|
ret.push_back(std::make_pair(name, search->second->description()));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_rawmidi_t* midi;
|
2019-07-20 04:22:36 +00:00
|
|
|
if (!snd_rawmidi_open(&midi, nullptr, name.c_str(), SND_RAWMIDI_NONBLOCK)) {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_info(midi, info);
|
|
|
|
ret.push_back(std::make_pair(name, snd_rawmidi_info_get_name(info)));
|
|
|
|
snd_rawmidi_close(midi);
|
|
|
|
}
|
2018-08-19 00:28:00 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
} while (device >= 0);
|
2018-08-19 00:28:00 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_ctl_close(ctl);
|
2018-08-18 22:08:58 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if ((status = snd_card_next(&card)) < 0)
|
|
|
|
break;
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_info_free(info);
|
2018-08-19 00:28:00 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool supportsVirtualMIDIIn() const override { return true; }
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static void MIDIFreeProc(void* midiStatus) { snd_rawmidi_status_free((snd_rawmidi_status_t*)midiStatus); }
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static void MIDIReceiveProc(snd_rawmidi_t* midi, const ReceiveFunctor& receiver) {
|
|
|
|
logvisor::RegisterThreadName("Boo MIDI");
|
|
|
|
snd_rawmidi_status_t* midiStatus;
|
|
|
|
snd_rawmidi_status_malloc(&midiStatus);
|
|
|
|
pthread_cleanup_push(MIDIFreeProc, midiStatus);
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
uint8_t buf[512];
|
|
|
|
while (true) {
|
|
|
|
snd_htimestamp_t ts;
|
|
|
|
snd_rawmidi_status(midi, midiStatus);
|
|
|
|
snd_rawmidi_status_get_tstamp(midiStatus, &ts);
|
|
|
|
int rdBytes = snd_rawmidi_read(midi, buf, 512);
|
|
|
|
if (rdBytes < 0) {
|
|
|
|
if (rdBytes != -EINTR) {
|
2019-07-20 04:22:36 +00:00
|
|
|
ALSALog.report(logvisor::Error, fmt("MIDI connection lost"));
|
2018-12-08 05:17:51 +00:00
|
|
|
break;
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
int oldtype;
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldtype);
|
|
|
|
receiver(std::vector<uint8_t>(std::cbegin(buf), std::cbegin(buf) + rdBytes), TimespecToDouble(ts));
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldtype);
|
|
|
|
pthread_testcancel();
|
|
|
|
}
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
pthread_cleanup_pop(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MIDIIn : public IMIDIIn {
|
|
|
|
snd_rawmidi_t* m_midi;
|
|
|
|
std::thread m_midiThread;
|
|
|
|
|
|
|
|
MIDIIn(LinuxMidi* parent, snd_rawmidi_t* midi, bool virt, ReceiveFunctor&& receiver)
|
|
|
|
: IMIDIIn(parent, virt, std::move(receiver))
|
|
|
|
, m_midi(midi)
|
|
|
|
, m_midiThread(std::bind(MIDIReceiveProc, m_midi, m_receiver)) {}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~MIDIIn() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_parent)
|
|
|
|
static_cast<LinuxMidi*>(m_parent)->_removeOpenHandle(this);
|
|
|
|
pthread_cancel(m_midiThread.native_handle());
|
|
|
|
if (m_midiThread.joinable())
|
|
|
|
m_midiThread.join();
|
|
|
|
snd_rawmidi_close(m_midi);
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::string description() const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_info_t* info;
|
|
|
|
snd_rawmidi_info_alloca(&info);
|
|
|
|
snd_rawmidi_info(m_midi, info);
|
|
|
|
std::string ret = snd_rawmidi_info_get_name(info);
|
|
|
|
return ret;
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct MIDIOut : public IMIDIOut {
|
|
|
|
snd_rawmidi_t* m_midi;
|
|
|
|
MIDIOut(LinuxMidi* parent, snd_rawmidi_t* midi, bool virt) : IMIDIOut(parent, virt), m_midi(midi) {}
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~MIDIOut() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_parent)
|
|
|
|
static_cast<LinuxMidi*>(m_parent)->_removeOpenHandle(this);
|
|
|
|
snd_rawmidi_close(m_midi);
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::string description() const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_info_t* info;
|
|
|
|
snd_rawmidi_info_alloca(&info);
|
|
|
|
snd_rawmidi_info(m_midi, info);
|
|
|
|
std::string ret = snd_rawmidi_info_get_name(info);
|
|
|
|
return ret;
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
size_t send(const void* buf, size_t len) const override {
|
|
|
|
return size_t(std::max(0l, snd_rawmidi_write(m_midi, buf, len)));
|
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct MIDIInOut : public IMIDIInOut {
|
|
|
|
snd_rawmidi_t* m_midiIn;
|
|
|
|
snd_rawmidi_t* m_midiOut;
|
|
|
|
std::thread m_midiThread;
|
|
|
|
|
|
|
|
MIDIInOut(LinuxMidi* parent, snd_rawmidi_t* midiIn, snd_rawmidi_t* midiOut, bool virt, ReceiveFunctor&& receiver)
|
|
|
|
: IMIDIInOut(parent, virt, std::move(receiver))
|
|
|
|
, m_midiIn(midiIn)
|
|
|
|
, m_midiOut(midiOut)
|
|
|
|
, m_midiThread(std::bind(MIDIReceiveProc, m_midiIn, m_receiver)) {}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~MIDIInOut() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_parent)
|
|
|
|
static_cast<LinuxMidi*>(m_parent)->_removeOpenHandle(this);
|
|
|
|
pthread_cancel(m_midiThread.native_handle());
|
|
|
|
if (m_midiThread.joinable())
|
|
|
|
m_midiThread.join();
|
|
|
|
snd_rawmidi_close(m_midiIn);
|
|
|
|
snd_rawmidi_close(m_midiOut);
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::string description() const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_info_t* info;
|
|
|
|
snd_rawmidi_info_alloca(&info);
|
|
|
|
snd_rawmidi_info(m_midiIn, info);
|
|
|
|
std::string ret = snd_rawmidi_info_get_name(info);
|
|
|
|
return ret;
|
2018-05-06 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
size_t send(const void* buf, size_t len) const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
return size_t(std::max(0l, snd_rawmidi_write(m_midiOut, buf, len)));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
int status;
|
|
|
|
snd_rawmidi_t* midi;
|
|
|
|
status = snd_rawmidi_open(&midi, nullptr, "virtual", 0);
|
|
|
|
if (status)
|
|
|
|
return {};
|
|
|
|
return std::make_unique<MIDIIn>(nullptr, midi, true, std::move(receiver));
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
int status;
|
|
|
|
snd_rawmidi_t* midi;
|
|
|
|
status = snd_rawmidi_open(nullptr, &midi, "virtual", 0);
|
|
|
|
if (status)
|
|
|
|
return {};
|
|
|
|
return std::make_unique<MIDIOut>(nullptr, midi, true);
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
int status;
|
|
|
|
snd_rawmidi_t* midiIn;
|
|
|
|
snd_rawmidi_t* midiOut;
|
|
|
|
status = snd_rawmidi_open(&midiIn, &midiOut, "virtual", 0);
|
|
|
|
if (status)
|
|
|
|
return {};
|
|
|
|
return std::make_unique<MIDIInOut>(nullptr, midiIn, midiOut, true, std::move(receiver));
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_t* midi;
|
|
|
|
int status = snd_rawmidi_open(&midi, nullptr, name, 0);
|
|
|
|
if (status)
|
|
|
|
return {};
|
|
|
|
auto ret = std::make_unique<MIDIIn>(this, midi, true, std::move(receiver));
|
|
|
|
_addOpenHandle(name, ret.get());
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_t* midi;
|
|
|
|
int status = snd_rawmidi_open(nullptr, &midi, name, 0);
|
|
|
|
if (status)
|
|
|
|
return {};
|
|
|
|
auto ret = std::make_unique<MIDIOut>(this, midi, true);
|
|
|
|
_addOpenHandle(name, ret.get());
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
snd_rawmidi_t* midiIn;
|
|
|
|
snd_rawmidi_t* midiOut;
|
|
|
|
int status = snd_rawmidi_open(&midiIn, &midiOut, name, 0);
|
|
|
|
if (status)
|
|
|
|
return {};
|
|
|
|
auto ret = std::make_unique<MIDIInOut>(this, midiIn, midiOut, true, std::move(receiver));
|
|
|
|
_addOpenHandle(name, ret.get());
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool useMIDILock() const override { return true; }
|
2018-05-06 22:44:31 +00:00
|
|
|
};
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
} // namespace boo
|