summaryrefslogtreecommitdiffstats
path: root/src/AlsaDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/AlsaDriver.cpp')
-rw-r--r--src/AlsaDriver.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp
new file mode 100644
index 0000000..2f4d52e
--- /dev/null
+++ b/src/AlsaDriver.cpp
@@ -0,0 +1,484 @@
+/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om 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 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <string>
+#include <iostream>
+#include <cassert>
+#include <sys/poll.h>
+#include <errno.h>
+#include "PatchageFlowCanvas.h"
+#include "AlsaDriver.h"
+#include "Patchage.h"
+#include "PatchageModule.h"
+#include "PatchagePort.h"
+
+using std::cerr;
+using std::string;
+
+
+using namespace LibFlowCanvas;
+
+AlsaDriver::AlsaDriver(Patchage* app)
+: m_app(app),
+ m_canvas(app->canvas()),
+ m_seq(NULL)
+{
+}
+
+
+AlsaDriver::~AlsaDriver()
+{
+ detach();
+}
+
+
+/** Attach to ALSA.
+ * @a launch_daemon is ignored, as ALSA has no daemon to launch/connect to.
+ */
+void
+AlsaDriver::attach(bool launch_daemon)
+{
+ cout << "Connecting to Alsa... ";
+ cout.flush();
+
+ int ret = snd_seq_open(&m_seq, "default",
+ SND_SEQ_OPEN_DUPLEX,
+ SND_SEQ_NONBLOCK);
+ if (ret) {
+ cout << "Failed" << endl;
+ m_seq = NULL;
+ } else {
+ cout << "Connected" << endl;
+
+ snd_seq_set_client_name(m_seq, "Patchage");
+
+ ret = pthread_create(&m_refresh_thread, NULL, &AlsaDriver::refresh_main, this);
+ if (ret)
+ cerr << "Couldn't start refresh thread" << endl;
+ }
+}
+
+
+void
+AlsaDriver::detach()
+{
+ if (m_seq != NULL) {
+ pthread_cancel(m_refresh_thread);
+ pthread_join(m_refresh_thread, NULL);
+ snd_seq_close(m_seq);
+ m_seq = NULL;
+ cout << "Disconnected from Alsa" << endl;
+ }
+}
+
+
+/** Refresh all Alsa Midi ports and connections.
+ */
+void
+AlsaDriver::refresh()
+{
+ if (!is_attached())
+ return;
+
+ assert(m_seq);
+
+ refresh_ports();
+ refresh_connections();
+
+ undirty();
+}
+
+
+/** Refresh all Alsa Midi ports.
+ */
+void
+AlsaDriver::refresh_ports()
+{
+ assert(is_attached());
+ assert(m_seq);
+
+ 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);
+
+ string client_name;
+ string port_name;
+ bool is_input = false;
+ bool is_duplex = false;
+ bool is_application = true;
+
+ while (snd_seq_query_next_client (m_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);
+
+ client_name = snd_seq_client_info_get_name(cinfo);
+
+ while (snd_seq_query_next_port(m_seq, pinfo) >= 0) {
+ int caps = snd_seq_port_info_get_capability(pinfo);
+ int type = snd_seq_port_info_get_type(pinfo);
+
+ // Skip ports we shouldn't show
+ if (caps & SND_SEQ_PORT_CAP_NO_EXPORT)
+ continue;
+ else if ( !( (caps & SND_SEQ_PORT_CAP_READ)
+ || (caps & SND_SEQ_PORT_CAP_WRITE)
+ || (caps & SND_SEQ_PORT_CAP_DUPLEX)))
+ continue;
+ 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)))
+ continue;
+
+ const snd_seq_addr_t addr = *snd_seq_port_info_get_addr(pinfo);
+
+ is_duplex = false;
+
+ // FIXME: Should be CAP_SUBS_READ etc?
+ 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);
+ port_name = snd_seq_port_info_get_name(pinfo);
+ PatchageModule* m = NULL;
+
+ //cout << client_name << " : " << port_name << " is_application = " << is_application
+ // << " is_duplex = " << is_duplex << endl;
+
+ bool split = m_app->state_manager()->get_module_split(client_name, !is_application);
+
+ // Application input/output ports go on the same module
+ if (!split) {
+ m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput);
+ if (m == NULL) {
+ m = new PatchageModule(m_app, client_name, InputOutput);
+ m->load_location();
+ m->store_location();
+ m_canvas->add_module(m);
+ }
+
+ if (!is_duplex) {
+ m->add_patchage_port(port_name, is_input, ALSA_MIDI, addr);
+ } else {
+ m->add_patchage_port(port_name, true, ALSA_MIDI, addr);
+ m->add_patchage_port(port_name, false, ALSA_MIDI, addr);
+ }
+ } else { // non-application input/output ports (hw interface, etc) go on separate modules
+ PatchageModule* m = NULL;
+ ModuleType type = InputOutput;
+
+ // The 'application' hint isn't always set by clients, so this bit
+ // is pretty nasty...
+
+ if (!is_duplex) { // just one port to add
+ if (is_input) type = Input;
+ else type = Output;
+
+ // See if an InputOutput module exists (maybe with Jack ports on it)
+ m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput);
+
+ if (m == NULL)
+ m = (PatchageModule*)m_canvas->find_module(client_name, type);
+ if (m == NULL) {
+ m = new PatchageModule(m_app, client_name, type);
+ m->load_location();
+ m->store_location();
+ m_canvas->add_module(m);
+ }
+ m->add_patchage_port(port_name, is_input, ALSA_MIDI, addr);
+ } else { // two ports to add
+ type = Input;
+
+ // See if an InputOutput module exists (maybe with Jack ports on it)
+ m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput);
+
+ if (m == NULL)
+ m = (PatchageModule*)m_canvas->find_module(client_name, type);
+ if (m == NULL) {
+ m = new PatchageModule(m_app, client_name, type);
+ m->load_location();
+ m->store_location();
+ m_canvas->add_module(m);
+ }
+ m->add_patchage_port(port_name, true, ALSA_MIDI, addr);
+
+ type = Output;
+
+ // See if an InputOutput module exists (maybe with Jack ports on it)
+ m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput);
+
+ if (m == NULL)
+ m = (PatchageModule*)m_canvas->find_module(client_name, type);
+ if (m == NULL) {
+ m = new PatchageModule(m_app, client_name, type);
+ m->load_location();
+ m->store_location();
+ m_canvas->add_module(m);
+ }
+ m->add_patchage_port(port_name, false, ALSA_MIDI, addr);
+ }
+ }
+ }
+ }
+}
+
+
+/** Refresh all Alsa Midi connections.
+ */
+void
+AlsaDriver::refresh_connections()
+{
+ assert(is_attached());
+ assert(m_seq);
+
+ PatchageModule* m = NULL;
+ PatchagePort* p = NULL;
+
+ for (ModuleMap::iterator i = m_canvas->modules().begin();
+ i != m_canvas->modules().end(); ++i) {
+ m = (PatchageModule*)((*i).second);
+ for (PortList::iterator j = m->ports().begin(); j != m->ports().end(); ++j) {
+ p = (PatchagePort*)(*j);
+ if (p->type() == ALSA_MIDI)
+ add_connections(p);
+ }
+ }
+}
+
+
+/** Add all connections for the given port.
+ */
+void
+AlsaDriver::add_connections(PatchagePort* port)
+{
+ assert(is_attached());
+ assert(m_seq);
+
+ const snd_seq_addr_t* addr = port->alsa_addr();
+ PatchagePort* connected_port = NULL;
+
+ // Fix a problem with duplex->duplex connections (would show up twice)
+ // No sense doing them all twice anyway..
+ if (port->is_input())
+ return;
+
+ 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(m_seq, subsinfo)) {
+ const snd_seq_addr_t* connected_addr = snd_seq_query_subscribe_get_addr(subsinfo);
+
+ connected_port = m_canvas->find_port(connected_addr, !port->is_input());
+
+ if (connected_port != NULL) {
+ m_canvas->add_connection(port, connected_port);
+ }
+
+ snd_seq_query_subscribe_set_index(subsinfo, snd_seq_query_subscribe_get_index(subsinfo) + 1);
+ }
+
+}
+
+
+/** Connects two Alsa Midi ports.
+ *
+ * \return Whether connection succeeded.
+ */
+bool
+AlsaDriver::connect(const PatchagePort* const src_port, const PatchagePort* const dst_port)
+{
+ const snd_seq_addr_t* src = src_port->alsa_addr();
+ const snd_seq_addr_t* dst = dst_port->alsa_addr();
+
+ bool result = false;
+
+ if (src && dst) {
+ snd_seq_port_subscribe_t* subs;
+ snd_seq_port_subscribe_malloc(&subs);
+ snd_seq_port_subscribe_set_sender(subs, src);
+ snd_seq_port_subscribe_set_dest(subs, dst);
+ 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(m_seq, subs)) {
+ cerr << "Error: Attempt to subscribe Alsa ports that are already subscribed." << endl;
+ result = false;
+ }
+
+ int ret = snd_seq_subscribe_port(m_seq, subs);
+ if (ret < 0) {
+ cerr << "Alsa subscription failed: " << snd_strerror(ret) << endl;
+ result = false;
+ }
+ }
+
+ return (!result);
+}
+
+
+/** Disconnects two Alsa Midi ports.
+ *
+ * \return Whether disconnection succeeded.
+ */
+bool
+AlsaDriver::disconnect(const PatchagePort* const src_port, const PatchagePort* const dst_port)
+{
+ const snd_seq_addr_t* src = src_port->alsa_addr();
+ const snd_seq_addr_t* dst = dst_port->alsa_addr();
+
+ bool result = false;
+
+ snd_seq_port_subscribe_t* subs;
+ snd_seq_port_subscribe_malloc(&subs);
+ snd_seq_port_subscribe_set_sender(subs, src);
+ snd_seq_port_subscribe_set_dest(subs, dst);
+ 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(m_seq, subs) != 0) {
+ cerr << "Error: Attempt to unsubscribe Alsa ports that are not subscribed." << endl;
+ result = false;
+ }
+
+ int ret = snd_seq_unsubscribe_port(m_seq, subs);
+ if (ret < 0) {
+ cerr << "Alsa unsubscription failed: " << snd_strerror(ret) << endl;
+ result = false;
+ }
+
+ return (!result);
+}
+
+
+bool
+AlsaDriver::create_refresh_port()
+{
+ // Mostly lifted from alsa-patch-bay, (C) 2002 Robert Ham, released under GPL
+
+ int ret;
+ 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_capability(port_info,
+ SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE|SND_SEQ_PORT_CAP_NO_EXPORT);
+
+ snd_seq_port_info_set_type(port_info, SND_SEQ_PORT_TYPE_APPLICATION);
+
+ ret = snd_seq_create_port(m_seq, port_info);
+ if (ret) {
+ cerr << "Error creating alsa port: " << snd_strerror(ret) << endl;
+ return false;
+ }
+
+ // Subscribe the port to the system announcer
+ ret = snd_seq_connect_from(m_seq,
+ snd_seq_port_info_get_port(port_info),
+ SND_SEQ_CLIENT_SYSTEM,
+ SND_SEQ_PORT_SYSTEM_ANNOUNCE);
+
+ if (ret) {
+ cerr << "Could not connect to system announcer port: " << snd_strerror(ret) << endl;
+ return false;
+ }
+
+ return true;
+}
+
+
+void*
+AlsaDriver::refresh_main(void* me)
+{
+ AlsaDriver* ad = (AlsaDriver*)me;
+ ad->m_refresh_main();
+ return NULL;
+}
+
+
+void
+AlsaDriver::m_refresh_main()
+{
+ // "Heavily influenced" from alsa-patch-bay
+ // (C) 2002 Robert Ham, released under GPL
+
+ int ret;
+ int nfds = snd_seq_poll_descriptors_count(m_seq, POLLIN);
+ struct pollfd* pfds = new struct pollfd[nfds];
+ unsigned short* revents = new unsigned short[nfds];
+
+ if (!create_refresh_port()) {
+ cerr << "Could not create Alsa listen port. Auto refreshing will not work." << endl;
+ return;
+ }
+
+ snd_seq_poll_descriptors(m_seq, pfds, nfds, POLLIN);
+
+ while (true) {
+ ret = poll(pfds, nfds, -1);
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+
+ cerr << "Error polling Alsa sequencer: " << strerror(errno) << endl;
+ continue;
+ }
+
+ ret = snd_seq_poll_descriptors_revents(m_seq, pfds, nfds, revents);
+ if (ret) {
+ cerr << "Error getting Alsa sequencer poll events: "
+ << snd_strerror(ret) << endl;
+ continue;
+ }
+
+ for (int i = 0; i < nfds; ++i) {
+ if (revents[i] > 0) {
+ snd_seq_event_t* ev;
+ snd_seq_event_input(m_seq, &ev);
+
+ if (ev == NULL)
+ continue;
+
+ switch (ev->type) {
+ case SND_SEQ_EVENT_RESET:
+ case SND_SEQ_EVENT_CLIENT_START:
+ case SND_SEQ_EVENT_CLIENT_EXIT:
+ case SND_SEQ_EVENT_CLIENT_CHANGE:
+ case SND_SEQ_EVENT_PORT_START:
+ case SND_SEQ_EVENT_PORT_EXIT:
+ case SND_SEQ_EVENT_PORT_CHANGE:
+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+ m_is_dirty = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}