Logo Search packages:      
Sourcecode: patchage version File versions  Download package

AlsaDriver.cpp

/* This file is part of Patchage.
 * Copyright (C) 2007 Dave 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 2 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include <string>
#include <set>
#include <iostream>
#include <cassert>
#include <raul/SharedPtr.hpp>
#include "PatchageCanvas.hpp"
#include "AlsaDriver.hpp"
#include "Patchage.hpp"
#include "PatchageModule.hpp"
#include "PatchagePort.hpp"

using namespace std;
using namespace FlowCanvas;

AlsaDriver::AlsaDriver(Patchage* app)
      : Driver(128)
      , _app(app)
      , _seq(NULL)
{
}


AlsaDriver::~AlsaDriver() 
{
      detach();
}


/** Attach to ALSA.
 * @a launch_daemon is ignored, as ALSA has no daemon to launch/connect to.
 */
void
00050 AlsaDriver::attach(bool /*launch_daemon*/)
{
      int ret = snd_seq_open(&_seq, "default",
                             SND_SEQ_OPEN_DUPLEX,
                             SND_SEQ_NONBLOCK);
      if (ret) {
            _app->status_msg("[ALSA] Unable to attach");
            _seq = NULL;
      } else {
            _app->status_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)
                  cerr << "Couldn't start refresh thread" << endl;
            
            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->status_msg("[ALSA] Detached");
      }
}


/** Refresh all Alsa Midi ports and connections.
 */
void
00093 AlsaDriver::refresh()
{
      if (!is_attached())
            return;

      assert(_seq);
      
      refresh_ports();
      refresh_connections();
}

      
boost::shared_ptr<PatchagePort>
AlsaDriver::create_port_view(Patchage*     patchage,
                             const PortID& id)
{
      return boost::shared_ptr<PatchagePort>();
}


boost::shared_ptr<PatchagePort>
AlsaDriver::create_port(boost::shared_ptr<PatchageModule> parent,
            const string& name, bool is_input, snd_seq_addr_t addr)
{
      boost::shared_ptr<PatchagePort> ret(
            new PatchagePort(parent, ALSA_MIDI, name, is_input,
                  _app->state_manager()->get_port_color(ALSA_MIDI)));
      ret->alsa_addr(addr);
      return ret;
}


/** Refresh all Alsa Midi ports. 
 */
void
00128 AlsaDriver::refresh_ports()
{
      assert(is_attached());
      assert(_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;
      bool need_refresh   = false;

      set< boost::shared_ptr<PatchageModule> > to_resize;

      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);

            client_name = snd_seq_client_info_get_name(cinfo);

            while (snd_seq_query_next_port(_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);
                  boost::shared_ptr<PatchageModule> m;

                  bool split = false;

                  // Because there would be name conflicts, we must force a split if (stupid)
                  // alsa duplex ports are present on the client
                  if (is_duplex) {
                        split = true;
                        if (!_app->state_manager()->get_module_split(client_name, !is_application)) {
                              need_refresh = true;
                              _app->state_manager()->set_module_split(client_name, true);
                        }
                  } else {
                        split = _app->state_manager()->get_module_split(client_name, !is_application);
                  }
                  
                  /*cout << "SHOW: " << client_name << " : " << port_name
                        << " is_application = " << is_application
                        << " is_duplex = " << is_duplex
                        << ", split = " << split << endl;*/
                  
                  // Application input/output ports go on the same module
                  if (!split) {
                        m = _app->canvas()->find_module(client_name, InputOutput);
                        if (!m) {
                              m = boost::shared_ptr<PatchageModule>(
                                          new PatchageModule(_app, client_name, InputOutput));
                              m->load_location();
                              _app->canvas()->add_item(m);
                        }
                        
                        if (!m->get_port(port_name)) {
                              if (!is_duplex) {
                                    m->add_port(create_port(m, port_name, is_input, addr));
                              } else {
                                    m->add_port(create_port(m, port_name, true, addr));
                                    m->add_port(create_port(m, port_name, false, addr));
                              }
                              to_resize.insert(m);
                        }

                  } else { // non-application input/output ports (hw interface, etc) go on separate modules
                        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

                              type = ((is_input) ? Input : Output);
                              
                              m = _app->canvas()->find_module(client_name, type);
                        
                              if (!m) {
                                    m = boost::shared_ptr<PatchageModule>(
                                          new PatchageModule(_app, client_name, type));
                                    m->load_location();
                                    _app->canvas()->add_item(m);
                              }
                              
                              if (!m->get_port(port_name)) {
                                    m->add_port(create_port(m, port_name, is_input, addr));
                                    to_resize.insert(m);
                              }

                        } else {  // two ports to add
                              type = Input;
                              
                              m = _app->canvas()->find_module(client_name, type);

                              if (!m) {
                                    m = boost::shared_ptr<PatchageModule>(
                                          new PatchageModule(_app, client_name, type));
                                    m->load_location();
                                    _app->canvas()->add_item(m);
                              }

                              assert(m);

                              if (!m->get_port(port_name)) {
                                    m->add_port(create_port(m, port_name, true, addr));
                                    to_resize.insert(m);
                              }

                              type = Output;
                              
                              m = _app->canvas()->find_module(client_name, type);

                              if (!m) {
                                    m = boost::shared_ptr<PatchageModule>(
                                          new PatchageModule(_app, client_name, type));
                                    m->load_location();
                                    _app->canvas()->add_item(m);
                              }

                              if (!m->get_port(port_name)) {
                                    m->add_port(create_port(m, port_name, false, addr));
                                    to_resize.insert(m);
                              }
                        }
                  }
            }
      }

      if (need_refresh) {
            _app->refresh();
      } else {
            for (set< boost::shared_ptr<PatchageModule> >::iterator i = to_resize.begin();
                        i != to_resize.end(); ++i) {
                  (*i)->resize();
            }
      }
}


