diff options
Diffstat (limited to 'src/AlsaDriver.cpp')
-rw-r--r-- | src/AlsaDriver.cpp | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp new file mode 100644 index 0000000..1ebd12d --- /dev/null +++ b/src/AlsaDriver.cpp @@ -0,0 +1,585 @@ +/* This file is part of Patchage. + * Copyright 2007-2014 David Robillard <http://drobilla.net> + * + * Patchage 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, either version 3 of the License, or (at your option) + * any later version. + * + * Patchage 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 details. + * + * You should have received a copy of the GNU General Public License + * along with Patchage. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <cassert> +#include <set> +#include <string> +#include <utility> + +#include <boost/format.hpp> + +#include "AlsaDriver.hpp" +#include "Patchage.hpp" +#include "PatchageCanvas.hpp" +#include "PatchageModule.hpp" +#include "PatchagePort.hpp" + +using std::endl; +using std::string; +using boost::format; + +AlsaDriver::AlsaDriver(Patchage* app) + : _app(app) + , _seq(NULL) +{ +} + +AlsaDriver::~AlsaDriver() +{ + detach(); +} + +/** Attach to ALSA. */ +void +AlsaDriver::attach(bool /*launch_daemon*/) +{ + int ret = snd_seq_open(&_seq, "default", SND_SEQ_OPEN_DUPLEX, 0); + if (ret) { + _app->error_msg("Alsa: Unable to attach."); + _seq = NULL; + } else { + _app->info_msg("Alsa: Attached."); + + snd_seq_set_client_name(_seq, "Patchage"); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 50000); + + ret = pthread_create(&_refresh_thread, &attr, &AlsaDriver::refresh_main, this); + if (ret) + _app->error_msg("Alsa: Failed to start refresh thread."); + + signal_attached.emit(); + } +} + +void +AlsaDriver::detach() +{ + if (_seq) { + pthread_cancel(_refresh_thread); + pthread_join(_refresh_thread, NULL); + snd_seq_close(_seq); + _seq = NULL; + signal_detached.emit(); + _app->info_msg("Alsa: Detached."); + } +} + +static bool +is_alsa_port(const PatchagePort* port) +{ + return port->type() == ALSA_MIDI; +} + +/** Destroy all JACK (canvas) ports. + */ +void +AlsaDriver::destroy_all() +{ + _app->canvas()->remove_ports(is_alsa_port); + _modules.clear(); + _port_addrs.clear(); +} + +/** Refresh all Alsa Midi ports and connections. + */ +void +AlsaDriver::refresh() +{ + if (!is_attached()) + return; + + assert(_seq); + + _modules.clear(); + _ignored.clear(); + _port_addrs.clear(); + + snd_seq_client_info_t* cinfo; + snd_seq_client_info_alloca(&cinfo); + snd_seq_client_info_set_client(cinfo, -1); + + snd_seq_port_info_t* pinfo; + snd_seq_port_info_alloca(&pinfo); + + PatchageModule* parent = NULL; + PatchagePort* port = NULL; + + // Create port views + while (snd_seq_query_next_client(_seq, cinfo) >= 0) { + snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(_seq, pinfo) >= 0) { + const snd_seq_addr_t& addr = *snd_seq_port_info_get_addr(pinfo); + if (ignore(addr)) { + continue; + } + + create_port_view_internal(_app, addr, parent, port); + } + } + + // Create connections + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(_seq, cinfo) >= 0) { + snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(_seq, pinfo) >= 0) { + const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(pinfo); + if (ignore(*addr)) { + continue; + } + + PatchagePort* port = _app->canvas()->find_port(PortID(*addr, false)); + if (!port) { + continue; + } + + snd_seq_query_subscribe_t* subsinfo; + snd_seq_query_subscribe_alloca(&subsinfo); + snd_seq_query_subscribe_set_root(subsinfo, addr); + snd_seq_query_subscribe_set_index(subsinfo, 0); + while (!snd_seq_query_port_subscribers(_seq, subsinfo)) { + const snd_seq_addr_t* addr2 = snd_seq_query_subscribe_get_addr(subsinfo); + if (addr2) { + const PortID id2(*addr2, true); + PatchagePort* port2 = _app->canvas()->find_port(id2); + if (port2 && !_app->canvas()->get_edge(port, port2)) { + _app->canvas()->make_connection(port, port2); + } + } + + snd_seq_query_subscribe_set_index( + subsinfo, snd_seq_query_subscribe_get_index(subsinfo) + 1); + } + } + } +} + +PatchagePort* +AlsaDriver::create_port_view(Patchage* patchage, + const PortID& id) +{ + PatchageModule* parent = NULL; + PatchagePort* port = NULL; + create_port_view_internal(patchage, id.id.alsa_addr, parent, port); + return port; +} + +PatchageModule* +AlsaDriver::find_module(uint8_t client_id, ModuleType type) +{ + const Modules::const_iterator i = _modules.find(client_id); + if (i == _modules.end()) + return NULL; + + PatchageModule* io_module = NULL; + for (Modules::const_iterator j = i; + j != _modules.end() && j->first == client_id; + ++j) { + if (j->second->type() == type) { + return j->second; + } else if (j->second->type() == InputOutput) { + io_module = j->second; + } + } + + // Return InputOutput module for Input or Output, or NULL if not found + return io_module; +} + +PatchageModule* +AlsaDriver::find_or_create_module( + Patchage* patchage, + uint8_t client_id, + const std::string& client_name, + ModuleType type) +{ + PatchageModule* m = find_module(client_id, type); + if (!m) { + m = new PatchageModule(patchage, client_name, type); + m->load_location(); + _app->canvas()->add_module(client_name, m); + _modules.insert(std::make_pair(client_id, m)); + } + return m; +} + +void +AlsaDriver::create_port_view_internal( + Patchage* patchage, + snd_seq_addr_t addr, + PatchageModule*& m, + PatchagePort*& port) +{ + if (ignore(addr)) + return; + + snd_seq_client_info_t* cinfo; + snd_seq_client_info_alloca(&cinfo); + snd_seq_client_info_set_client(cinfo, addr.client); + snd_seq_get_any_client_info(_seq, addr.client, cinfo); + + snd_seq_port_info_t* pinfo; + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_client(pinfo, addr.client); + snd_seq_port_info_set_port(pinfo, addr.port); + snd_seq_get_any_port_info(_seq, addr.client, addr.port, pinfo); + + const string client_name = snd_seq_client_info_get_name(cinfo); + const string port_name = snd_seq_port_info_get_name(pinfo); + bool is_input = false; + bool is_duplex = false; + bool is_application = true; + + int caps = snd_seq_port_info_get_capability(pinfo); + int type = snd_seq_port_info_get_type(pinfo); + + // Figure out direction + if ((caps & SND_SEQ_PORT_CAP_READ) && (caps & SND_SEQ_PORT_CAP_WRITE)) + is_duplex = true; + else if (caps & SND_SEQ_PORT_CAP_READ) + is_input = false; + else if (caps & SND_SEQ_PORT_CAP_WRITE) + is_input = true; + + is_application = (type & SND_SEQ_PORT_TYPE_APPLICATION); + + // Because there would be name conflicts, we must force a split if (stupid) + // alsa duplex ports are present on the client + bool split = false; + if (is_duplex) { + split = true; + if (!_app->conf()->get_module_split(client_name, !is_application)) { + _app->conf()->set_module_split(client_name, true); + } + } else { + split = _app->conf()->get_module_split(client_name, !is_application); + } + + /*cout << "ALSA PORT: " << client_name << " : " << port_name + << " is_application = " << is_application + << " is_duplex = " << is_duplex + << " split = " << split << endl;*/ + + if (!split) { + m = find_or_create_module(_app, addr.client, client_name, InputOutput); + if (!m->get_port(port_name)) { + port = create_port(*m, port_name, is_input, addr); + port->show(); + } + + } else { // split + ModuleType type = ((is_input) ? Input : Output); + m = find_or_create_module(_app, addr.client, client_name, type); + if (!m->get_port(port_name)) { + port = create_port(*m, port_name, is_input, addr); + port->show(); + } + + if (is_duplex) { + type = ((!is_input) ? Input : Output); + m = find_or_create_module(_app, addr.client, client_name, type); + if (!m->get_port(port_name)) { + port = create_port(*m, port_name, !is_input, addr); + port->show(); + } + } + } +} + +PatchagePort* +AlsaDriver::create_port(PatchageModule& parent, + const string& name, bool is_input, snd_seq_addr_t addr) +{ + PatchagePort* ret = new PatchagePort( + parent, ALSA_MIDI, name, "", is_input, + _app->conf()->get_port_color(ALSA_MIDI), + _app->show_human_names()); + + dynamic_cast<PatchageCanvas*>(parent.canvas())->index_port( + PortID(addr, is_input), ret); + + _app->canvas()->index_port(PortID(addr, is_input), ret); + _port_addrs.insert(std::make_pair(ret, PortID(addr, is_input))); + return ret; +} + +bool +AlsaDriver::ignore(const snd_seq_addr_t& addr, bool add) +{ + if (_ignored.find(addr) != _ignored.end()) + return true; + + if (!add) + return false; + + snd_seq_client_info_t* cinfo; + snd_seq_client_info_alloca(&cinfo); + snd_seq_client_info_set_client(cinfo, addr.client); + snd_seq_get_any_client_info(_seq, addr.client, cinfo); + + snd_seq_port_info_t* pinfo; + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_client(pinfo, addr.client); + snd_seq_port_info_set_port(pinfo, addr.port); + snd_seq_get_any_port_info(_seq, addr.client, addr.port, pinfo); + + const int type = snd_seq_port_info_get_type(pinfo); + const int caps = snd_seq_port_info_get_capability(pinfo); + + if (caps & SND_SEQ_PORT_CAP_NO_EXPORT) { + _ignored.insert(addr); + return true; + } else if ( !( (caps & SND_SEQ_PORT_CAP_READ) + || (caps & SND_SEQ_PORT_CAP_WRITE) + || (caps & SND_SEQ_PORT_CAP_DUPLEX))) { + _ignored.insert(addr); + return true; + } else if ((snd_seq_client_info_get_type(cinfo) != SND_SEQ_USER_CLIENT) + && ((type == SND_SEQ_PORT_SYSTEM_TIMER + || type == SND_SEQ_PORT_SYSTEM_ANNOUNCE))) { + _ignored.insert(addr); + return true; + } + + return false; +} + +/** Connects two Alsa Midi ports. + * + * \return Whether connection succeeded. + */ +bool +AlsaDriver::connect(PatchagePort* src_port, + PatchagePort* dst_port) +{ + PortAddrs::const_iterator s = _port_addrs.find(src_port); + PortAddrs::const_iterator d = _port_addrs.find(dst_port); + + if (s == _port_addrs.end() || d == _port_addrs.end()) { + _app->error_msg("Alsa: Attempt to connect port with no address."); + return false; + } + + const PortID src = s->second; + const PortID dst = d->second; + + if (src.id.alsa_addr.client == dst.id.alsa_addr.client + && src.id.alsa_addr.port == dst.id.alsa_addr.port) { + _app->warning_msg("Alsa: Refusing to connect port to itself."); + return false; + } + + bool result = true; + + snd_seq_port_subscribe_t* subs; + snd_seq_port_subscribe_malloc(&subs); + snd_seq_port_subscribe_set_sender(subs, &src.id.alsa_addr); + snd_seq_port_subscribe_set_dest(subs, &dst.id.alsa_addr); + snd_seq_port_subscribe_set_exclusive(subs, 0); + snd_seq_port_subscribe_set_time_update(subs, 0); + snd_seq_port_subscribe_set_time_real(subs, 0); + + // Already connected (shouldn't happen) + if (!snd_seq_get_port_subscription(_seq, subs)) { + _app->error_msg("Alsa: Attempt to double subscribe ports."); + result = false; + } + + int ret = snd_seq_subscribe_port(_seq, subs); + if (ret < 0) { + _app->error_msg((format("Alsa: Subscription failed (%1%).") + % snd_strerror(ret)).str()); + result = false; + } + + if (result) + _app->info_msg(string("Alsa: Connected ") + + src_port->full_name() + " => " + dst_port->full_name()); + else + _app->error_msg(string("Alsa: Unable to connect ") + + src_port->full_name() + " => " + dst_port->full_name()); + + return (!result); +} + +/** Disconnects two Alsa Midi ports. + * + * \return Whether disconnection succeeded. + */ +bool +AlsaDriver::disconnect(PatchagePort* src_port, + PatchagePort* dst_port) +{ + PortAddrs::const_iterator s = _port_addrs.find(src_port); + PortAddrs::const_iterator d = _port_addrs.find(dst_port); + + if (s == _port_addrs.end() || d == _port_addrs.end()) { + _app->error_msg("Alsa: Attempt to connect port with no address"); + return false; + } + + const PortID src = s->second; + const PortID dst = d->second; + + snd_seq_port_subscribe_t* subs; + snd_seq_port_subscribe_malloc(&subs); + snd_seq_port_subscribe_set_sender(subs, &src.id.alsa_addr); + snd_seq_port_subscribe_set_dest(subs, &dst.id.alsa_addr); + snd_seq_port_subscribe_set_exclusive(subs, 0); + snd_seq_port_subscribe_set_time_update(subs, 0); + snd_seq_port_subscribe_set_time_real(subs, 0); + + // Not connected (shouldn't happen) + if (snd_seq_get_port_subscription(_seq, subs) != 0) { + _app->error_msg("Alsa: Attempt to unsubscribe ports that are not subscribed."); + return false; + } + + int ret = snd_seq_unsubscribe_port(_seq, subs); + if (ret < 0) { + _app->error_msg(string("Alsa: Unable to disconnect ") + + src_port->full_name() + " => " + dst_port->full_name() + + "(" + snd_strerror(ret) + ")"); + return false; + } + + _app->info_msg(string("Alsa: Disconnected ") + + src_port->full_name() + " => " + dst_port->full_name()); + + return true; +} + +bool +AlsaDriver::create_refresh_port() +{ + snd_seq_port_info_t* port_info; + snd_seq_port_info_alloca(&port_info); + snd_seq_port_info_set_name(port_info, "System Announcement Reciever"); + snd_seq_port_info_set_type(port_info, SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_capability(port_info, + SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE|SND_SEQ_PORT_CAP_NO_EXPORT); + + int ret = snd_seq_create_port(_seq, port_info); + if (ret) { + _app->error_msg((format("Alsa: Error creating port (%1%): ") + % snd_strerror(ret)).str()); + return false; + } + + // Subscribe the port to the system announcer + ret = snd_seq_connect_from(_seq, + snd_seq_port_info_get_port(port_info), + SND_SEQ_CLIENT_SYSTEM, + SND_SEQ_PORT_SYSTEM_ANNOUNCE); + if (ret) { + _app->error_msg((format("Alsa: Failed to connect to system announce port (%1%)") + % snd_strerror(ret)).str()); + return false; + } + + return true; +} + +void* +AlsaDriver::refresh_main(void* me) +{ + AlsaDriver* ad = (AlsaDriver*)me; + ad->_refresh_main(); + return NULL; +} + +void +AlsaDriver::_refresh_main() +{ + if (!create_refresh_port()) { + _app->error_msg("Alsa: Could not create listen port, auto-refresh disabled."); + return; + } + + int caps = 0; + + snd_seq_client_info_t* cinfo; + snd_seq_client_info_alloca(&cinfo); + + snd_seq_port_info_t* pinfo; + snd_seq_port_info_alloca(&pinfo); + + snd_seq_event_t* ev; + while (snd_seq_event_input(_seq, &ev) > 0) { + assert(ev); + + Glib::Mutex::Lock lock(_events_mutex); + + switch (ev->type) { + case SND_SEQ_EVENT_PORT_SUBSCRIBED: + if (!ignore(ev->data.connect.sender) && !ignore(ev->data.connect.dest)) + _events.push(PatchageEvent(PatchageEvent::CONNECTION, + ev->data.connect.sender, ev->data.connect.dest)); + break; + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: + if (!ignore(ev->data.connect.sender) && !ignore(ev->data.connect.dest)) + _events.push(PatchageEvent(PatchageEvent::DISCONNECTION, + ev->data.connect.sender, ev->data.connect.dest)); + break; + case SND_SEQ_EVENT_PORT_START: + snd_seq_get_any_client_info(_seq, ev->data.addr.client, cinfo); + snd_seq_get_any_port_info(_seq, ev->data.addr.client, ev->data.addr.port, pinfo); + caps = snd_seq_port_info_get_capability(pinfo); + + if (!ignore(ev->data.addr)) + _events.push(PatchageEvent(PatchageEvent::PORT_CREATION, + PortID(ev->data.addr, (caps & SND_SEQ_PORT_CAP_READ)))); + break; + case SND_SEQ_EVENT_PORT_EXIT: + if (!ignore(ev->data.addr, false)) { + // Note: getting caps at this point does not work + // Delete both inputs and outputs (in case this is a duplex port) + _events.push(PatchageEvent(PatchageEvent::PORT_DESTRUCTION, + PortID(ev->data.addr, true))); + _events.push(PatchageEvent(PatchageEvent::PORT_DESTRUCTION, + PortID(ev->data.addr, false))); + _port_addrs.erase(_app->canvas()->find_port( + PortID(ev->data.addr, false))); + _port_addrs.erase(_app->canvas()->find_port( + PortID(ev->data.addr, true))); + } + break; + case SND_SEQ_EVENT_CLIENT_CHANGE: + case SND_SEQ_EVENT_CLIENT_EXIT: + case SND_SEQ_EVENT_CLIENT_START: + case SND_SEQ_EVENT_PORT_CHANGE: + case SND_SEQ_EVENT_RESET: + default: + //_events.push(PatchageEvent(PatchageEvent::REFRESH)); + break; + } + } +} + +void +AlsaDriver::process_events(Patchage* app) +{ + Glib::Mutex::Lock lock(_events_mutex); + while (!_events.empty()) { + PatchageEvent& ev = _events.front(); + ev.execute(app); + _events.pop(); + } +} |