summaryrefslogtreecommitdiffstats
path: root/src/AlsaDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/AlsaDriver.cpp')
-rw-r--r--src/AlsaDriver.cpp585
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();
+ }
+}