/** Refresh all Alsa Midi connections.
 */
void
00303 AlsaDriver::refresh_connections()
{
      assert(is_attached());
      assert(_seq);
      
      boost::shared_ptr<PatchageModule> m;
      boost::shared_ptr<PatchagePort>   p;
      
      for (ItemList::iterator i = _app->canvas()->items().begin();
                  i != _app->canvas()->items().end(); ++i) {
            m = boost::dynamic_pointer_cast<PatchageModule>(*i);
            if (m) {
                  for (PortVector::const_iterator j = m->ports().begin(); j != m->ports().end(); ++j) {
                        p = boost::dynamic_pointer_cast<PatchagePort>(*j);
                        if (p->type() == ALSA_MIDI)
                              add_connections(p);
                  }
            }
      }
}


/** Add all connections for the given port.
 */
void
00328 AlsaDriver::add_connections(boost::shared_ptr<PatchagePort> port)
{
      assert(is_attached());
      assert(_seq);
      
      const snd_seq_addr_t* addr = port->alsa_addr();
      boost::shared_ptr<PatchagePort> connected_port;
      
      // 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(_seq, subsinfo)) {
            const snd_seq_addr_t* connected_addr = snd_seq_query_subscribe_get_addr(subsinfo);
            if (!connected_addr)
                  continue;
            
            PortID id(*connected_addr, true);
            connected_port = _app->canvas()->find_port(id);

            if (connected_port && !port->is_connected_to(connected_port))
                  _app->canvas()->add_connection(port, connected_port, port->color() + 0x22222200);

            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
00368 AlsaDriver::connect(boost::shared_ptr<PatchagePort> src_port, boost::shared_ptr<PatchagePort> dst_port) 
{
      const snd_seq_addr_t* src = src_port->alsa_addr();
      const snd_seq_addr_t* dst = dst_port->alsa_addr();

      bool result = true;
      
      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(_seq, subs)) {
                  cerr << "Error: Attempt to subscribe Alsa ports that are already subscribed." << endl;
                  result = false;
            }

            int ret = snd_seq_subscribe_port(_seq, subs);
            if (ret < 0) {
                  cerr << "Alsa subscription failed: " << snd_strerror(ret) << endl;
                  result = false;
            }
      }
      
      if (result)
            _app->status_msg(string("[ALSA] Connected ")
                  + src_port->full_name() + " -> " + dst_port->full_name());
      else
            _app->status_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
00413 AlsaDriver::disconnect(boost::shared_ptr<PatchagePort> src_port, boost::shared_ptr<PatchagePort> dst_port) 
{
      const snd_seq_addr_t* src = src_port->alsa_addr();
      const snd_seq_addr_t* dst = dst_port->alsa_addr();
      
      bool result = true;

      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(_seq, subs) != 0) {
            cerr << "Error: Attempt to unsubscribe Alsa ports that are not subscribed." << endl;
            result = false;
      }
      
      int ret = snd_seq_unsubscribe_port(_seq, subs);
      if (ret < 0) {
            cerr << "Alsa unsubscription failed: " << snd_strerror(ret) << endl;
            result = false;
      }
      
      if (result)
            _app->status_msg(string("[ALSA] Disconnected ")
                  + src_port->full_name() + " -> " + dst_port->full_name());
      else
            _app->status_msg(string("[ALSA] Unable to disconnect ")
                  + src_port->full_name() + " -> " + dst_port->full_name());

      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(_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(_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->_refresh_main();
      return NULL;
}


void
AlsaDriver::_refresh_main()
{
      // "Heavily influenced" from alsa-patch-bay
      // (C) 2002 Robert Ham, released under GPL
      
      if (!create_refresh_port()) {
            cerr << "Could not create Alsa listen port.  Auto refreshing will not work." << endl;
            return;
      }

      int             ret;
      int             nfds    = snd_seq_poll_descriptors_count(_seq, POLLIN);
      struct pollfd*  pfds    = new struct pollfd[nfds];
      unsigned short* revents = new unsigned short[nfds];

      snd_seq_poll_descriptors(_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(_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(_seq, &ev);

                        if (ev == NULL)
                              continue;

                        switch (ev->type) {
                        case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                              _events.push(PatchageEvent(PatchageEvent::CONNECTION,
                                                ev->data.connect.sender, ev->data.connect.dest));
                              break;
                        case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
                              _events.push(PatchageEvent(PatchageEvent::DISCONNECTION,
                                                ev->data.connect.sender, ev->data.connect.dest));
                              break;
                        case SND_SEQ_EVENT_PORT_START:
                        case SND_SEQ_EVENT_PORT_EXIT:
                        case SND_SEQ_EVENT_PORT_CHANGE:
                        case SND_SEQ_EVENT_CLIENT_START:
                        case SND_SEQ_EVENT_CLIENT_EXIT:
                        case SND_SEQ_EVENT_CLIENT_CHANGE:
                        case SND_SEQ_EVENT_RESET:
                        default:
                              // FIXME: Ultra slow kludge, use proper find-grained events
                              _events.push(PatchageEvent(PatchageEvent::REFRESH));
                        }
                  }
            }
      }

      delete[] pfds;
      delete[] revents;
}


void
AlsaDriver::print_addr(snd_seq_addr_t addr)
{
      cout << (int)addr.client << ":" << (int)addr.port << endl;
}

Generated by  Doxygen 1.6.0   Back to index