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

AlsaDriver.cpp

/* 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 <sys/poll.h>
#include <errno.h>
#include <string>
#include <iostream>
#include "PatchagePatchBayArea.h"
#include "AlsaDriver.h"
#include "Patchage.h"
#include "PatchageModule.h"
#include "PatchagePort.h"

using std::cerr;
using std::string;


using namespace PatchBay;

AlsaDriver::AlsaDriver(Patchage* app, pthread_mutex_t* refresh_mutex)
: m_app(app),
  m_patch_bay(app->patch_bay()),
  m_refresh_mutex(refresh_mutex)
{
}


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


void
AlsaDriver::attach()
{
      cout << "Connecting to Alsa... ";

      int ret = snd_seq_open(&m_seq, "default",
                             SND_SEQ_OPEN_DUPLEX,
                             SND_SEQ_NONBLOCK);
      if (ret) {
            cerr << "Error: Couldn't open Alsa sequencer" << endl;
            throw;
      } else {
            cout << "Done" << 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.
 * 
 * This is to be called from Patchage/PatchBayArea, not from AlsaDriver.
 */
void
00088 AlsaDriver::refresh()
{
      refresh_ports();
      refresh_connections();
}


/** Refresh all Alsa Midi ports. 
 */
void
00098 AlsaDriver::refresh_ports()
{
      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;
      bool is_duplex;
      bool is_application;
      
      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 (type == SND_SEQ_PORT_SYSTEM_TIMER)
                        continue;
                  else if (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;

                  // Application input/output ports go on the same module
                  if (is_application) {
                        m = (PatchageModule*)m_patch_bay->find_module(client_name, InputOutput);
                        if (m == NULL) {
                              m = new PatchageModule(m_app, client_name, InputOutput);
                              m->load_location();
                              m->store_location();
                              m_patch_bay->add_module(m);
                        }
                        
                        if (!is_duplex) {
                              m->add_patchage_port(port_name, is_input, ALSA, addr);
                        } else {
                              m->add_patchage_port(port_name, true, ALSA, addr);
                              m->add_patchage_port(port_name, false, ALSA, 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_patch_bay->find_module(client_name, InputOutput);
                        
                              if (m == NULL)
                                    m = (PatchageModule*)m_patch_bay->find_module(client_name, type);
                              if (m == NULL) {
                                    m = new PatchageModule(m_app, client_name, type);
                                    m->load_location();
                                    m->store_location();
                                    m_patch_bay->add_module(m);
                              }
                              m->add_patchage_port(port_name, is_input, ALSA, addr);
                        } else {  // two ports to add
                              type = Input;
                              
                              // See if an InputOutput module exists (maybe with Jack ports on it)
                              m = (PatchageModule*)m_patch_bay->find_module(client_name, InputOutput);
                              
                              if (m == NULL)
                                    m = (PatchageModule*)m_patch_bay->find_module(client_name, type);
                              if (m == NULL) {
                                    m = new PatchageModule(m_app, client_name, type);
                                    m->load_location();
                                    m->store_location();
                                    m_patch_bay->add_module(m);
                              }
                              m->add_patchage_port(port_name, true, ALSA, addr);

                              type = Output;
                              
                              // See if an InputOutput module exists (maybe with Jack ports on it)
                              m = (PatchageModule*)m_patch_bay->find_module(client_name, InputOutput);
                              
                              if (m == NULL)
                                    m = (PatchageModule*)m_patch_bay->find_module(client_name, type);
                              if (m == NULL) {
                                    m = new PatchageModule(m_app, client_name, type);
                                    m->load_location();
                                    m->store_location();
                                    m_patch_bay->add_module(m);
                              }
                              m->add_patchage_port(port_name, false, ALSA, addr);
                        }
                  }
            }
      }
}


/** Refresh all Alsa Midi connections.
 */
void
00233 AlsaDriver::refresh_connections()
{
      PatchageModule* m = NULL;
      PatchagePort*   p = NULL;
      
      for (ModuleMap::iterator i = m_patch_bay->modules().begin();
                  i != m_patch_bay->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)
                        add_connections(p);
            }
      }
}


/** Add all connections for the given port.
 */
void
00253 AlsaDriver::add_connections(PatchagePort* port)
{
      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_patch_bay->find_port(connected_addr, !port->is_input());

            if (connected_port != NULL) {
                  m_patch_bay->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
00288 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;
      pthread_mutex_lock(m_refresh_mutex);

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

      pthread_mutex_unlock(m_refresh_mutex);
      return (!result);
}


/** Disconnects two Alsa Midi ports.
 * 
 * \return Whether disconnection succeeded.
 */
bool
00326 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;
      pthread_mutex_lock(m_refresh_mutex);

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

      pthread_mutex_unlock(m_refresh_mutex);
      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()
{
      // Mostly lifted from alsa-patch-bay, (C) 2002 Robert Ham, released under GPL
      // (Though it looks like that bit was just taken from somewhere else anyway...)

      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, 1000);
            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;
                        int err;

                        // FIXME: catch errors here

                        err = snd_seq_event_input(m_seq, &ev);

                        if (ev == NULL)
                              continue;

                        switch (ev->type) {
                        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_app->queue_refresh();
                              break;
                        default:
                              break;
                        }
                  }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index