// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-

/*
    Rosegarden
    A sequencer and musical notation editor.
 
    This program is Copyright 2000-2007
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <bownie@bownie.com>
 
    The moral right of the authors to claim authorship of this work
    has been asserted.
 
    This program 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.  See the file
    COPYING included with this distribution for more information.
*/

#include <iostream>
#include "misc/Debug.h"
#include <cstdlib>
#include <cstdio>
#include <algorithm>

#ifdef HAVE_ALSA

// ALSA
#include <alsa/asoundlib.h>
#include <alsa/seq_event.h>
#include <alsa/version.h>
#include <alsa/seq.h>

#include "AlsaDriver.h"
#include "AlsaPort.h"
#include "ExternalTransport.h"
#include "MappedInstrument.h"
#include "Midi.h"
#include "MappedStudio.h"
#include "misc/Strings.h"
#include "MappedCommon.h"
#include "MappedEvent.h"
#include "Audit.h"
#include "AudioPlayQueue.h"
#include "ExternalTransport.h"

#include <qregexp.h>
#include <pthread.h>


//#define DEBUG_ALSA 1
//#define DEBUG_PROCESS_MIDI_OUT 1
//#define MTC_DEBUG 1

// This driver implements MIDI in and out via the ALSA (www.alsa-project.org)
// sequencer interface.

using std::cerr;
using std::endl;

static size_t _debug_jack_frame_count = 0;

#define AUTO_TIMER_NAME "(auto)"


namespace Rosegarden
{

#define FAILURE_REPORT_COUNT 256
static MappedEvent::FailureCode _failureReports[FAILURE_REPORT_COUNT];
static int _failureReportWriteIndex = 0;
static int _failureReportReadIndex = 0;

AlsaDriver::AlsaDriver(MappedStudio *studio):
        SoundDriver(studio, std::string("alsa-lib version ") +
                    std::string(SND_LIB_VERSION_STR)),
        m_client( -1),
        m_inputPort( -1),
        m_syncOutputPort( -1),
        m_controllerPort( -1),
        m_queue( -1),
        m_maxClients( -1),
        m_maxPorts( -1),
        m_maxQueues( -1),
        m_midiInputPortConnected(false),
        m_midiSyncAutoConnect(false),
        m_alsaPlayStartTime(0, 0),
        m_alsaRecordStartTime(0, 0),
        m_loopStartTime(0, 0),
        m_loopEndTime(0, 0),
        m_eat_mtc(0),
        m_looping(false),
        m_haveShutdown(false)
#ifdef HAVE_LIBJACK
        , m_jackDriver(0)
#endif
        , m_queueRunning(false)
        , m_portCheckNeeded(false),
        m_needJackStart(NeedNoJackStart),
        m_doTimerChecks(false),
        m_firstTimerCheck(true),
        m_timerRatio(0),
        m_timerRatioCalculated(false)

{
    Audit audit;
    audit << "Rosegarden " << VERSION << " - AlsaDriver - "
    << m_name << std::endl;
}

AlsaDriver::~AlsaDriver()
{
    if (!m_haveShutdown) {
        std::cerr << "WARNING: AlsaDriver::shutdown() was not called before destructor, calling now" << std::endl;
        shutdown();
    }
}

int
AlsaDriver::checkAlsaError(int rc, const char *
#ifdef DEBUG_ALSA
                           message
#endif
                          )
{
#ifdef DEBUG_ALSA
    if (rc < 0) {
        std::cerr << "AlsaDriver::"
        << message
        << ": " << rc
        << " (" << snd_strerror(rc) << ")"
        << std::endl;
    }
#endif
    return rc;
}

void
AlsaDriver::shutdown()
{
#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::~AlsaDriver - shutting down" << std::endl;
#endif

#ifdef HAVE_LIBJACK
    delete m_jackDriver;
    m_jackDriver = 0;
#endif

    if (m_midiHandle) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::shutdown - closing MIDI client" << std::endl;
#endif

        checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, 0), "shutdown(): stopping queue");
        checkAlsaError(snd_seq_drain_output(m_midiHandle), "shutdown(): drain output");
#ifdef DEBUG_ALSA

        std::cerr << "AlsaDriver::shutdown - stopped queue" << std::endl;
#endif

        snd_seq_close(m_midiHandle);
#ifdef DEBUG_ALSA

        std::cerr << "AlsaDriver::shutdown - closed MIDI handle" << std::endl;
#endif

        m_midiHandle = 0;
    }

    DataBlockRepository::clear();

    m_haveShutdown = true;
}

void
AlsaDriver::setLoop(const RealTime &loopStart, const RealTime &loopEnd)
{
    m_loopStartTime = loopStart;
    m_loopEndTime = loopEnd;

    // currently we use this simple test for looping - it might need
    // to get more sophisticated in the future.
    //
    if (m_loopStartTime != m_loopEndTime)
        m_looping = true;
    else
        m_looping = false;
}

void
AlsaDriver::getSystemInfo()
{
    int err;
    snd_seq_system_info_t *sysinfo;

    snd_seq_system_info_alloca(&sysinfo);

    if ((err = snd_seq_system_info(m_midiHandle, sysinfo)) < 0) {
        std::cerr << "System info error: " << snd_strerror(err)
        << std::endl;
        reportFailure(MappedEvent::FailureALSACallFailed);
        m_maxQueues = 0;
        m_maxClients = 0;
        m_maxPorts = 0;
        return ;
    }

    m_maxQueues = snd_seq_system_info_get_queues(sysinfo);
    m_maxClients = snd_seq_system_info_get_clients(sysinfo);
    m_maxPorts = snd_seq_system_info_get_ports(sysinfo);
}

void
AlsaDriver::showQueueStatus(int queue)
{
    int err, idx, min, max;
    snd_seq_queue_status_t *status;

    snd_seq_queue_status_alloca(&status);
    min = queue < 0 ? 0 : queue;
    max = queue < 0 ? m_maxQueues : queue + 1;

    for (idx = min; idx < max; ++idx) {
        if ((err = snd_seq_get_queue_status(m_midiHandle, idx, status)) < 0) {

            if (err == -ENOENT)
                continue;

            std::cerr << "Client " << idx << " info error: "
            << snd_strerror(err) << std::endl;

            reportFailure(MappedEvent::FailureALSACallFailed);
            return ;
        }

#ifdef DEBUG_ALSA
        std::cerr << "Queue " << snd_seq_queue_status_get_queue(status)
        << std::endl;

        std::cerr << "Tick       = "
        << snd_seq_queue_status_get_tick_time(status)
        << std::endl;

        std::cerr << "Realtime   = "
        << snd_seq_queue_status_get_real_time(status)->tv_sec
        << "."
        << snd_seq_queue_status_get_real_time(status)->tv_nsec
        << std::endl;

        std::cerr << "Flags      = 0x"
        << snd_seq_queue_status_get_status(status)
        << std::endl;
#endif

    }

}


void
AlsaDriver::generateTimerList()
{
    // Enumerate the available timers

    snd_timer_t *timerHandle;

    snd_timer_id_t *timerId;
    snd_timer_info_t *timerInfo;

    snd_timer_id_alloca(&timerId);
    snd_timer_info_alloca(&timerInfo);

    snd_timer_query_t *timerQuery;
    char timerName[64];

    m_timers.clear();

    if (snd_timer_query_open(&timerQuery, "hw", 0) >= 0) {

        snd_timer_id_set_class(timerId, SND_TIMER_CLASS_NONE);

        while (1) {

            if (snd_timer_query_next_device(timerQuery, timerId) < 0)
                break;
            if (snd_timer_id_get_class(timerId) < 0)
                break;

            AlsaTimerInfo info = {
                                     snd_timer_id_get_class(timerId),
                                     snd_timer_id_get_sclass(timerId),
                                     snd_timer_id_get_card(timerId),
                                     snd_timer_id_get_device(timerId),
                                     snd_timer_id_get_subdevice(timerId),
                                     "",
                                     0
                                 };

            if (info.card < 0)
                info.card = 0;
            if (info.device < 0)
                info.device = 0;
            if (info.subdevice < 0)
                info.subdevice = 0;

            //	    std::cerr << "got timer: class " << info.clas << std::endl;

            sprintf(timerName, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i",
                    info.clas, info.sclas, info.card, info.device, info.subdevice);

            if (snd_timer_open(&timerHandle, timerName, SND_TIMER_OPEN_NONBLOCK) < 0) {
                std::cerr << "Failed to open timer: " << timerName << std::endl;
                continue;
            }

            if (snd_timer_info(timerHandle, timerInfo) < 0)
                continue;

            info.name = snd_timer_info_get_name(timerInfo);
            info.resolution = snd_timer_info_get_resolution(timerInfo);
            snd_timer_close(timerHandle);

            //	    std::cerr << "adding timer: " << info.name << std::endl;

            m_timers.push_back(info);
        }

        snd_timer_query_close(timerQuery);
    }
}


std::string
AlsaDriver::getAutoTimer(bool &wantTimerChecks)
{
    Audit audit;

    // Look for the apparent best-choice timer.

    if (m_timers.empty())
        return "";

    // The system RTC timer ought to be good, but it doesn't look like
    // a very safe choice -- we've seen some system lockups apparently
    // connected with use of this timer on 2.6 kernels.  So we avoid
    // using that as an auto option.

    // Looks like our most reliable options for timers are, in order:
    //
    // 1. System timer if at 1000Hz, with timer checks (i.e. automatic
    //    drift correction against PCM frame count).  Only available
    //    when JACK is running.
    //
    // 2. PCM playback timer currently in use by JACK (no drift, but
    //    suffers from jitter).
    //
    // 3. System timer if at 1000Hz.
    //
    // 4. System RTC timer.
    //
    // 5. System timer.

    // As of Linux kernel 2.6.13 (?) the default system timer
    // resolution has been reduced from 1000Hz to 250Hz, giving us
    // only 4ms accuracy instead of 1ms.  This may be better than the
    // 10ms available from the stock 2.4 kernel, but it's not enough
    // for really solid MIDI timing.  If JACK is running at 44.1 or
    // 48KHz with a buffer size less than 256 frames, then the PCM
    // timer will give us less jitter.  Even at 256 frames, it may be
    // preferable in practice just because it's simpler.

    // However, we can't safely choose the PCM timer over the system
    // timer unless the latter has really awful resolution, because we
    // don't know for certain which PCM JACK is using.  We guess at
    // hw:0 for the moment, which gives us a stuck timer problem if
    // it's actually using something else.  So if the system timer
    // runs at 250Hz, we really have to choose it anyway and just give
    // a warning.

    wantTimerChecks = false; // for most options

#ifdef HAVE_LIBJACK

    if (m_jackDriver) {

        // use system timer with timer checks if at 1000Hz or more

        for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
                i != m_timers.end(); ++i) {
            if (i->sclas != SND_TIMER_SCLASS_NONE)
                continue;
            if (i->clas == SND_TIMER_CLASS_GLOBAL) {
                if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
                    long hz = 1000000000 / i->resolution;
                    if (hz > 240) {
                        if (hz < 750) {
                            audit << "System timer is only " << hz << "Hz, sending a warning" << std::endl;
                            reportFailure(MappedEvent::WarningImpreciseTimer);
                        }
                        wantTimerChecks = true;
                        return i->name;
                    }
                }
            }
        }

        // look for the first PCM playback timer; that's all we know
        // about for now (until JACK becomes able to tell us which PCM
        // it's on)

        for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
                i != m_timers.end(); ++i) {
            if (i->sclas != SND_TIMER_SCLASS_NONE)
                continue;
            if (i->clas == SND_TIMER_CLASS_PCM)
                return i->name;
        }
    }
#endif

    // look for a system timer with a frequency of 1000Hz or more

    for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
            i != m_timers.end(); ++i) {
        if (i->sclas != SND_TIMER_SCLASS_NONE)
            continue;
        if (i->clas == SND_TIMER_CLASS_GLOBAL) {
            if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
                long hz = 1000000000 / i->resolution;
                if (hz > 240) {
                    if (hz < 750) {
                        audit << "System timer is only " << hz << "Hz, sending a warning" << std::endl;
                        reportFailure(MappedEvent::WarningImpreciseTimer);
                    }
                    return i->name;
                }
            }
        }
    }

    // look for the system RTC timer if available

    /* No, this doesn't seem to be safe with some popular kernels
     
        for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
    	 i != m_timers.end(); ++i) {
    	if (i->sclas != SND_TIMER_SCLASS_NONE) continue;
    	if (i->clas == SND_TIMER_CLASS_GLOBAL) {
    	    if (i->device == SND_TIMER_GLOBAL_RTC) return i->name;
    	}
        }
    */

    // next look for slow, unpopular 100Hz 2.4 system timer

    for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
            i != m_timers.end(); ++i) {
        if (i->sclas != SND_TIMER_SCLASS_NONE)
            continue;
        if (i->clas == SND_TIMER_CLASS_GLOBAL) {
            if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
                audit << "Using low-resolution system timer, sending a warning" << std::endl;
                reportFailure(MappedEvent::WarningImpreciseTimer);
                return i->name;
            }
        }
    }

    // falling back to something that almost certainly won't work,
    // if for any reason all of the above failed

    return m_timers.begin()->name;
}



void
AlsaDriver::generatePortList(AlsaPortList *newPorts)
{
    Audit audit;
    AlsaPortList alsaPorts;

    snd_seq_client_info_t *cinfo;
    snd_seq_port_info_t *pinfo;
    int client;
    unsigned int writeCap = SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE;
    unsigned int readCap = SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_READ;

    snd_seq_client_info_alloca(&cinfo);
    snd_seq_client_info_set_client(cinfo, -1);

    audit << std::endl << "  ALSA Client information:"
    << std::endl << std::endl;

    // Get only the client ports we're interested in and store them
    // for sorting and then device creation.
    //
    while (snd_seq_query_next_client(m_midiHandle, cinfo) >= 0) {
        client = snd_seq_client_info_get_client(cinfo);
        snd_seq_port_info_alloca(&pinfo);
        snd_seq_port_info_set_client(pinfo, client);
        snd_seq_port_info_set_port(pinfo, -1);

        // Ignore ourselves and the system client
        //
        if (client == m_client || client == 0)
            continue;

        while (snd_seq_query_next_port(m_midiHandle, pinfo) >= 0) {
            int client = snd_seq_port_info_get_client(pinfo);
            int port = snd_seq_port_info_get_port(pinfo);
            unsigned int clientType = snd_seq_client_info_get_type(cinfo);
            unsigned int portType = snd_seq_port_info_get_type(pinfo);
            unsigned int capability = snd_seq_port_info_get_capability(pinfo);


            if ((((capability & writeCap) == writeCap) ||
                    ((capability & readCap) == readCap)) &&
                    ((capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0)) {
                audit << "    "
                << client << ","
                << port << " - ("
                << snd_seq_client_info_get_name(cinfo) << ", "
                << snd_seq_port_info_get_name(pinfo) << ")";

                PortDirection direction;

                if ((capability & SND_SEQ_PORT_CAP_DUPLEX) ||
                        ((capability & SND_SEQ_PORT_CAP_WRITE) &&
                         (capability & SND_SEQ_PORT_CAP_READ))) {
                    direction = Duplex;
                    audit << "\t\t\t(DUPLEX)";
                } else if (capability & SND_SEQ_PORT_CAP_WRITE) {
                    direction = WriteOnly;
                    audit << "\t\t(WRITE ONLY)";
                } else {
                    direction = ReadOnly;
                    audit << "\t\t(READ ONLY)";
                }

                audit << " [ctype " << clientType << ", ptype " << portType << ", cap " << capability << "]";

                // Generate a unique name using the client id
                //
                char portId[40];
                sprintf(portId, "%d:%d ", client, port);

                std::string fullClientName =
                    std::string(snd_seq_client_info_get_name(cinfo));

                std::string fullPortName =
                    std::string(snd_seq_port_info_get_name(pinfo));

                std::string name;

                // If the first part of the client name is the same as the
                // start of the port name, just use the port name.  otherwise
                // concatenate.
                //
                int firstSpace = fullClientName.find(" ");

                // If no space is found then we try to match the whole string
                //
                if (firstSpace < 0)
                    firstSpace = fullClientName.length();

                if (firstSpace > 0 &&
                        int(fullPortName.length()) >= firstSpace &&
                        fullPortName.substr(0, firstSpace) ==
                        fullClientName.substr(0, firstSpace)) {
                    name = portId + fullPortName;
                } else {
                    name = portId + fullClientName + ": " + fullPortName;
                }

                // Sanity check for length
                //
                if (name.length() > 35)
                    name = portId + fullPortName;

                if (direction == WriteOnly) {
                    name += " (write)";
                } else if (direction == ReadOnly) {
                    name += " (read)";
                } else if (direction == Duplex) {
                    name += " (duplex)";
                }

                AlsaPortDescription *portDescription =
                    new AlsaPortDescription(
                        Instrument::Midi,
                        name,
                        client,
                        port,
                        clientType,
                        portType,
                        capability,
                        direction);

                if (newPorts &&
                        (getPortName(ClientPortPair(client, port)) == "")) {
                    newPorts->push_back(portDescription);
                }

                alsaPorts.push_back(portDescription);

                audit << std::endl;
            }
        }
    }

    audit << std::endl;

    // Ok now sort by duplexicity
    //
    std::sort(alsaPorts.begin(), alsaPorts.end(), AlsaPortCmp());
    m_alsaPorts = alsaPorts;
}


void
AlsaDriver::generateInstruments()
{
    // Reset these before each Instrument hunt
    //
    int audioCount = 0;
    getAudioInstrumentNumbers(m_audioRunningId, audioCount);
    m_midiRunningId = MidiInstrumentBase;

    // Clear these
    //
    m_instruments.clear();
    m_devices.clear();
    m_devicePortMap.clear();
    m_suspendedPortMap.clear();

    AlsaPortList::iterator it = m_alsaPorts.begin();
    for (; it != m_alsaPorts.end(); it++) {
        if ((*it)->m_client == m_client) {
            std::cerr << "(Ignoring own port " << (*it)->m_client
            << ":" << (*it)->m_port << ")" << std::endl;
            continue;
        } else if ((*it)->m_client == 0) {
            std::cerr << "(Ignoring system port " << (*it)->m_client
            << ":" << (*it)->m_port << ")" << std::endl;
            continue;
        }

        if ((*it)->isWriteable()) {
            MappedDevice *device = createMidiDevice(*it, MidiDevice::Play);
            if (!device) {
#ifdef DEBUG_ALSA
                std::cerr << "WARNING: Failed to create play device" << std::endl;
#else

                ;
#endif

            } else {
                addInstrumentsForDevice(device);
                m_devices.push_back(device);
            }
        }
        if ((*it)->isReadable()) {
            MappedDevice *device = createMidiDevice(*it, MidiDevice::Record);
            if (!device) {
#ifdef DEBUG_ALSA
                std::cerr << "WARNING: Failed to create record device" << std::endl;
#else

                ;
#endif

            } else {
                m_devices.push_back(device);
            }
        }
    }

#ifdef HAVE_DSSI
    // Create a number of soft synth Instruments
    //
    {
        MappedInstrument *instr;
        char number[100];
        InstrumentId first;
        int count;
        getSoftSynthInstrumentNumbers(first, count);

        DeviceId ssiDeviceId = getSpareDeviceId();

        if (m_driverStatus & AUDIO_OK) {
            for (int i = 0; i < count; ++i) {
                sprintf(number, " #%d", i + 1);
                std::string name = "Synth plugin" + std::string(number);
                instr = new MappedInstrument(Instrument::SoftSynth,
                                             i,
                                             first + i,
                                             name,
                                             ssiDeviceId);
                m_instruments.push_back(instr);

                m_studio->createObject(MappedObject::AudioFader,
                                       first + i);
            }

            MappedDevice *device =
                new MappedDevice(ssiDeviceId,
                                 Device::SoftSynth,
                                 "Synth plugin",
                                 "Soft synth connection");
            m_devices.push_back(device);
        }
    }
#endif

#ifdef HAVE_LIBJACK

    // Create a number of audio Instruments - these are just
    // logical Instruments anyway and so we can create as
    // many as we like and then use them as Tracks.
    //
    {
        MappedInstrument *instr;
        char number[100];
        std::string audioName;

        DeviceId audioDeviceId = getSpareDeviceId();

        if (m_driverStatus & AUDIO_OK)
        {
            for (int channel = 0; channel < audioCount; ++channel) {
                sprintf(number, " #%d", channel + 1);
                audioName = "Audio" + std::string(number);
                instr = new MappedInstrument(Instrument::Audio,
                                             channel,
                                             m_audioRunningId,
                                             audioName,
                                             audioDeviceId);
                m_instruments.push_back(instr);

                // Create a fader with a matching id - this is the starting
                // point for all audio faders.
                //
                m_studio->createObject(MappedObject::AudioFader,
                                       m_audioRunningId);

                /*
                std::cerr  << "AlsaDriver::generateInstruments - "
                           << "added audio fader (id=" << m_audioRunningId
                           << ")" << std::endl;
                           */

                m_audioRunningId++;
            }

            // Create audio device
            //
            MappedDevice *device =
                new MappedDevice(audioDeviceId,
                                 Device::Audio,
                                 "Audio",
                                 "Audio connection");
            m_devices.push_back(device);
        }
    }
#endif

}

MappedDevice *
AlsaDriver::createMidiDevice(AlsaPortDescription *port,
                             MidiDevice::DeviceDirection reqDirection)
{
    char deviceName[100];
    std::string connectionName("");
    Audit audit;

    static int unknownCounter;

    static int counters[3][2]; // [system/hardware/software][out/in]
    const int SYSTEM = 0, HARDWARE = 1, SOFTWARE = 2;
    static const char *firstNames[4][2] = {
                                              { "MIDI output system device", "MIDI input system device"
                                              },
                                              { "MIDI external device", "MIDI hardware input device" },
                                              { "MIDI software device", "MIDI software input" }
                                          };
    static const char *countedNames[4][2] = {
                                                { "MIDI output system device %d", "MIDI input system device %d"
                                                },
                                                { "MIDI external device %d", "MIDI hardware input device %d" },
                                                { "MIDI software device %d", "MIDI software input %d" }
                                            };

    static int specificCounters[2];
    static const char *specificNames[2] = {
                                              "MIDI soundcard synth", "MIDI soft synth",
                                          };
    static const char *specificCountedNames[2] = {
                "MIDI soundcard synth %d", "MIDI soft synth %d",
            };

    DeviceId deviceId = getSpareDeviceId();

    if (port) {

        if (reqDirection == MidiDevice::Record && !port->isReadable())
            return 0;
        if (reqDirection == MidiDevice::Play && !port->isWriteable())
            return 0;

        // ALSA 1.0.11rc1 changed the client number ranges, suddenly
        // and without notice.
        //
        // Before the switch, system clients were 0-63, hardware
        // clients 64-127 and software clients 128+.  After the
        // switch, system clients are 0-15, hardware clients 16-127
        // and software clients 128+.
        //
        // This is problematic because it means we can no longer tell
        // reliably whether a client in the range 16-63 is a system or
        // hardware client without knowing the ALSA driver version.
        // And we don't know that -- it's not guaranteed to be the
        // same as the library version and probably in many cases it
        // isn't, regardless of what the ALSA webpage advises.
        //
        // Using these numbers for this purpose is clearly not
        // supported by ALSA, but there doesn't seem to be any other
        // way to distinguish between these categories, and it's an
        // important distinction because we want to be able to
        // autoconnect to hardware clients but absolutely not to
        // system ones.

        int category = SOFTWARE;
        bool ambiguousCategory = false;
        bool noConnect = false;

        if (port->m_client < 16) {
            category = SYSTEM;
            noConnect = true;
        } else if (port->m_client < 64) {
            // I said we can't rely on this, but it's better than
            // nothing.  What an absolutely foul hack.
            bool oldScheme = (SND_LIB_MAJOR == 0 ||
                              (SND_LIB_MAJOR == 1 &&
                               SND_LIB_MINOR == 0 &&
                               SND_LIB_SUBMINOR < 11));
            if (oldScheme) {
                category = SYSTEM;
                // These numbers were even exported via the ALSA API!
                if (port->m_client == 62 || port->m_client == 63) {
                    noConnect = true;
                }
            } else {
                category = HARDWARE;
            }
            ambiguousCategory = true;
        } else if (port->m_client < 128) {
            category = HARDWARE;
        } else {
            category = SOFTWARE;
        }

        bool haveName = false;

        if (category != SYSTEM && reqDirection == MidiDevice::Play) {

            // No way to query whether a port is a MIDI synth, as
            // PORT_TYPE_SYNTH actually indicates something different
            // (ability to do direct wavetable synthesis -- nothing
            // to do with MIDI).  But we assume GM/GS/XG/MT32 devices
            // are synths.

            bool isSynth = (port->m_portType &
                            (SND_SEQ_PORT_TYPE_MIDI_GM |
                             SND_SEQ_PORT_TYPE_MIDI_GS |
                             SND_SEQ_PORT_TYPE_MIDI_XG |
                             SND_SEQ_PORT_TYPE_MIDI_MT32));

            // Because we can't discover through the API whether a
            // port is a synth, we are instead reduced to this
            // disgusting hack.  (At least we should make this
            // configurable!)

            if (!isSynth &&
                    (port->m_name.find("ynth") < port->m_name.length()))
                isSynth = true;
            if (!isSynth &&
                    (port->m_name.find("nstrument") < port->m_name.length()))
                isSynth = true;
            if (!isSynth &&
                    (port->m_name.find("VSTi") < port->m_name.length()))
                isSynth = true;

            if (category == SYSTEM)
                isSynth = false;

            if (isSynth) {
                int clientType = (category == SOFTWARE) ? 1 : 0;
                if (specificCounters[clientType] == 0) {
                    sprintf(deviceName, specificNames[clientType]);
                    ++specificCounters[clientType];
                } else {
                    sprintf(deviceName,
                            specificCountedNames[clientType],
                            ++specificCounters[clientType]);
                }
                haveName = true;
            }
        }

        if (!haveName) {

            if (counters[category][reqDirection] == 0) {
                sprintf(deviceName, firstNames[category][reqDirection]);
                ++counters[category][reqDirection];
            } else {
                sprintf(deviceName,
                        countedNames[category][reqDirection],
                        ++counters[category][reqDirection]);
            }
        }

        if (!noConnect) {
            m_devicePortMap[deviceId] = ClientPortPair(port->m_client,
                                        port->m_port);
            connectionName = port->m_name;
        }

        audit << "Creating device " << deviceId << " in "
        << (reqDirection == MidiDevice::Play ? "Play" : "Record")
        << " mode for connection " << port->m_name
        << (noConnect ? " (not connecting)" : "")
        << "\nDefault device name for this device is "
        << deviceName << std::endl;

    } else {

        sprintf(deviceName, "Anonymous MIDI device %d", ++unknownCounter);

        audit << "Creating device " << deviceId << " in "
        << (reqDirection == MidiDevice::Play ? "Play" : "Record")
        << " mode -- no connection available "
        << "\nDefault device name for this device is "
        << deviceName << std::endl;
    }

    if (reqDirection == MidiDevice::Play) {

        QString portName;

        if (QString(deviceName).startsWith("Anonymous MIDI device ")) {
            portName = QString("out %1")
                       .arg(m_outputPorts.size() + 1);
        } else {
            portName = QString("out %1 - %2")
                       .arg(m_outputPorts.size() + 1)
                       .arg(deviceName);
        }

        int outputPort = checkAlsaError(snd_seq_create_simple_port
                                        (m_midiHandle,
                                         portName,
                                         SND_SEQ_PORT_CAP_READ |
                                         SND_SEQ_PORT_CAP_SUBS_READ,
                                         SND_SEQ_PORT_TYPE_APPLICATION),
                                        "createMidiDevice - can't create output port");

        if (outputPort >= 0) {

            std::cerr << "CREATED OUTPUT PORT " << outputPort << ":" << portName << " for device " << deviceId << std::endl;

            m_outputPorts[deviceId] = outputPort;

            if (port) {
                if (connectionName != "") {
                    std::cerr << "Connecting my port " << outputPort << " to " << port->m_client << ":" << port->m_port << " on initialisation" << std::endl;
                    snd_seq_connect_to(m_midiHandle,
                                       outputPort,
                                       port->m_client,
                                       port->m_port);
                    if (m_midiSyncAutoConnect) {
                        snd_seq_connect_to(m_midiHandle,
                                           m_syncOutputPort,
                                           port->m_client,
                                           port->m_port);
                    }
                }
                std::cerr << "done" << std::endl;
            }
        }
    }

    MappedDevice *device = new MappedDevice(deviceId,
                                            Device::Midi,
                                            deviceName,
                                            connectionName);
    device->setDirection(reqDirection);
    return device;
}

DeviceId
AlsaDriver::getSpareDeviceId()
{
    std::set
        <DeviceId> ids;
    for (unsigned int i = 0; i < m_devices.size(); ++i) {
        ids.insert(m_devices[i]->getId());
    }

    DeviceId id = 0;
    while (ids.find(id) != ids.end())
        ++id;
    return id;
}

void
AlsaDriver::addInstrumentsForDevice(MappedDevice *device)
{
    std::string channelName;
    char number[100];

    for (int channel = 0; channel < 16; ++channel) {
        // Create MappedInstrument for export to GUI
        //
        // name is just number, derive rest from device at gui
        sprintf(number, "#%d", channel + 1);
        channelName = std::string(number);

        if (channel == 9)
            channelName = std::string("#10[D]");
        MappedInstrument *instr = new MappedInstrument(Instrument::Midi,
                                  channel,
                                  m_midiRunningId++,
                                  channelName,
                                  device->getId());
        m_instruments.push_back(instr);
    }
}


bool
AlsaDriver::canReconnect(Device::DeviceType type)
{
    return (type == Device::Midi);
}

DeviceId
AlsaDriver::addDevice(Device::DeviceType type,
                      MidiDevice::DeviceDirection direction)
{
    if (type == Device::Midi) {

        MappedDevice *device = createMidiDevice(0, direction);
        if (!device) {
#ifdef DEBUG_ALSA
            std::cerr << "WARNING: Device creation failed" << std::endl;
#else

            ;
#endif

        } else {
            addInstrumentsForDevice(device);
            m_devices.push_back(device);

            MappedEvent *mE =
                new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
                                0, 0);
            insertMappedEventForReturn(mE);

            return device->getId();
        }
    }

    return Device::NO_DEVICE;
}

void
AlsaDriver::removeDevice(DeviceId id)
{
    DeviceIntMap::iterator i1 = m_outputPorts.find(id);
    if (i1 == m_outputPorts.end()) {
        std::cerr << "WARNING: AlsaDriver::removeDevice: Cannot find device "
        << id << " in port map" << std::endl;
        return ;
    }
    checkAlsaError( snd_seq_delete_port(m_midiHandle, i1->second),
                    "removeDevice");
    m_outputPorts.erase(i1);

    for (MappedDeviceList::iterator i = m_devices.end();
            i != m_devices.begin(); ) {

        --i;

        if ((*i)->getId() == id) {
            delete *i;
            m_devices.erase(i);
        }
    }

    for (MappedInstrumentList::iterator i = m_instruments.end();
            i != m_instruments.begin(); ) {

        --i;

        if ((*i)->getDevice() == id) {
            delete *i;
            m_instruments.erase(i);
        }
    }

    MappedEvent *mE =
        new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
                        0, 0);
    insertMappedEventForReturn(mE);
}

void
AlsaDriver::renameDevice(DeviceId id, QString name)
{
    DeviceIntMap::iterator i = m_outputPorts.find(id);
    if (i == m_outputPorts.end()) {
        std::cerr << "WARNING: AlsaDriver::renameDevice: Cannot find device "
        << id << " in port map" << std::endl;
        return ;
    }

    snd_seq_port_info_t *pinfo;
    snd_seq_port_info_alloca(&pinfo);
    snd_seq_get_port_info(m_midiHandle, i->second, pinfo);

    QString oldName = snd_seq_port_info_get_name(pinfo);
    int sep = oldName.find(" - ");

    QString newName;

    if (name.startsWith("Anonymous MIDI device ")) {
        if (sep < 0)
            sep = 0;
        newName = oldName.left(sep);
    } else if (sep < 0) {
        newName = oldName + " - " + name;
    } else {
        newName = oldName.left(sep + 3) + name;
    }

    snd_seq_port_info_set_name(pinfo, newName.data());
    checkAlsaError(snd_seq_set_port_info(m_midiHandle, i->second, pinfo),
                   "renameDevice");

    for (unsigned int i = 0; i < m_devices.size(); ++i) {
        if (m_devices[i]->getId() == id) {
            m_devices[i]->setName(newName.data());
            break;
        }
    }

    std::cerr << "Renamed " << m_client << ":" << i->second << " to " << name << std::endl;
}

ClientPortPair
AlsaDriver::getPortByName(std::string name)
{
    for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
        if (m_alsaPorts[i]->m_name == name) {
            return ClientPortPair(m_alsaPorts[i]->m_client,
                                  m_alsaPorts[i]->m_port);
        }
    }
    return ClientPortPair( -1, -1);
}

std::string
AlsaDriver::getPortName(ClientPortPair port)
{
    for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
        if (m_alsaPorts[i]->m_client == port.first &&
                m_alsaPorts[i]->m_port == port.second) {
            return m_alsaPorts[i]->m_name;
        }
    }
    return "";
}


unsigned int
AlsaDriver::getConnections(Device::DeviceType type,
                           MidiDevice::DeviceDirection direction)
{
    if (type != Device::Midi)
        return 0;

    int count = 0;
    for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
        if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
                (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
            ++count;
        }
    }

    return count;
}

QString
AlsaDriver::getConnection(Device::DeviceType type,
                          MidiDevice::DeviceDirection direction,
                          unsigned int connectionNo)
{
    if (type != Device::Midi)
        return "";

    AlsaPortList tempList;
    for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
        if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
                (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
            tempList.push_back(m_alsaPorts[j]);
        }
    }

    if (connectionNo < tempList.size()) {
        return tempList[connectionNo]->m_name.c_str();
    }

    return "";
}

void
AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection)
{
    ClientPortPair pair( -1, -1);
    if (connection && connection != "") {
        pair = getPortByName(connection.data());
    }
    setConnectionToDevice(device, connection, pair);
}

void
AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection,
                                  const ClientPortPair &pair)
{
    QString prevConnection = device.getConnection().c_str();
    device.setConnection(connection.data());

    if (device.getDirection() == MidiDevice::Play) {

        DeviceIntMap::iterator j = m_outputPorts.find(device.getId());

        if (j != m_outputPorts.end()) {

            if (prevConnection != "") {
                ClientPortPair prevPair = getPortByName(prevConnection.data());
                if (prevPair.first >= 0 && prevPair.second >= 0) {

                    std::cerr << "Disconnecting my port " << j->second << " from " << prevPair.first << ":" << prevPair.second << " on reconnection" << std::endl;
                    snd_seq_disconnect_to(m_midiHandle,
                                          j->second,
                                          prevPair.first,
                                          prevPair.second);

                    if (m_midiSyncAutoConnect) {
                        bool foundElsewhere = false;
                        for (MappedDeviceList::iterator k = m_devices.begin();
                                k != m_devices.end(); ++k) {
                            if ((*k)->getId() != device.getId()) {
                                if ((*k)->getConnection() == prevConnection.data()) {
                                    foundElsewhere = true;
                                    break;
                                }
                            }
                        }
                        if (!foundElsewhere) {
                            snd_seq_disconnect_to(m_midiHandle,
                                                  m_syncOutputPort,
                                                  pair.first,
                                                  pair.second);
                        }
                    }
                }
            }

            if (pair.first >= 0 && pair.second >= 0) {
                std::cerr << "Connecting my port " << j->second << " to " << pair.first << ":" << pair.second << " on reconnection" << std::endl;
                snd_seq_connect_to(m_midiHandle,
                                   j->second,
                                   pair.first,
                                   pair.second);
                if (m_midiSyncAutoConnect) {
                    snd_seq_connect_to(m_midiHandle,
                                       m_syncOutputPort,
                                       pair.first,
                                       pair.second);
                }
            }
        }
    }
}

void
AlsaDriver::setConnection(DeviceId id, QString connection)
{
    Audit audit;
    ClientPortPair port(getPortByName(connection.data()));

    if (port.first != -1 && port.second != -1) {

        m_devicePortMap[id] = port;

        for (unsigned int i = 0; i < m_devices.size(); ++i) {

            if (m_devices[i]->getId() == id) {
                setConnectionToDevice(*m_devices[i], connection, port);

                MappedEvent *mE =
                    new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
                                    0, 0);
                insertMappedEventForReturn(mE);

                break;
            }
        }
    }
}

void
AlsaDriver::setPlausibleConnection(DeviceId id, QString idealConnection)
{
    Audit audit;
    ClientPortPair port(getPortByName(idealConnection.data()));

    audit << "AlsaDriver::setPlausibleConnection: connection like "
    << idealConnection << " requested for device " << id << std::endl;

    if (port.first != -1 && port.second != -1) {

        m_devicePortMap[id] = port;

        for (unsigned int i = 0; i < m_devices.size(); ++i) {

            if (m_devices[i]->getId() == id) {
                setConnectionToDevice(*m_devices[i], idealConnection, port);
                break;
            }
        }

        audit << "AlsaDriver::setPlausibleConnection: exact match available"
        << std::endl;
        return ;
    }

    // What we want is a connection that:
    //
    //  * is in the right "class" (the 0-63/64-127/128+ range of client id)
    //  * has at least some text in common
    //  * is not yet in use for any device.
    //
    // To do this, we exploit our privileged position as part of AlsaDriver
    // and use our knowledge of how connection strings are made (see
    // AlsaDriver::generatePortList above) to pick out the relevant parts
    // of the requested string.

    int client = -1;
    int colon = idealConnection.find(":");
    if (colon >= 0)
        client = idealConnection.left(colon).toInt();

    int portNo = -1;
    if (client > 0) {
        QString remainder = idealConnection.mid(colon + 1);
        int space = remainder.find(" ");
        if (space >= 0)
            portNo = remainder.left(space).toInt();
    }

    int firstSpace = idealConnection.find(" ");
    int endOfText = idealConnection.find(QRegExp("[^\\w ]"), firstSpace);

    QString text;
    if (endOfText < 2) {
        text = idealConnection.mid(firstSpace + 1);
    } else {
        text = idealConnection.mid(firstSpace + 1, endOfText - firstSpace - 2);
    }

    for (int testUsed = 1; testUsed >= 0; --testUsed) {

        for (int testNumbers = 1; testNumbers >= 0; --testNumbers) {

            for (int testName = 1; testName >= 0; --testName) {

                int fitness =
                    (testName << 3) +
                    (testNumbers << 2) +
                    (testUsed << 1) + 1;

                for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {

                    AlsaPortDescription *port = m_alsaPorts[i];

                    if (client > 0) {

                        if (port->m_client / 64 != client / 64)
                            continue;

                        if (testNumbers) {
                            // We always check the client class (above).
                            // But we also prefer to have something in
                            // common with client or port number, at least
                            // for ports that aren't used elsewhere
                            // already.  We don't check both because the
                            // chances are the entire string would already
                            // have matched if both figures did; instead
                            // we check the port if it's > 0 (handy for
                            // e.g. matching the MIDI synth port on a
                            // multi-port soundcard) and the client
                            // otherwise.
                            if (portNo > 0) {
                                if (port->m_port != portNo)
                                    continue;
                            } else {
                                if (port->m_client != client)
                                    continue;
                            }
                        }
                    }

                    if (testName && text != "" &&
                            !QString(port->m_name.c_str()).contains(text))
                        continue;

                    if (testUsed) {
                        bool used = false;
                        for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
                                dpmi != m_devicePortMap.end(); ++dpmi) {
                            if (dpmi->second.first == port->m_client &&
                                    dpmi->second.second == port->m_port) {
                                used = true;
                                break;
                            }
                        }
                        if (used)
                            continue;
                    }

                    // OK, this one will do

                    audit << "AlsaDriver::setPlausibleConnection: fuzzy match "
                    << port->m_name << " available with fitness "
                    << fitness << std::endl;

                    m_devicePortMap[id] = ClientPortPair(port->m_client, port->m_port);

                    for (unsigned int i = 0; i < m_devices.size(); ++i) {

                        if (m_devices[i]->getId() == id) {
                            setConnectionToDevice(*m_devices[i],
                                                  port->m_name.c_str(),
                                                  m_devicePortMap[id]);

                            // in this case we don't request a device resync,
                            // because this is only invoked at times such as
                            // file load when the GUI is well aware that the
                            // whole situation is in upheaval anyway

                            return ;
                        }
                    }
                }
            }
        }
    }

    audit << "AlsaDriver::setPlausibleConnection: nothing suitable available"
    << std::endl;
}


void
AlsaDriver::checkTimerSync(size_t frames)
{
    if (!m_doTimerChecks)
        return ;

#ifdef HAVE_LIBJACK

    if (!m_jackDriver || !m_queueRunning || frames == 0 ||
            (getMTCStatus() == TRANSPORT_SLAVE)) {
        m_firstTimerCheck = true;
        return ;
    }

    static RealTime startAlsaTime;
    static size_t startJackFrames = 0;
    static size_t lastJackFrames = 0;

    size_t nowJackFrames = m_jackDriver->getFramesProcessed();
    RealTime nowAlsaTime = getAlsaTime();

    if (m_firstTimerCheck ||
            (nowJackFrames <= lastJackFrames) ||
            (nowAlsaTime <= startAlsaTime)) {

        startAlsaTime = nowAlsaTime;
        startJackFrames = nowJackFrames;
        lastJackFrames = nowJackFrames;

        m_firstTimerCheck = false;
        return ;
    }

    RealTime jackDiff = RealTime::frame2RealTime
                        (nowJackFrames - startJackFrames,
                         m_jackDriver->getSampleRate());

    RealTime alsaDiff = nowAlsaTime - startAlsaTime;

    if (alsaDiff > RealTime(10, 0)) {

#ifdef DEBUG_ALSA
        if (!m_playing) {
            std::cout << "\nALSA:" << startAlsaTime << "\t->" << nowAlsaTime << "\nJACK: " << startJackFrames << "\t\t-> " << nowJackFrames << std::endl;
            std::cout << "ALSA diff:  " << alsaDiff << "\nJACK diff:  " << jackDiff << std::endl;
        }
#endif

        double ratio = (jackDiff - alsaDiff) / alsaDiff;

        if (fabs(ratio) > 0.1) {
#ifdef DEBUG_ALSA
            if (!m_playing) {
                std::cout << "Ignoring excessive ratio " << ratio
                << ", hoping for a more likely result next time"
                << std::endl;
            }
#endif

        } else if (fabs(ratio) > 0.000001) {

#ifdef DEBUG_ALSA
            if (alsaDiff > RealTime::zeroTime && jackDiff > RealTime::zeroTime) {
                if (!m_playing) {
                    if (jackDiff < alsaDiff) {
                        std::cout << "<<<< ALSA timer is faster by " << 100.0 * ((alsaDiff - jackDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
                    } else {
                        std::cout << ">>>> JACK timer is faster by " << 100.0 * ((jackDiff - alsaDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
                    }
                }
            }
#endif

            m_timerRatio = ratio;
            m_timerRatioCalculated = true;
        }

        m_firstTimerCheck = true;
    }
#endif
}


unsigned int
AlsaDriver::getTimers()
{
    return m_timers.size() + 1; // one extra for auto
}

QString
AlsaDriver::getTimer(unsigned int n)
{
    if (n == 0)
        return AUTO_TIMER_NAME;
    else
        return m_timers[n -1].name.c_str();
}

QString
AlsaDriver::getCurrentTimer()
{
    return m_currentTimer.c_str();
}

void
AlsaDriver::setCurrentTimer(QString timer)
{
    Audit audit;

    if (timer == getCurrentTimer())
        return ;

    std::cerr << "AlsaDriver::setCurrentTimer(" << timer << ")" << std::endl;

    std::string name(timer.data());

    if (name == AUTO_TIMER_NAME) {
        name = getAutoTimer(m_doTimerChecks);
    } else {
        m_doTimerChecks = false;
    }
    m_timerRatioCalculated = false;

    // Stop and restart the queue around the timer change.  We don't
    // call stopClocks/startClocks here because they do the wrong
    // thing if we're currently playing and on the JACK transport.

    m_queueRunning = false;
    checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "setCurrentTimer(): stopping queue");
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to stop queue");

    snd_seq_event_t event;
    snd_seq_ev_clear(&event);
    snd_seq_real_time_t z = { 0, 0 };
    snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
    snd_seq_ev_set_direct(&event);
    checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
                                         0, &event), "setCurrentTimer(): control queue");
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to control queue");
    m_alsaPlayStartTime = RealTime::zeroTime;

    for (unsigned int i = 0; i < m_timers.size(); ++i) {
        if (m_timers[i].name == name) {

            snd_seq_queue_timer_t *timer;
            snd_timer_id_t *timerid;

            snd_seq_queue_timer_alloca(&timer);
            snd_seq_get_queue_timer(m_midiHandle, m_queue, timer);

            snd_timer_id_alloca(&timerid);
            snd_timer_id_set_class(timerid, m_timers[i].clas);
            snd_timer_id_set_sclass(timerid, m_timers[i].sclas);
            snd_timer_id_set_card(timerid, m_timers[i].card);
            snd_timer_id_set_device(timerid, m_timers[i].device);
            snd_timer_id_set_subdevice(timerid, m_timers[i].subdevice);

            snd_seq_queue_timer_set_id(timer, timerid);
            snd_seq_set_queue_timer(m_midiHandle, m_queue, timer);

            if (m_doTimerChecks) {
                audit << "    Current timer set to \"" << name << "\" with timer checks"
                << std::endl;
            } else {
                audit << "    Current timer set to \"" << name << "\""
                << std::endl;
            }

            if (m_timers[i].clas == SND_TIMER_CLASS_GLOBAL &&
                    m_timers[i].device == SND_TIMER_GLOBAL_SYSTEM) {
                long hz = 1000000000 / m_timers[i].resolution;
                if (hz < 900) {
                    audit << "    WARNING: using system timer with only "
                    << hz << "Hz resolution!" << std::endl;
                }
            }

            break;
        }
    }

#ifdef HAVE_LIBJACK
    if (m_jackDriver)
        m_jackDriver->prebufferAudio();
#endif

    checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "checkAlsaError(): continue queue");
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to continue queue");
    m_queueRunning = true;

    m_firstTimerCheck = true;
}

bool
AlsaDriver::initialise()
{
    bool result = true;

    initialiseAudio();
    result = initialiseMidi();

    return result;
}



// Set up queue, client and port
//
bool
AlsaDriver::initialiseMidi()
{
    Audit audit;

    // Create a non-blocking handle.
    //
    if (snd_seq_open(&m_midiHandle,
                     "default",
                     SND_SEQ_OPEN_DUPLEX,
                     SND_SEQ_NONBLOCK) < 0) {
        audit << "AlsaDriver::initialiseMidi - "
        << "couldn't open sequencer - " << snd_strerror(errno)
        << " - perhaps you need to modprobe snd-seq-midi."
        << std::endl;
        reportFailure(MappedEvent::FailureALSACallFailed);
        return false;
    }

    snd_seq_set_client_name(m_midiHandle, "rosegarden");

    if ((m_client = snd_seq_client_id(m_midiHandle)) < 0) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::initialiseMidi - can't create client"
        << std::endl;
#endif

        return false;
    }

    // Create a queue
    //
    if ((m_queue = snd_seq_alloc_named_queue(m_midiHandle,
                   "Rosegarden queue")) < 0) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::initialiseMidi - can't allocate queue"
        << std::endl;
#endif

        return false;
    }

    // Create the input port
    //
    snd_seq_port_info_t *pinfo;

    snd_seq_port_info_alloca(&pinfo);
    snd_seq_port_info_set_capability(pinfo,
                                     SND_SEQ_PORT_CAP_WRITE |
                                     SND_SEQ_PORT_CAP_SUBS_WRITE );
    snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION);
    snd_seq_port_info_set_midi_channels(pinfo, 16);
    /* we want to know when the events got delivered to us */
    snd_seq_port_info_set_timestamping(pinfo, 1);
    snd_seq_port_info_set_timestamp_real(pinfo, 1);
    snd_seq_port_info_set_timestamp_queue(pinfo, m_queue);
    snd_seq_port_info_set_name(pinfo, "record in");

    if (checkAlsaError(snd_seq_create_port(m_midiHandle, pinfo),
                       "initialiseMidi - can't create input port") < 0)
        return false;
    m_inputPort = snd_seq_port_info_get_port(pinfo);

    // Subscribe the input port to the ALSA Announce port
    // to receive notifications when clients, ports and subscriptions change
    snd_seq_connect_from( m_midiHandle, m_inputPort,
                          SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE );

    m_midiInputPortConnected = true;

    // Set the input queue size
    //
    if (snd_seq_set_client_pool_output(m_midiHandle, 2000) < 0 ||
            snd_seq_set_client_pool_input(m_midiHandle, 2000) < 0 ||
            snd_seq_set_client_pool_output_room(m_midiHandle, 2000) < 0) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::initialiseMidi - "
        << "can't modify pool parameters"
        << std::endl;
#endif

        return false;
    }

    // Create sync output now as well
    m_syncOutputPort = checkAlsaError(snd_seq_create_simple_port
                                      (m_midiHandle,
                                       "sync out",
                                       SND_SEQ_PORT_CAP_READ |
                                       SND_SEQ_PORT_CAP_SUBS_READ,
                                       SND_SEQ_PORT_TYPE_APPLICATION),
                                      "initialiseMidi - can't create sync output port");

    // and port for hardware controller
    m_controllerPort = checkAlsaError(snd_seq_create_simple_port
                                      (m_midiHandle,
                                       "external controller",
                                       SND_SEQ_PORT_CAP_READ |
                                       SND_SEQ_PORT_CAP_WRITE |
                                       SND_SEQ_PORT_CAP_SUBS_READ |
                                       SND_SEQ_PORT_CAP_SUBS_WRITE,
                                       SND_SEQ_PORT_TYPE_APPLICATION),
                                      "initialiseMidi - can't create controller port");

    getSystemInfo();

    generatePortList();
    generateInstruments();

    // Modify status with MIDI success
    //
    m_driverStatus |= MIDI_OK;

    generateTimerList();
    setCurrentTimer(AUTO_TIMER_NAME);

    // Start the timer
    if (checkAlsaError(snd_seq_start_queue(m_midiHandle, m_queue, NULL),
                       "initialiseMidi(): couldn't start queue") < 0) {
        reportFailure(MappedEvent::FailureALSACallFailed);
        return false;
    }

    m_queueRunning = true;

    // process anything pending
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "initialiseMidi(): couldn't drain output");

    audit << "AlsaDriver::initialiseMidi -  initialised MIDI subsystem"
    << std::endl << std::endl;

    return true;
}

// We don't even attempt to use ALSA audio.  We just use JACK instead.
// See comment at the top of this file and jackProcess() for further
// information on how we use this.
//
void
AlsaDriver::initialiseAudio()
{
#ifdef HAVE_LIBJACK
    m_jackDriver = new JackDriver(this);

    if (m_jackDriver->isOK()) {
        m_driverStatus |= AUDIO_OK;
    } else {
        delete m_jackDriver;
        m_jackDriver = 0;
    }
#endif
}

void
AlsaDriver::initialisePlayback(const RealTime &position)
{
#ifdef DEBUG_ALSA
    std::cerr << "\n\nAlsaDriver - initialisePlayback" << std::endl;
#endif

    // now that we restart the queue at each play, the origin is always zero
    m_alsaPlayStartTime = RealTime::zeroTime;
    m_playStartPosition = position;

    m_startPlayback = true;

    m_mtcFirstTime = -1;
    m_mtcSigmaE = 0;
    m_mtcSigmaC = 0;

    if (getMMCStatus() == TRANSPORT_MASTER) {
        sendMMC(127, MIDI_MMC_PLAY, true, "");
        m_eat_mtc = 0;
    }

    if (getMTCStatus() == TRANSPORT_MASTER) {
        insertMTCFullFrame(position);
    }

    // If MIDI Sync is enabled then adjust for the MIDI Clock to
    // synchronise the sequencer with the clock.
    //
    if (getMIDISyncStatus() == TRANSPORT_MASTER) {
        // Send the Song Position Pointer for MIDI CLOCK positioning
        //
        // Get time from current alsa time to start of alsa timing -
        // add the initial starting point and divide by the MIDI Beat
        // length.  The SPP is is the MIDI Beat upon which to start the song.
        // Songs are always assumed to start on a MIDI Beat of 0. Each MIDI
        // Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th
        // note (since there are 24 MIDI Clocks in a quarter note).
        //
        long spp =
            long(((getAlsaTime() - m_alsaPlayStartTime + m_playStartPosition) /
                  m_midiClockInterval) / 6.0 );

        // Ok now we have the new SPP - stop the transport and restart with the
        // new value.
        //
        sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);

        signed int args = spp;
        sendSystemDirect(SND_SEQ_EVENT_SONGPOS, &args);

        // Now send the START/CONTINUE
        //
        if (m_playStartPosition == RealTime::zeroTime)
            sendSystemQueued(SND_SEQ_EVENT_START, "",
                             m_alsaPlayStartTime);
        else
            sendSystemQueued(SND_SEQ_EVENT_CONTINUE, "",
                             m_alsaPlayStartTime);
    }

#ifdef HAVE_LIBJACK
    if (m_jackDriver) {
        m_needJackStart = NeedJackStart;
    }
#endif
}


void
AlsaDriver::stopPlayback()
{
#ifdef DEBUG_ALSA
    std::cerr << "\n\nAlsaDriver - stopPlayback" << std::endl;
#endif

    if (getMIDISyncStatus() == TRANSPORT_MASTER) {
        sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
    }

    if (getMMCStatus() == TRANSPORT_MASTER) {
        sendMMC(127, MIDI_MMC_STOP, true, "");
        //<VN> need to throw away the next MTC event
        m_eat_mtc = 3;
    }

    allNotesOff();
    m_playing = false;

#ifdef HAVE_LIBJACK

    if (m_jackDriver) {
        m_jackDriver->stopTransport();
        m_needJackStart = NeedNoJackStart;
    }
#endif

    // Flush the output and input queues
    //
    snd_seq_remove_events_t *info;
    snd_seq_remove_events_alloca(&info);
    snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_INPUT |
                                        SND_SEQ_REMOVE_OUTPUT);
    snd_seq_remove_events(m_midiHandle, info);

    // send sounds-off to all play devices
    //
    for (MappedDeviceList::iterator i = m_devices.begin(); i != m_devices.end(); ++i) {
        if ((*i)->getDirection() == MidiDevice::Play) {
            sendDeviceController((*i)->getId(),
                                 MIDI_CONTROLLER_SUSTAIN, 0);
            sendDeviceController((*i)->getId(),
                                 MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
        }
    }

    punchOut();

    stopClocks(); // Resets ALSA timer to zero

    clearAudioQueue();

    startClocksApproved(); // restarts ALSA timer without starting JACK transport
}

void
AlsaDriver::punchOut()
{
#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::punchOut" << std::endl;
#endif

#ifdef HAVE_LIBJACK
    // Close any recording file
    if (m_recordStatus == RECORD_ON) {
        for (InstrumentSet::const_iterator i = m_recordingInstruments.begin();
                i != m_recordingInstruments.end(); ++i) {

            InstrumentId id = *i;

            if (id >= AudioInstrumentBase &&
                    id < MidiInstrumentBase) {

                AudioFileId auid = 0;
                if (m_jackDriver && m_jackDriver->closeRecordFile(id, auid)) {

#ifdef DEBUG_ALSA
                    std::cerr << "AlsaDriver::stopPlayback: sending back to GUI for instrument " << id << std::endl;
#endif

                    // Create event to return to gui to say that we've
                    // completed an audio file and we can generate a
                    // preview for it now.
                    //
                    // nasty hack -- don't have right audio id here, and
                    // the sequencer will wipe out the instrument id and
                    // replace it with currently-selected one in gui --
                    // so use audio id slot to pass back instrument id
                    // and handle accordingly in gui
                    try {
                        MappedEvent *mE =
                            new MappedEvent(id,
                                            MappedEvent::AudioGeneratePreview,
                                            id % 256,
                                            id / 256);

                        // send completion event
                        insertMappedEventForReturn(mE);
                    } catch (...) {
                        ;
                    }
                }
            }
        }
    }
#endif

    // Change recorded state if any set
    //
    if (m_recordStatus == RECORD_ON)
        m_recordStatus = RECORD_OFF;

    m_recordingInstruments.clear();
}

void
AlsaDriver::resetPlayback(const RealTime &oldPosition, const RealTime &position)
{
#ifdef DEBUG_ALSA
    std::cerr << "\n\nAlsaDriver - resetPlayback(" << position << ")" << std::endl;
#endif

    if (getMMCStatus() == TRANSPORT_MASTER) {
        unsigned char t_sec = (unsigned char) position.sec % 60;
        unsigned char t_min = (unsigned char) (position.sec / 60) % 60;
        unsigned char t_hrs = (unsigned char) (position.sec / 3600);
#define STUPID_BROKEN_EQUIPMENT
#ifdef STUPID_BROKEN_EQUIPMENT
        // Some recorders assume you are talking in 30fps...
        unsigned char t_frm = (unsigned char) (position.nsec / 33333333U);
        unsigned char t_sbf = (unsigned char) ((position.nsec / 333333U) % 100U);
#else
        // We always send at 25fps, it's the easiest to avoid rounding problems
        unsigned char t_frm = (unsigned char) (position.nsec / 40000000U);
        unsigned char t_sbf = (unsigned char) ((position.nsec / 400000U) % 100U);
#endif

        std::cerr << "\n Jump using MMC LOCATE to" << position << std::endl;
        std::cerr << "\t which is " << int(t_hrs) << ":" << int(t_min) << ":" << int(t_sec) << "." << int(t_frm) << "." << int(t_sbf) << std::endl;
        unsigned char locateDataArr[7] = {
                                             0x06,
                                             0x01,
                                             0x60 + t_hrs,    // (30fps flag) + hh
                                             t_min, 	    // mm
                                             t_sec, 	    // ss
                                             t_frm, 	    // frames
                                             t_sbf	    // subframes
                                         };

        sendMMC(127, MIDI_MMC_LOCATE, true, std::string((const char *) locateDataArr, 7));
    }

    m_playStartPosition = position;
    m_alsaPlayStartTime = getAlsaTime();

    // Reset note offs to correct positions
    //
    RealTime jump = position - oldPosition;

    // modify the note offs that exist as they're relative to the
    // playStartPosition terms.
    //
    for (NoteOffQueue::iterator i = m_noteOffQueue.begin();
            i != m_noteOffQueue.end(); ++i) {
        // if we're fast forwarding then we bring the note off closer
        if (jump >= RealTime::zeroTime) {
            (*i)->setRealTime((*i)->getRealTime() - jump /* + modifyNoteOff */);
        } else // we're rewinding - kill the note immediately
        {
            (*i)->setRealTime(m_playStartPosition);
        }
    }

    // Ensure we clear down output queue on reset - in the case of
    // MIDI clock where we might have a long queue of events already
    // posted.
    //
    snd_seq_remove_events_t *info;
    snd_seq_remove_events_alloca(&info);
    //snd_seq_remove_events_set_event_type(info,
    snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
    snd_seq_remove_events(m_midiHandle, info);

    if (getMTCStatus() == TRANSPORT_MASTER) {
        m_mtcFirstTime = -1;
        m_mtcSigmaE = 0;
        m_mtcSigmaC = 0;
        insertMTCFullFrame(position);
    }

#ifdef HAVE_LIBJACK
    if (m_jackDriver) {
        m_needJackStart = NeedJackReposition;
    }
#endif
}

void
AlsaDriver::setMIDIClockInterval(RealTime interval)
{
#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::setMIDIClockInterval(" << interval << ")" << endl;
#endif

    // Reset the value
    //
    SoundDriver::setMIDIClockInterval(interval);

    // Return if the clock isn't enabled
    //
    if (!m_midiClockEnabled)
        return ;

    if (false)  // don't remove any events quite yet
    {

        // Remove all queued events (although we should filter this
        // down to just the clock events.
        //
        snd_seq_remove_events_t *info;
        snd_seq_remove_events_alloca(&info);

        //if (snd_seq_type_check(SND_SEQ_EVENT_CLOCK, SND_SEQ_EVFLG_CONTROL))
        //snd_seq_remove_events_set_event_type(info,
        snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
        snd_seq_remove_events_set_event_type(info, SND_SEQ_EVFLG_CONTROL);
        std::cout << "AlsaDriver::setMIDIClockInterval - "
        << "MIDI CLOCK TYPE IS CONTROL" << std::endl;
        snd_seq_remove_events(m_midiHandle, info);
    }

}




void
AlsaDriver::allNotesOff()
{
    snd_seq_event_t event;
    ClientPortPair outputDevice;
    RealTime offTime;

    // drop any pending notes
    snd_seq_drop_output_buffer(m_midiHandle);
    snd_seq_drop_output(m_midiHandle);

    // prepare the event
    snd_seq_ev_clear(&event);
    offTime = getAlsaTime();

    for (NoteOffQueue::iterator it = m_noteOffQueue.begin();
            it != m_noteOffQueue.end(); ++it) {
        // Set destination according to connection for instrument
        //
        outputDevice = getPairForMappedInstrument((*it)->getInstrument());
        if (outputDevice.first < 0 || outputDevice.second < 0)
            continue;

        snd_seq_ev_set_subs(&event);

        // Set source according to port for device
        //
        int src = getOutputPortForMappedInstrument((*it)->getInstrument());
        if (src < 0)
            continue;
        snd_seq_ev_set_source(&event, src);

        snd_seq_ev_set_noteoff(&event,
                               (*it)->getChannel(),
                               (*it)->getPitch(),
                               127);

        //snd_seq_event_output(m_midiHandle, &event);
        int error = snd_seq_event_output_direct(m_midiHandle, &event);

        if (error < 0) {
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::allNotesOff - "
            << "can't send event" << std::endl;
#endif

        }

        delete(*it);
    }

    m_noteOffQueue.erase(m_noteOffQueue.begin(), m_noteOffQueue.end());

    /*
    std::cerr << "AlsaDriver::allNotesOff - "
              << " queue size = " << m_noteOffQueue.size() << std::endl;
              */

    // flush
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "allNotesOff(): draining");
}

void
AlsaDriver::processNotesOff(const RealTime &time, bool now)
{
    if (m_noteOffQueue.empty())
        return ;

    snd_seq_event_t event;

    ClientPortPair outputDevice;
    RealTime offTime;

    // prepare the event
    snd_seq_ev_clear(&event);

#ifdef DEBUG_PROCESS_MIDI_OUT

    std::cerr << "AlsaDriver::processNotesOff(" << time << ")" << std::endl;
#endif

    while (m_noteOffQueue.begin() != m_noteOffQueue.end() &&
            (*m_noteOffQueue.begin())->getRealTime() < time) {

        NoteOffEvent *ev = *m_noteOffQueue.begin();

#ifdef DEBUG_PROCESS_MIDI_OUT

        std::cerr << "AlsaDriver::processNotesOff(" << time << "): found event at " << ev->getRealTime() << ", instr " << ev->getInstrument() << ", channel " << int(ev->getChannel()) << ", pitch " << int(ev->getPitch()) << std::endl;
#endif

        bool isSoftSynth = (ev->getInstrument() >= SoftSynthInstrumentBase);

        offTime = ev->getRealTime();

        snd_seq_real_time_t alsaOffTime = { offTime.sec,
                                            offTime.nsec };

        if (!isSoftSynth) {

            snd_seq_ev_set_subs(&event);

            // Set source according to instrument
            //
            int src = getOutputPortForMappedInstrument(ev->getInstrument());
            if (src < 0) {
                delete ev;
                m_noteOffQueue.erase(m_noteOffQueue.begin());
                continue;
            }

            snd_seq_ev_set_source(&event, src);
            snd_seq_ev_schedule_real(&event, m_queue, 0, &alsaOffTime);

        } else {

            event.time.time = alsaOffTime;
        }

        snd_seq_ev_set_noteoff(&event,
                               ev->getChannel(),
                               ev->getPitch(),
                               127);

        // send note off
        if (isSoftSynth) {
            processSoftSynthEventOut(ev->getInstrument(), &event, now);
        } else {
            snd_seq_event_output(m_midiHandle, &event);
        }

        delete ev;
        m_noteOffQueue.erase(m_noteOffQueue.begin());
    }

    // We don't flush the queue here, as this is called nested from
    // processMidiOut, which does the flushing

#ifdef DEBUG_PROCESS_MIDI_OUT
    std::cerr << "AlsaDriver::processNotesOff - "
    << " queue size now: " << m_noteOffQueue.size() << std::endl;
#endif
}

// Get the queue time and convert it to RealTime for the gui
// to use.
//
RealTime
AlsaDriver::getSequencerTime()
{
    RealTime t(0, 0);

    t = getAlsaTime() + m_playStartPosition - m_alsaPlayStartTime;

    //    std::cerr << "AlsaDriver::getSequencerTime: alsa time is "
    //	      << getAlsaTime() << ", start time is " << m_alsaPlayStartTime << ", play start position is " << m_playStartPosition << endl;

    return t;
}

// Gets the time of the ALSA queue
//
RealTime
AlsaDriver::getAlsaTime()
{
    RealTime sequencerTime(0, 0);

    snd_seq_queue_status_t *status;
    snd_seq_queue_status_alloca(&status);

    if (snd_seq_get_queue_status(m_midiHandle, m_queue, status) < 0) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::getAlsaTime - can't get queue status"
        << std::endl;
#endif

        return sequencerTime;
    }

    sequencerTime.sec = snd_seq_queue_status_get_real_time(status)->tv_sec;
    sequencerTime.nsec = snd_seq_queue_status_get_real_time(status)->tv_nsec;

    //    std::cerr << "AlsaDriver::getAlsaTime: alsa time is " << sequencerTime << std::endl;

    return sequencerTime;
}


// Get all pending input events and turn them into a MappedComposition.
//
//
MappedComposition*
AlsaDriver::getMappedComposition()
{
    m_recordComposition.clear();

    while (_failureReportReadIndex != _failureReportWriteIndex) {
        MappedEvent::FailureCode code = _failureReports[_failureReportReadIndex];
        //	std::cerr << "AlsaDriver::reportFailure(" << code << ")" << std::endl;
        MappedEvent *mE = new MappedEvent
                          (0, MappedEvent::SystemFailure, code, 0);
        m_returnComposition.insert(mE);
        _failureReportReadIndex =
            (_failureReportReadIndex + 1) % FAILURE_REPORT_COUNT;
    }

    if (!m_returnComposition.empty()) {
        for (MappedComposition::iterator i = m_returnComposition.begin();
                i != m_returnComposition.end(); ++i) {
            m_recordComposition.insert(new MappedEvent(**i));
        }
        m_returnComposition.clear();
    }

    // If the input port hasn't connected we shouldn't poll it
    //
    if (m_midiInputPortConnected == false) {
        return &m_recordComposition;
    }

    RealTime eventTime(0, 0);

    snd_seq_event_t *event;

    while (snd_seq_event_input(m_midiHandle, &event) > 0) {
        unsigned int channel = (unsigned int)event->data.note.channel;
        unsigned int chanNoteKey = ( channel << 8 ) +
                                   (unsigned int) event->data.note.note;

        bool fromController = false;
        if (event->dest.client == m_client &&
                event->dest.port == m_controllerPort) {
#ifdef DEBUG_ALSA
            std::cerr << "Received an external controller event" << std::endl;
#endif

            fromController = true;
        } else {
#ifdef DEBUG_ALSA
            std::cerr << "Received non-controller event (dest=" << (int)event->dest.client << ":" << (int)event->dest.port << ", controller is " << (int)m_client << ":" << (int)m_controllerPort << ")" << std::endl;
#endif

        }

        unsigned int deviceId = Device::NO_DEVICE;

        if (fromController) {
            deviceId = Device::CONTROL_DEVICE;
        } else {
            for (MappedDeviceList::iterator i = m_devices.begin();
                    i != m_devices.end(); ++i) {
                ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
                if (((*i)->getDirection() == MidiDevice::Record) &&
                        ( pair.first == event->source.client ) &&
                        ( pair.second == event->source.port )) {
                    deviceId = (*i)->getId();
                    break;
                }
            }
        }

        eventTime.sec = event->time.time.tv_sec;
        eventTime.nsec = event->time.time.tv_nsec;
        eventTime = eventTime - m_alsaRecordStartTime + m_playStartPosition;

        switch (event->type) {
        case SND_SEQ_EVENT_NOTE:
        case SND_SEQ_EVENT_NOTEON:
            if (fromController)
                continue;
            if (event->data.note.velocity > 0) {
                MappedEvent *mE = new MappedEvent();
                mE->setPitch(event->data.note.note);
                mE->setVelocity(event->data.note.velocity);
                mE->setEventTime(eventTime);
                mE->setRecordedChannel(channel);
                mE->setRecordedDevice(deviceId);

                // Negative duration - we need to hear the NOTE ON
                // so we must insert it now with a negative duration
                // and pick and mix against the following NOTE OFF
                // when we create the recorded segment.
                //
                mE->setDuration(RealTime( -1, 0));

                // Create a copy of this when we insert the NOTE ON -
                // keeping a copy alive on the m_noteOnMap.
                //
                // We shake out the two NOTE Ons after we've recorded
                // them.
                //
                m_recordComposition.insert(new MappedEvent(mE));
                m_noteOnMap[deviceId][chanNoteKey] = mE;

                break;
            }

        case SND_SEQ_EVENT_NOTEOFF:
            if (fromController)
                continue;
            if (m_noteOnMap[deviceId][chanNoteKey] != 0) {
                // Set duration correctly on the NOTE OFF
                //
                MappedEvent *mE = m_noteOnMap[deviceId][chanNoteKey];
                RealTime duration = eventTime - mE->getEventTime();

                if (duration < RealTime::zeroTime)
                    break;

                // Velocity 0 - NOTE OFF.  Set duration correctly
                // for recovery later.
                //
                mE->setVelocity(0);
                mE->setDuration(duration);

                // force shut off of note
                m_recordComposition.insert(mE);

                // reset the reference
                //
                m_noteOnMap[deviceId][chanNoteKey] = 0;

            }
            break;

        case SND_SEQ_EVENT_KEYPRESS: {
                if (fromController)
                    continue;

                // Fix for 632964 by Pedro Lopez-Cabanillas (20030523)
                //
                MappedEvent *mE = new MappedEvent();
                mE->setType(MappedEvent::MidiKeyPressure);
                mE->setEventTime(eventTime);
                mE->setData1(event->data.note.note);
                mE->setData2(event->data.note.velocity);
                mE->setRecordedChannel(channel);
                mE->setRecordedDevice(deviceId);
                m_recordComposition.insert(mE);
            }
            break;

        case SND_SEQ_EVENT_CONTROLLER: {
                MappedEvent *mE = new MappedEvent();
                mE->setType(MappedEvent::MidiController);
                mE->setEventTime(eventTime);
                mE->setData1(event->data.control.param);
                mE->setData2(event->data.control.value);
                mE->setRecordedChannel(channel);
                mE->setRecordedDevice(deviceId);
                m_recordComposition.insert(mE);
            }
            break;

        case SND_SEQ_EVENT_PGMCHANGE: {
                MappedEvent *mE = new MappedEvent();
                mE->setType(MappedEvent::MidiProgramChange);
                mE->setEventTime(eventTime);
                mE->setData1(event->data.control.value);
                mE->setRecordedChannel(channel);
                mE->setRecordedDevice(deviceId);
                m_recordComposition.insert(mE);

            }
            break;

        case SND_SEQ_EVENT_PITCHBEND: {
                if (fromController)
                    continue;

                // Fix for 711889 by Pedro Lopez-Cabanillas (20030523)
                //
                int s = event->data.control.value + 8192;
                int d1 = (s >> 7) & 0x7f; // data1 = MSB
                int d2 = s & 0x7f; // data2 = LSB
                MappedEvent *mE = new MappedEvent();
                mE->setType(MappedEvent::MidiPitchBend);
                mE->setEventTime(eventTime);
                mE->setData1(d1);
                mE->setData2(d2);
                mE->setRecordedChannel(channel);
                mE->setRecordedDevice(deviceId);
                m_recordComposition.insert(mE);
            }
            break;

        case SND_SEQ_EVENT_CHANPRESS: {
                if (fromController)
                    continue;

                // Fixed by Pedro Lopez-Cabanillas (20030523)
                //
                int s = event->data.control.value & 0x7f;
                MappedEvent *mE = new MappedEvent();
                mE->setType(MappedEvent::MidiChannelPressure);
                mE->setEventTime(eventTime);
                mE->setData1(s);
                mE->setRecordedChannel(channel);
                mE->setRecordedDevice(deviceId);
                m_recordComposition.insert(mE);
            }
            break;

        case SND_SEQ_EVENT_SYSEX:

            if (fromController)
                continue;

            if (!testForMTCSysex(event) &&
                    !testForMMCSysex(event)) {

                // Bundle up the data into a block on the MappedEvent
                //
                std::string data;
                char *ptr = (char*)(event->data.ext.ptr);
                for (unsigned int i = 0; i < event->data.ext.len; ++i)
                    data += *(ptr++);

#ifdef DEBUG_ALSA

                if ((MidiByte)(data[1]) == MIDI_SYSEX_RT) {
                    std::cerr << "REALTIME SYSEX" << endl;
                    for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
                        printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
                    }
                } else {
                    std::cerr << "NON-REALTIME SYSEX" << endl;
                    for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
                        printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
                    }
                }
#endif

                MappedEvent *mE = new MappedEvent();
                mE->setType(MappedEvent::MidiSystemMessage);
                mE->setData1(MIDI_SYSTEM_EXCLUSIVE);
                mE->setRecordedDevice(deviceId);
                // chop off SYX and EOX bytes from data block
                // Fix for 674731 by Pedro Lopez-Cabanillas (20030601)
                DataBlockRepository::setDataBlockForEvent(mE, data.substr(1, data.length() - 2));
                mE->setEventTime(eventTime);
                m_recordComposition.insert(mE);
            }
            break;


        case SND_SEQ_EVENT_SENSING:  // MIDI device is still there
            break;

        case SND_SEQ_EVENT_QFRAME:
            if (fromController)
                continue;
            if (getMTCStatus() == TRANSPORT_SLAVE) {
                handleMTCQFrame(event->data.control.value, eventTime);
            }
            break;

        case SND_SEQ_EVENT_CLOCK:
#ifdef DEBUG_ALSA

            std::cerr << "AlsaDriver::getMappedComposition - "
            << "got realtime MIDI clock" << std::endl;
#endif

            break;

        case SND_SEQ_EVENT_START:
            if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
                ExternalTransport *transport = getExternalTransportControl();
                if (transport) {
                    transport->transportJump(ExternalTransport::TransportStopAtTime,
                                             RealTime::zeroTime);
                    transport->transportChange(ExternalTransport::TransportStart);
                }
            }
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::getMappedComposition - "
            << "START" << std::endl;
#endif

            break;

        case SND_SEQ_EVENT_CONTINUE:
            if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
                ExternalTransport *transport = getExternalTransportControl();
                if (transport) {
                    transport->transportChange(ExternalTransport::TransportPlay);
                }
            }
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::getMappedComposition - "
            << "CONTINUE" << std::endl;
#endif

            break;

        case SND_SEQ_EVENT_STOP:
            if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && isPlaying()) {
                ExternalTransport *transport = getExternalTransportControl();
                if (transport) {
                    transport->transportChange(ExternalTransport::TransportStop);
                }
            }
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::getMappedComposition - "
            << "STOP" << std::endl;
#endif

            break;

        case SND_SEQ_EVENT_SONGPOS:
#ifdef DEBUG_ALSA

            std::cerr << "AlsaDriver::getMappedComposition - "
            << "SONG POSITION" << std::endl;
#endif

            break;

            // these cases are handled by checkForNewClients
            //
        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_portCheckNeeded = true;
#ifdef DEBUG_ALSA

            std::cerr << "AlsaDriver::getMappedComposition - "
            << "got announce event ("
            << int(event->type) << ")" << std::endl;
#endif

            break;
        case SND_SEQ_EVENT_TICK:
        default:
#ifdef DEBUG_ALSA

            std::cerr << "AlsaDriver::getMappedComposition - "
            << "got unhandled MIDI event type from ALSA sequencer"
            << "(" << int(event->type) << ")" << std::endl;
#endif

            break;


        }
    }

    if (getMTCStatus() == TRANSPORT_SLAVE && isPlaying()) {
#ifdef MTC_DEBUG
        std::cerr << "seq time is " << getSequencerTime() << ", last MTC receive "
        << m_mtcLastReceive << ", first time " << m_mtcFirstTime << std::endl;
#endif

        if (m_mtcFirstTime == 0) { // have received _some_ MTC quarter-frame info
            RealTime seqTime = getSequencerTime();
            if (m_mtcLastReceive < seqTime &&
                    seqTime - m_mtcLastReceive > RealTime(0, 500000000L)) {
                ExternalTransport *transport = getExternalTransportControl();
                if (transport) {
                    transport->transportJump(ExternalTransport::TransportStopAtTime,
                                             m_mtcLastEncoded);
                }
            }
        }
    }

    return &m_recordComposition;
}

static int lock_count = 0;

void
AlsaDriver::handleMTCQFrame(unsigned int data_byte, RealTime the_time)
{
    if (getMTCStatus() != TRANSPORT_SLAVE)
        return ;

    switch (data_byte & 0xF0) {
        /* Frame */
    case 0x00:
        /*
         * Reset everything
         */
        m_mtcReceiveTime = the_time;
        m_mtcFrames = data_byte & 0x0f;
        m_mtcSeconds = 0;
        m_mtcMinutes = 0;
        m_mtcHours = 0;
        m_mtcSMPTEType = 0;

        break;

    case 0x10:
        m_mtcFrames |= (data_byte & 0x0f) << 4;
        break;

        /* Seconds */
    case 0x20:
        m_mtcSeconds = data_byte & 0x0f;
        break;
    case 0x30:
        m_mtcSeconds |= (data_byte & 0x0f) << 4;
        break;

        /* Minutes */
    case 0x40:
        m_mtcMinutes = data_byte & 0x0f;
        break;
    case 0x50:
        m_mtcMinutes |= (data_byte & 0x0f) << 4;
        break;

        /* Hours and SMPTE type */
    case 0x60:
        m_mtcHours = data_byte & 0x0f;
        break;

    case 0x70: {
            m_mtcHours |= (data_byte & 0x01) << 4;
            m_mtcSMPTEType = (data_byte & 0x06) >> 1;

            int fps = 30;
            if (m_mtcSMPTEType == 0)
                fps = 24;
            else if (m_mtcSMPTEType == 1)
                fps = 25;

            /*
             * Ok, got all the bits now
             * (Assuming time is rolling forward)
             */

            /* correct for 2-frame lag */
            m_mtcFrames += 2;
            if (m_mtcFrames >= fps) {
                m_mtcFrames -= fps;
                if (++m_mtcSeconds == 60) {
                    m_mtcSeconds = 0;
                    if (++m_mtcMinutes == 60) {
                        m_mtcMinutes = 0;
                        ++m_mtcHours;
                    }
                }
            }

#ifdef MTC_DEBUG
            printf("RG MTC: Got a complete sequence: %02d:%02d:%02d.%02d (type %d)\n",
                   m_mtcHours,
                   m_mtcMinutes,
                   m_mtcSeconds,
                   m_mtcFrames,
                   m_mtcSMPTEType);
#endif

            /* compute encoded time */
            m_mtcEncodedTime.sec = m_mtcSeconds +
                                   m_mtcMinutes * 60 +
                                   m_mtcHours * 60 * 60;

            switch (fps) {
            case 24:
                m_mtcEncodedTime.nsec = (int)
                                        ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
                break;
            case 25:
                m_mtcEncodedTime.nsec = (int)
                                        (40000000UL * (unsigned)m_mtcFrames);
                break;
            case 30:
            default:
                m_mtcEncodedTime.nsec = (int)
                                        ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
                break;
            }

            /*
             * We only mess with the clock if we are playing
             */
            if (m_playing) {
#ifdef MTC_DEBUG
                std::cerr << "RG MTC: Tstamp " << m_mtcEncodedTime;
                std::cerr << " Received @ " << m_mtcReceiveTime << endl;
#endif

                calibrateMTC();

                RealTime t_diff = m_mtcEncodedTime - m_mtcReceiveTime;
#ifdef MTC_DEBUG

                std::cerr << "Diff: " << t_diff << endl;
#endif

                /* -ve diff means ALSA time ahead of MTC time */

                if (t_diff.sec > 0) {
                    tweakSkewForMTC(60000);
                } else if (t_diff.sec < 0) {
                    tweakSkewForMTC( -60000);
                } else {
                    /* "small" diff - use adaptive technique */
                    tweakSkewForMTC(t_diff.nsec / 1400);
                    if ((t_diff.nsec / 1000000) == 0) {
                        if (++lock_count == 3) {
                            printf("Got a lock @ %02d:%02d:%02d.%02d (type %d)\n",
                                   m_mtcHours,
                                   m_mtcMinutes,
                                   m_mtcSeconds,
                                   m_mtcFrames,
                                   m_mtcSMPTEType);
                        }
                    } else {
                        lock_count = 0;
                    }
                }

            } else if (m_eat_mtc > 0) {
#ifdef MTC_DEBUG
                std::cerr << "MTC: Received quarter frame just after issuing MMC stop - ignore it" << std::endl;
#endif

                --m_eat_mtc;
            } else {
                /* If we're not playing, we should be. */
#ifdef MTC_DEBUG
                std::cerr << "MTC: Received quarter frame while not playing - starting now" << std::endl;
#endif

                ExternalTransport *transport = getExternalTransportControl();
                if (transport) {
                    transport->transportJump
                    (ExternalTransport::TransportStartAtTime,
                     m_mtcEncodedTime);
                }
            }

            break;
        }

        /* Oh dear, demented device! */
    default:
        break;
    }
}

void
AlsaDriver::insertMTCFullFrame(RealTime time)
{
    snd_seq_event_t event;

    snd_seq_ev_clear(&event);
    snd_seq_ev_set_source(&event, m_syncOutputPort);
    snd_seq_ev_set_subs(&event);

    m_mtcEncodedTime = time;
    m_mtcSeconds = m_mtcEncodedTime.sec % 60;
    m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
    m_mtcHours = (m_mtcEncodedTime.sec / 3600);

    // We always send at 25fps, it's the easiest to avoid rounding problems
    m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U;

    time = time + m_alsaPlayStartTime - m_playStartPosition;
    snd_seq_real_time_t atime = { time.sec, time.nsec };

    unsigned char data[10] =
        { MIDI_SYSTEM_EXCLUSIVE,
          MIDI_SYSEX_RT, 127, 1, 1,
          0, 0, 0, 0,
          MIDI_END_OF_EXCLUSIVE };

    data[5] = ((unsigned char)m_mtcHours & 0x1f) + (1 << 5); // 1 indicates 25fps
    data[6] = (unsigned char)m_mtcMinutes;
    data[7] = (unsigned char)m_mtcSeconds;
    data[8] = (unsigned char)m_mtcFrames;

    snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
    snd_seq_ev_set_sysex(&event, 10, data);

    checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
                   "insertMTCFullFrame event send");

    if (m_queueRunning) {
        checkAlsaError(snd_seq_drain_output(m_midiHandle), "insertMTCFullFrame drain");
    }
}

void
AlsaDriver::insertMTCQFrames(RealTime sliceStart, RealTime sliceEnd)
{
    if (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime) {
        // not a real slice
        return ;
    }

    // We send at 25fps, it's the easiest to avoid rounding problems
    RealTime twoFrames(0, 80000000U);
    RealTime quarterFrame(0, 10000000U);
    int fps = 25;

#ifdef MTC_DEBUG

    std::cout << "AlsaDriver::insertMTCQFrames(" << sliceStart << ","
    << sliceEnd << "): first time " << m_mtcFirstTime << std::endl;
#endif

    RealTime t;

    if (m_mtcFirstTime != 0) { // first time through, reset location
        m_mtcEncodedTime = sliceStart;
        t = sliceStart;
        m_mtcFirstTime = 0;
    } else {
        t = m_mtcEncodedTime + quarterFrame;
    }

    m_mtcSeconds = m_mtcEncodedTime.sec % 60;
    m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
    m_mtcHours = (m_mtcEncodedTime.sec / 3600);
    m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U; // 25fps

    std::string bytes = " ";

    int type = 0;

    while (m_mtcEncodedTime < sliceEnd) {

        snd_seq_event_t event;
        snd_seq_ev_clear(&event);
        snd_seq_ev_set_source(&event, m_syncOutputPort);
        snd_seq_ev_set_subs(&event);

#ifdef MTC_DEBUG

        std::cout << "Sending MTC quarter frame at " << t << std::endl;
#endif

        unsigned char c = (type << 4);

        switch (type) {
        case 0:
            c += ((unsigned char)m_mtcFrames & 0x0f);
            break;
        case 1:
            c += (((unsigned char)m_mtcFrames & 0xf0) >> 4);
            break;
        case 2:
            c += ((unsigned char)m_mtcSeconds & 0x0f);
            break;
        case 3:
            c += (((unsigned char)m_mtcSeconds & 0xf0) >> 4);
            break;
        case 4:
            c += ((unsigned char)m_mtcMinutes & 0x0f);
            break;
        case 5:
            c += (((unsigned char)m_mtcMinutes & 0xf0) >> 4);
            break;
        case 6:
            c += ((unsigned char)m_mtcHours & 0x0f);
            break;
        case 7:  // hours high nibble + smpte type
            c += (m_mtcHours >> 4) & 0x01;
            c += (1 << 1); // type 1 indicates 25fps
            break;
        }

        RealTime scheduleTime = t + m_alsaPlayStartTime - m_playStartPosition;
        snd_seq_real_time_t atime = { scheduleTime.sec, scheduleTime.nsec };

        event.type = SND_SEQ_EVENT_QFRAME;
        event.data.control.value = c;

        snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);

        checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
                       "insertMTCQFrames sending qframe event");

        if (++type == 8) {
            m_mtcFrames += 2;
            if (m_mtcFrames >= fps) {
                m_mtcFrames -= fps;
                if (++m_mtcSeconds == 60) {
                    m_mtcSeconds = 0;
                    if (++m_mtcMinutes == 60) {
                        m_mtcMinutes = 0;
                        ++m_mtcHours;
                    }
                }
            }
            m_mtcEncodedTime = t;
            type = 0;
        }

        t = t + quarterFrame;
    }
}

bool
AlsaDriver::testForMTCSysex(const snd_seq_event_t *event)
{
    if (getMTCStatus() != TRANSPORT_SLAVE)
        return false;

    // At this point, and possibly for the foreseeable future, the only
    // sysex we're interested in is full-frame transport location

#ifdef MTC_DEBUG

    std::cerr << "MTC: testing sysex of length " << event->data.ext.len << ":" << std::endl;
    for (int i = 0; i < event->data.ext.len; ++i) {
        std::cerr << (int)*((unsigned char *)event->data.ext.ptr + i) << " ";
    }
    std::cerr << endl;
#endif

    if (event->data.ext.len != 10)
        return false;

    unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);

    if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
        return false;
    if (*ptr++ != MIDI_SYSEX_RT)
        return false;
    if (*ptr++ > 127)
        return false;

    // 01 01 for MTC full frame

    if (*ptr++ != 1)
        return false;
    if (*ptr++ != 1)
        return false;

    int htype = *ptr++;
    int min = *ptr++;
    int sec = *ptr++;
    int frame = *ptr++;

    if (*ptr != MIDI_END_OF_EXCLUSIVE)
        return false;

    int hour = (htype & 0x1f);
    int type = (htype & 0xe0) >> 5;

    m_mtcFrames = frame;
    m_mtcSeconds = sec;
    m_mtcMinutes = min;
    m_mtcHours = hour;
    m_mtcSMPTEType = type;

    int fps = 30;
    if (m_mtcSMPTEType == 0)
        fps = 24;
    else if (m_mtcSMPTEType == 1)
        fps = 25;

    m_mtcEncodedTime.sec = sec + min * 60 + hour * 60 * 60;

    switch (fps) {
    case 24:
        m_mtcEncodedTime.nsec = (int)
                                ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
        break;
    case 25:
        m_mtcEncodedTime.nsec = (int)
                                (40000000UL * (unsigned)m_mtcFrames);
        break;
    case 30:
    default:
        m_mtcEncodedTime.nsec = (int)
                                ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
        break;
    }

#ifdef MTC_DEBUG
    std::cerr << "MTC: MTC sysex found (frame type " << type
    << "), jumping to " << m_mtcEncodedTime << std::endl;
#endif

    ExternalTransport *transport = getExternalTransportControl();
    if (transport) {
        transport->transportJump
        (ExternalTransport::TransportJumpToTime,
         m_mtcEncodedTime);
    }

    return true;
}

static int last_factor = 0;
static int bias_factor = 0;

void
AlsaDriver::calibrateMTC()
{
    if (m_mtcFirstTime < 0)
        return ;
    else if (m_mtcFirstTime > 0) {
        --m_mtcFirstTime;
        m_mtcSigmaC = 0;
        m_mtcSigmaE = 0;
    } else {
        RealTime diff_e = m_mtcEncodedTime - m_mtcLastEncoded;
        RealTime diff_c = m_mtcReceiveTime - m_mtcLastReceive;

#ifdef MTC_DEBUG

        printf("RG MTC: diffs %d %d %d\n", diff_c.nsec, diff_e.nsec, m_mtcSkew);
#endif

        m_mtcSigmaE += ((long long int) diff_e.nsec) * m_mtcSkew;
        m_mtcSigmaC += diff_c.nsec;


        int t_bias = (m_mtcSigmaE / m_mtcSigmaC) - 0x10000;

#ifdef MTC_DEBUG

        printf("RG MTC: sigmas %lld %lld %d\n", m_mtcSigmaE, m_mtcSigmaC, t_bias);
#endif

        bias_factor = t_bias;
    }

    m_mtcLastReceive = m_mtcReceiveTime;
    m_mtcLastEncoded = m_mtcEncodedTime;

}

void
AlsaDriver::tweakSkewForMTC(int factor)
{
    if (factor > 50000) {
        factor = 50000;
    } else if (factor < -50000) {
        factor = -50000;
    } else if (factor == last_factor) {
        return ;
    } else {
        if (m_mtcFirstTime == -1)
            m_mtcFirstTime = 5;
    }
    last_factor = factor;

    snd_seq_queue_tempo_t *q_ptr;
    snd_seq_queue_tempo_alloca(&q_ptr);

    snd_seq_get_queue_tempo( m_midiHandle, m_queue, q_ptr);

    unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
#ifdef MTC_DEBUG

    std::cerr << "RG MTC: skew: " << t_skew;
#endif

    t_skew = 0x10000 + factor + bias_factor;

#ifdef MTC_DEBUG

    std::cerr << " changed to " << factor << "+" << bias_factor << endl;
#endif

    snd_seq_queue_tempo_set_skew(q_ptr, t_skew);
    snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);

    m_mtcSkew = t_skew;
}

bool
AlsaDriver::testForMMCSysex(const snd_seq_event_t *event)
{
    if (getMMCStatus() != TRANSPORT_SLAVE)
        return false;

    if (event->data.ext.len != 6)
        return false;

    unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);

    if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
        return false;
    if (*ptr++ != MIDI_SYSEX_RT)
        return false;
    if (*ptr++ > 127)
        return false;
    if (*ptr++ != MIDI_SYSEX_RT_COMMAND)
        return false;

    int instruction = *ptr++;

    if (*ptr != MIDI_END_OF_EXCLUSIVE)
        return false;

    if (instruction == MIDI_MMC_PLAY ||
            instruction == MIDI_MMC_DEFERRED_PLAY) {
        ExternalTransport *transport = getExternalTransportControl();
        if (transport) {
            transport->transportChange(ExternalTransport::TransportPlay);
        }
    } else if (instruction == MIDI_MMC_STOP) {
        ExternalTransport *transport = getExternalTransportControl();
        if (transport) {
            transport->transportChange(ExternalTransport::TransportStop);
        }
    }

    return true;
}

void
AlsaDriver::processMidiOut(const MappedComposition &mC,
                           const RealTime &sliceStart,
                           const RealTime &sliceEnd)
{
    RealTime outputTime;
    RealTime outputStopTime;
    MappedInstrument *instrument;
    ClientPortPair outputDevice;
    MidiByte channel;
    snd_seq_event_t event;

    // special case for unqueued events
    bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);

    // These won't change in this slice
    //
    snd_seq_ev_clear(&event);

    if ((mC.begin() != mC.end()) && getSequencerDataBlock()) {
        getSequencerDataBlock()->setVisual(*mC.begin());
    }

#ifdef DEBUG_PROCESS_MIDI_OUT
    std::cerr << "AlsaDriver::processMidiOut(" << sliceStart << "," << sliceEnd
    << "), " << mC.size() << " events, now is " << now << std::endl;
#endif

    // NB the MappedComposition is implicitly ordered by time (std::multiset)

    for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
        if ((*i)->getType() >= MappedEvent::Audio)
            continue;

        bool isControllerOut = ((*i)->getRecordedDevice() ==
                                Device::CONTROL_DEVICE);

        bool isSoftSynth = (!isControllerOut &&
                            ((*i)->getInstrument() >= SoftSynthInstrumentBase));

        outputTime = (*i)->getEventTime() - m_playStartPosition +
                     m_alsaPlayStartTime;

        if (now && !m_playing && m_queueRunning) {
            // stop queue to ensure exact timing and make sure the
            // event gets through right now
#ifdef DEBUG_PROCESS_MIDI_OUT
            std::cerr << "processMidiOut: stopping queue for now-event" << std::endl;
#endif

            checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): stop queue");
            checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
        }

        RealTime alsaTimeNow = getAlsaTime();

        if (now) {
            if (!m_playing) {
                outputTime = alsaTimeNow;
            } else if (outputTime < alsaTimeNow) {
                outputTime = alsaTimeNow + RealTime(0, 10000000);
            }
        }

#ifdef DEBUG_PROCESS_MIDI_OUT
        std::cerr << "processMidiOut[" << now << "]: event is at " << outputTime << " (" << outputTime - alsaTimeNow << " ahead of queue time), type " << int((*i)->getType()) << ", duration " << (*i)->getDuration() << std::endl;
#endif

        if (!m_queueRunning && outputTime < alsaTimeNow) {
            RealTime adjust = alsaTimeNow - outputTime;
            if ((*i)->getDuration() > RealTime::zeroTime) {
                if ((*i)->getDuration() <= adjust) {
#ifdef DEBUG_PROCESS_MIDI_OUT
                    std::cerr << "processMidiOut[" << now << "]: too late for this event, abandoning it" << std::endl;
#endif

                    continue;
                } else {
#ifdef DEBUG_PROCESS_MIDI_OUT
                    std::cerr << "processMidiOut[" << now << "]: pushing event forward and reducing duration by " << adjust << std::endl;
#endif

                    (*i)->setDuration((*i)->getDuration() - adjust);
                }
            } else {
#ifdef DEBUG_PROCESS_MIDI_OUT
                std::cerr << "processMidiOut[" << now << "]: pushing zero-duration event forward by " << adjust << std::endl;
#endif

            }
            outputTime = alsaTimeNow;
        }

        processNotesOff(outputTime, now);

#ifdef HAVE_LIBJACK

        if (m_jackDriver) {
            size_t frameCount = m_jackDriver->getFramesProcessed();
            size_t elapsed = frameCount - _debug_jack_frame_count;
            RealTime rt = RealTime::frame2RealTime(elapsed, m_jackDriver->getSampleRate());
            rt = rt - getAlsaTime();
#ifdef DEBUG_PROCESS_MIDI_OUT

            std::cerr << "processMidiOut[" << now << "]: JACK time is " << rt << " ahead of ALSA time" << std::endl;
#endif

        }
#endif

        // Second and nanoseconds for ALSA
        //
        snd_seq_real_time_t time = { outputTime.sec, outputTime.nsec };

        if (!isSoftSynth) {

#ifdef DEBUG_PROCESS_MIDI_OUT
            std::cout << "processMidiOut[" << now << "]: instrument " << (*i)->getInstrument() << std::endl;
            std::cout << "pitch: " << (int)(*i)->getPitch() << ", velocity " << (int)(*i)->getVelocity() << ", duration " << (*i)->getDuration() << std::endl;
#endif

            snd_seq_ev_set_subs(&event);

            // Set source according to port for device
            //
            int src;

            if (isControllerOut) {
                src = m_controllerPort;
            } else {
                src = getOutputPortForMappedInstrument((*i)->getInstrument());
            }

            if (src < 0)
                continue;
            snd_seq_ev_set_source(&event, src);

            snd_seq_ev_schedule_real(&event, m_queue, 0, &time);

        } else {
            event.time.time = time;
        }

        instrument = getMappedInstrument((*i)->getInstrument());

        // set the stop time for Note Off
        //
        outputStopTime = outputTime + (*i)->getDuration()
                         - RealTime(0, 1); // notch it back 1nsec just to ensure
        // correct ordering against any other
        // note-ons at the same nominal time
        bool needNoteOff = false;

        if (isControllerOut) {
            channel = (*i)->getRecordedChannel();
#ifdef DEBUG_ALSA

            std::cerr << "processMidiOut() - Event of type " << (int)((*i)->getType()) << " (data1 " << (int)(*i)->getData1() << ", data2 " << (int)(*i)->getData2() << ") for external controller channel " << (int)channel << std::endl;
#endif

        } else if (instrument != 0) {
            channel = instrument->getChannel();
        } else {
#ifdef DEBUG_ALSA
            std::cerr << "processMidiOut() - No instrument for event of type "
            << (int)(*i)->getType() << " at " << (*i)->getEventTime()
            << std::endl;
#endif

            channel = 0;
        }

        switch ((*i)->getType()) {
        case MappedEvent::MidiNoteOneShot: {
                snd_seq_ev_set_noteon(&event,
                                      channel,
                                      (*i)->getPitch(),
                                      (*i)->getVelocity());
                needNoteOff = true;

                if (!isSoftSynth && getSequencerDataBlock()) {
                    LevelInfo info;
                    info.level = (*i)->getVelocity();
                    info.levelRight = 0;
                    getSequencerDataBlock()->setInstrumentLevel
                    ((*i)->getInstrument(), info);
                }
            }
            break;

        case MappedEvent::MidiNote:
            // We always use plain NOTE ON here, not ALSA
            // time+duration notes, because we have our own NOTE
            // OFF stack (which will be augmented at the bottom of
            // this function) and we want to ensure it gets used
            // for the purposes of e.g. soft synths
            //
            if ((*i)->getVelocity() > 0) {
                snd_seq_ev_set_noteon(&event,
                                      channel,
                                      (*i)->getPitch(),
                                      (*i)->getVelocity());

                if (!isSoftSynth && getSequencerDataBlock()) {
                    LevelInfo info;
                    info.level = (*i)->getVelocity();
                    info.levelRight = 0;
                    getSequencerDataBlock()->setInstrumentLevel
                    ((*i)->getInstrument(), info);
                }
            } else {
                snd_seq_ev_set_noteoff(&event,
                                       channel,
                                       (*i)->getPitch(),
                                       (*i)->getVelocity());
            }

            break;

        case MappedEvent::MidiProgramChange:
            snd_seq_ev_set_pgmchange(&event,
                                     channel,
                                     (*i)->getData1());
            break;

        case MappedEvent::MidiKeyPressure:
            snd_seq_ev_set_keypress(&event,
                                    channel,
                                    (*i)->getData1(),
                                    (*i)->getData2());
            break;

        case MappedEvent::MidiChannelPressure:
            snd_seq_ev_set_chanpress(&event,
                                     channel,
                                     (*i)->getData1());
            break;

        case MappedEvent::MidiPitchBend: {
                int d1 = (int)((*i)->getData1());
                int d2 = (int)((*i)->getData2());
                int value = ((d1 << 7) | d2) - 8192;

                // keep within -8192 to +8192
                //
                // if (value & 0x4000)
                //    value -= 0x8000;

                snd_seq_ev_set_pitchbend(&event,
                                         channel,
                                         value);
            }
            break;

        case MappedEvent::MidiSystemMessage: {
                switch ((*i)->getData1()) {
                case MIDI_SYSTEM_EXCLUSIVE: {
                        char out[2];
                        sprintf(out, "%c", MIDI_SYSTEM_EXCLUSIVE);
                        std::string data = out;

                        data += DataBlockRepository::getDataBlockForEvent((*i));

                        sprintf(out, "%c", MIDI_END_OF_EXCLUSIVE);
                        data += out;

                        snd_seq_ev_set_sysex(&event,
                                             data.length(),
                                             (char*)(data.c_str()));
                    }
                    break;

                case MIDI_TIMING_CLOCK: {
                        RealTime rt =
                            RealTime(time.tv_sec, time.tv_nsec);

                        /*
                        std::cerr << "AlsaDriver::processMidiOut - "
                                  << "send clock @ " << rt << std::endl;
                                  */

                        sendSystemQueued(SND_SEQ_EVENT_CLOCK, "", rt);

                        continue;

                    }
                    break;

                default:
                    std::cerr << "AlsaDriver::processMidiOut - "
                    << "unrecognised system message"
                    << std::endl;
                    break;
                }
            }
            break;

        case MappedEvent::MidiController:
            snd_seq_ev_set_controller(&event,
                                      channel,
                                      (*i)->getData1(),
                                      (*i)->getData2());
            break;

        case MappedEvent::Audio:
        case MappedEvent::AudioCancel:
        case MappedEvent::AudioLevel:
        case MappedEvent::AudioStopped:
        case MappedEvent::SystemUpdateInstruments:
        case MappedEvent::SystemJackTransport:  //???
        case MappedEvent::SystemMMCTransport:
        case MappedEvent::SystemMIDIClock:
        case MappedEvent::SystemMIDISyncAuto:
            break;

        default:
        case MappedEvent::InvalidMappedEvent:
#ifdef DEBUG_ALSA

            std::cerr << "AlsaDriver::processMidiOut - "
            << "skipping unrecognised or invalid MappedEvent type"
            << std::endl;
#endif

            continue;
        }

        if (isSoftSynth) {

            processSoftSynthEventOut((*i)->getInstrument(), &event, now);

        } else {
            checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
                           "processMidiOut(): output queued");

            if (now) {
                if (m_queueRunning && !m_playing) {
                    // restart queue
#ifdef DEBUG_PROCESS_MIDI_OUT
                    std::cerr << "processMidiOut: restarting queue after now-event" << std::endl;
#endif

                    checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
                }
                checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
            }
        }

        // Add note to note off stack
        //
        if (needNoteOff) {
            NoteOffEvent *noteOffEvent =
                new NoteOffEvent(outputStopTime,  // already calculated
                                 (*i)->getPitch(),
                                 channel,
                                 (*i)->getInstrument());

#ifdef DEBUG_ALSA

            std::cerr << "Adding NOTE OFF at " << outputStopTime
            << std::endl;
#endif

            m_noteOffQueue.insert(noteOffEvent);
        }
    }

    processNotesOff(sliceEnd - m_playStartPosition + m_alsaPlayStartTime, now);

    if (getMTCStatus() == TRANSPORT_MASTER) {
        insertMTCQFrames(sliceStart, sliceEnd);
    }

    if (m_queueRunning) {

        if (now && !m_playing) {
            // just to be sure
#ifdef DEBUG_PROCESS_MIDI_OUT
            std::cerr << "processMidiOut: restarting queue after all now-events" << std::endl;
#endif

            checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
        }

#ifdef DEBUG_PROCESS_MIDI_OUT 
        //	std::cerr << "processMidiOut: m_queueRunning " << m_queueRunning
        //		  << ", now " << now << std::endl;
#endif
        checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
    }
}

void
AlsaDriver::processSoftSynthEventOut(InstrumentId id, const snd_seq_event_t *ev, bool now)
{
#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::processSoftSynthEventOut: instrument " << id << ", now " << now << std::endl;
#endif

#ifdef HAVE_LIBJACK

    if (!m_jackDriver)
        return ;
    RunnablePluginInstance *synthPlugin = m_jackDriver->getSynthPlugin(id);

    if (synthPlugin) {

        RealTime t(ev->time.time.tv_sec, ev->time.time.tv_nsec);

        if (now)
            t = RealTime::zeroTime;
        else
            t = t + m_playStartPosition - m_alsaPlayStartTime;

#ifdef DEBUG_ALSA

        std::cerr << "AlsaDriver::processSoftSynthEventOut: time " << t << std::endl;
#endif

        synthPlugin->sendEvent(t, ev);

        if (now) {
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::processSoftSynthEventOut: setting haveAsyncAudioEvent" << std::endl;
#endif

            m_jackDriver->setHaveAsyncAudioEvent();
        }
    }
#endif
}

void
AlsaDriver::startClocks()
{
    int result;

#ifdef DEBUG_ALSA

    std::cerr << "AlsaDriver::startClocks" << std::endl;
#endif

    if (m_needJackStart) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::startClocks: Need JACK start (m_playing = " << m_playing << ")" << std::endl;
#endif

    }

#ifdef HAVE_LIBJACK

    // New JACK transport scheme: The initialisePlayback,
    // resetPlayback and stopPlayback methods set m_needJackStart, and
    // then this method checks it and calls the appropriate JACK
    // transport start or relocate method, which calls back on
    // startClocksApproved when ready.  (Previously this method always
    // called the JACK transport start method, so we couldn't handle
    // moving the pointer when not playing, and we had to stop the
    // transport explicitly from resetPlayback when repositioning
    // during playback.)

    if (m_jackDriver) {

        // Don't need any locks on this, except for those that the
        // driver methods take and hold for themselves

        if (m_needJackStart != NeedNoJackStart) {
            if (m_needJackStart == NeedJackStart ||
                    m_playing) {
#ifdef DEBUG_ALSA
                std::cerr << "AlsaDriver::startClocks: playing, prebuffer audio" << std::endl;
#endif

                m_jackDriver->prebufferAudio();
            } else {
#ifdef DEBUG_ALSA
                std::cerr << "AlsaDriver::startClocks: prepare audio only" << std::endl;
#endif

                m_jackDriver->prepareAudio();
            }
            bool rv;
            if (m_needJackStart == NeedJackReposition) {
                rv = m_jackDriver->relocateTransport();
            } else {
                rv = m_jackDriver->startTransport();
                if (!rv) {
#ifdef DEBUG_ALSA
                    std::cerr << "AlsaDriver::startClocks: Waiting for startClocksApproved" << std::endl;
#endif
                    // need to wait for transport sync
                    _debug_jack_frame_count = m_jackDriver->getFramesProcessed();
                    return ;
                }
            }
        }
    }
#endif

    // Restart the timer
    if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
        std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
        << snd_strerror(result)
        << std::endl;
        reportFailure(MappedEvent::FailureALSACallFailed);
    }

#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::startClocks: started clocks" << std::endl;
#endif

    m_queueRunning = true;

#ifdef HAVE_LIBJACK

    if (m_jackDriver) {
        _debug_jack_frame_count = m_jackDriver->getFramesProcessed();
    }
#endif

    // process pending MIDI events
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocks(): draining");
}

void
AlsaDriver::startClocksApproved()
{
#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::startClocks: startClocksApproved" << std::endl;
#endif

    //!!!
    m_needJackStart = NeedNoJackStart;
    startClocks();
    return ;

    int result;

    // Restart the timer
    if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
        std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
        << snd_strerror(result)
        << std::endl;
        reportFailure(MappedEvent::FailureALSACallFailed);
    }

    m_queueRunning = true;

    // process pending MIDI events
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocksApproved(): draining");
}

void
AlsaDriver::stopClocks()
{
#ifdef DEBUG_ALSA
    std::cerr << "AlsaDriver::stopClocks" << std::endl;
#endif

    if (checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "stopClocks(): stopping queue") < 0) {
        reportFailure(MappedEvent::FailureALSACallFailed);
    }
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to stop queue");

    m_queueRunning = false;

    // We used to call m_jackDriver->stop() from here, but we no
    // longer do -- it's now called from stopPlayback() so as to
    // handle repositioning during playback (when stopClocks is
    // necessary but stopPlayback and m_jackDriver->stop() are not).

    snd_seq_event_t event;
    snd_seq_ev_clear(&event);
    snd_seq_real_time_t z = { 0, 0 };
    snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
    snd_seq_ev_set_direct(&event);
    checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
                                         0, &event), "stopClocks(): setting zpos to queue");
    // process that
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to zpos queue");

#ifdef DEBUG_ALSA

    std::cerr << "AlsaDriver::stopClocks: ALSA time now is " << getAlsaTime() << std::endl;
#endif

    m_alsaPlayStartTime = RealTime::zeroTime;
}


void
AlsaDriver::processEventsOut(const MappedComposition &mC)
{
    processEventsOut(mC, RealTime::zeroTime, RealTime::zeroTime);
}

void
AlsaDriver::processEventsOut(const MappedComposition &mC,
                             const RealTime &sliceStart,
                             const RealTime &sliceEnd)
{
    // special case for unqueued events
    bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);

    if (m_startPlayback) {
        m_startPlayback = false;
        // This only records whether we're playing in principle,
        // not whether the clocks are actually ticking.  Contrariwise,
        // areClocksRunning tells us whether the clocks are ticking
        // but not whether we're actually playing (the clocks go even
        // when we're not).  Check both if you want to know whether
        // we're really rolling.
        m_playing = true;

        if (getMTCStatus() == TRANSPORT_SLAVE) {
            tweakSkewForMTC(0);
        }
    }

    AudioFile *audioFile = 0;
    bool haveNewAudio = false;

    // insert audio events if we find them
    for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
#ifdef HAVE_LIBJACK

        // Play an audio file
        //
        if ((*i)->getType() == MappedEvent::Audio) {
            if (!m_jackDriver)
                continue;

            // This is used for handling asynchronous
            // (i.e. unexpected) audio events only

            if ((*i)->getEventTime() > RealTime( -120, 0)) {
                // Not an asynchronous event
                continue;
            }

            // Check for existence of file - if the sequencer has died
            // and been restarted then we're not always loaded up with
            // the audio file references we should have.  In the future
            // we could make this just get the gui to reload our files
            // when (or before) this fails.
            //
            audioFile = getAudioFile((*i)->getAudioID());

            if (audioFile) {
                MappedAudioFader *fader =
                    dynamic_cast<MappedAudioFader*>
                    (getMappedStudio()->getAudioFader((*i)->getInstrument()));

                if (!fader) {
                    std::cerr << "WARNING: AlsaDriver::processEventsOut: no fader for audio instrument " << (*i)->getInstrument() << std::endl;
                    continue;
                }

                unsigned int channels = fader->getPropertyList(
                                            MappedAudioFader::Channels)[0].toInt();

                RealTime bufferLength = getAudioReadBufferLength();
                int bufferFrames = RealTime::realTime2Frame
                                   (bufferLength, m_jackDriver->getSampleRate());
                if (bufferFrames % m_jackDriver->getBufferSize()) {
                    bufferFrames /= m_jackDriver->getBufferSize();
                    bufferFrames ++;
                    bufferFrames *= m_jackDriver->getBufferSize();
                }

                //#define DEBUG_PLAYING_AUDIO
#ifdef DEBUG_PLAYING_AUDIO
                std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << (*i)->getEventTime() << ", time now " << getAlsaTime() << ", start marker " << (*i)->getAudioStartMarker() << ", duration " << (*i)->getDuration() << ", instrument " << (*i)->getInstrument() << " channels " << channels << std::endl;

                std::cout << "Read buffer length is " << bufferLength << " (" << bufferFrames << " frames)" << std::endl;
#endif

                PlayableAudioFile *paf = 0;

                try {
                    paf = new PlayableAudioFile((*i)->getInstrument(),
                                                audioFile,
                                                getSequencerTime() +
                                                (RealTime(1, 0) / 4),
                                                (*i)->getAudioStartMarker(),
                                                (*i)->getDuration(),
                                                bufferFrames,
                                                getSmallFileSize() * 1024,
                                                channels,
                                                m_jackDriver->getSampleRate());
                } catch (...) {
                    continue;
                }

                if ((*i)->isAutoFading()) {
                    paf->setAutoFade(true);
                    paf->setFadeInTime((*i)->getFadeInTime());
                    paf->setFadeOutTime((*i)->getFadeInTime());

                    //#define DEBUG_AUTOFADING
#ifdef DEBUG_AUTOFADING

                    std::cout << "PlayableAudioFile is AUTOFADING - "
                    << "in = " << (*i)->getFadeInTime()
                    << ", out = " << (*i)->getFadeOutTime()
                    << std::endl;
#endif

                }
#ifdef DEBUG_AUTOFADING
                else {
                    std::cout << "PlayableAudioFile has no AUTOFADE"
                    << std::endl;
                }
#endif


                // segment runtime id
                paf->setRuntimeSegmentId((*i)->getRuntimeSegmentId());

                m_audioQueue->addUnscheduled(paf);

                haveNewAudio = true;
            } else {
#ifdef DEBUG_ALSA
                std::cerr << "AlsaDriver::processEventsOut - "
                << "can't find audio file reference"
                << std::endl;

                std::cerr << "AlsaDriver::processEventsOut - "
                << "try reloading the current Rosegarden file"
                << std::endl;
#else

                ;
#endif

            }
        }

        // Cancel a playing audio file preview (this is predicated on
        // runtime segment ID and optionally start time)
        //
        if ((*i)->getType() == MappedEvent::AudioCancel) {
            cancelAudioFile(*i);
        }

#endif // HAVE_LIBJACK

        if ((*i)->getType() == MappedEvent::SystemMIDIClock) {
            switch ((int)(*i)->getData1()) {
            case 0:
                m_midiClockEnabled = false;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden MIDI CLOCK, START and STOP DISABLED"
                << std::endl;
#endif

                setMIDISyncStatus(TRANSPORT_OFF);
                break;

            case 1:
                m_midiClockEnabled = true;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden send MIDI CLOCK, START and STOP ENABLED"
                << std::endl;
#endif

                setMIDISyncStatus(TRANSPORT_MASTER);
                break;

            case 2:
                m_midiClockEnabled = false;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden accept START and STOP ENABLED"
                << std::endl;
#endif

                setMIDISyncStatus(TRANSPORT_SLAVE);
                break;
            }
        }

        if ((*i)->getType() == MappedEvent::SystemMIDISyncAuto) {
            if ((*i)->getData1()) {
                m_midiSyncAutoConnect = true;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden MIDI SYNC AUTO ENABLED"
                << std::endl;
#endif

                for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
                        dpmi != m_devicePortMap.end(); ++dpmi) {
                    snd_seq_connect_to(m_midiHandle,
                                       m_syncOutputPort,
                                       dpmi->second.first,
                                       dpmi->second.second);
                }
            } else {
                m_midiSyncAutoConnect = false;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden MIDI SYNC AUTO DISABLED"
                << std::endl;
#endif

            }
        }

#ifdef HAVE_LIBJACK

        // Set the JACK transport
        if ((*i)->getType() == MappedEvent::SystemJackTransport) {
            bool enabled = false;
            bool master = false;

            switch ((int)(*i)->getData1()) {
            case 2:
                master = true;
                enabled = true;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden to follow JACK transport and request JACK timebase master role (not yet implemented)"
                << std::endl;
#endif

                break;

            case 1:
                enabled = true;
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden to follow JACK transport"
                << std::endl;
#endif

                break;

            case 0:
            default:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden to ignore JACK transport"
                << std::endl;
#endif

                break;
            }

            if (m_jackDriver) {
                m_jackDriver->setTransportEnabled(enabled);
                m_jackDriver->setTransportMaster(master);
            }
        }
#endif // HAVE_LIBJACK


        if ((*i)->getType() == MappedEvent::SystemMMCTransport) {
            switch ((int)(*i)->getData1()) {
            case 1:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden is MMC MASTER"
                << std::endl;
#endif

                setMMCStatus(TRANSPORT_MASTER);
                break;

            case 2:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden is MMC SLAVE"
                << std::endl;
#endif

                setMMCStatus(TRANSPORT_SLAVE);
                break;

            case 0:
            default:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden MMC Transport DISABLED"
                << std::endl;
#endif

                setMMCStatus(TRANSPORT_OFF);
                break;
            }
        }

        if ((*i)->getType() == MappedEvent::SystemMTCTransport) {
            switch ((int)(*i)->getData1()) {
            case 1:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden is MTC MASTER"
                << std::endl;
#endif

                setMTCStatus(TRANSPORT_MASTER);
                tweakSkewForMTC(0);
                m_mtcFirstTime = -1;
                break;

            case 2:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden is MTC SLAVE"
                << std::endl;
#endif

                setMTCStatus(TRANSPORT_SLAVE);
                m_mtcFirstTime = -1;
                break;

            case 0:
            default:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "Rosegarden MTC Transport DISABLED"
                << std::endl;
#endif

                setMTCStatus(TRANSPORT_OFF);
                m_mtcFirstTime = -1;
                break;
            }
        }

        if ((*i)->getType() == MappedEvent::SystemRecordDevice) {
            DeviceId recordDevice =
                (DeviceId)((*i)->getData1());
            bool conn = (bool) ((*i)->getData2());

            // Unset connections
            //
            // unsetRecordDevices();

            // Special case to set for all record ports
            //
            if (recordDevice == Device::ALL_DEVICES) {
                /* set all record devices */
#ifdef DEBUG_ALSA
                std::cerr << "AlsaDriver::processEventsOut - "
                << "set all record devices - not implemented"
                << std::endl;
#endif

                /*
                MappedDeviceList::iterator it = m_devices.begin();
                std::vector<int> ports;
                std::vector<int>::iterator pIt;

                for (; it != m_devices.end(); ++it)
                {
                    std::cout << "DEVICE = " << (*it)->getName() << " - DIR = "
                         << (*it)->getDirection() << endl;
                    // ignore ports we can't connect to
                    if ((*it)->getDirection() == MidiDevice::WriteOnly) continue;

                    std::cout << "PORTS = " << ports.size() << endl;
                    ports = (*it)->getPorts();
                    for (pIt = ports.begin(); pIt != ports.end(); ++pIt)
                    {
                        setRecordDevice((*it)->getClient(), *pIt);
                    }
                }
                */
            } else {
                // Otherwise just for the one device and port
                //
                setRecordDevice(recordDevice, conn);
            }
        }

        if ((*i)->getType() == MappedEvent::SystemAudioPortCounts) {
            // never actually used, I think?
        }

        if ((*i)->getType() == MappedEvent::SystemAudioPorts) {
#ifdef HAVE_LIBJACK
            if (m_jackDriver) {
                int data = (*i)->getData1();
                m_jackDriver->setAudioPorts(data & MappedEvent::FaderOuts,
                                            data & MappedEvent::SubmasterOuts);
            }
#else
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::processEventsOut - "
            << "MappedEvent::SystemAudioPorts - no audio subsystem"
            << std::endl;
#endif
#endif

        }

        if ((*i)->getType() == MappedEvent::SystemAudioFileFormat) {
#ifdef HAVE_LIBJACK
            int format = (*i)->getData1();
            switch (format) {
            case 0:
                m_audioRecFileFormat = RIFFAudioFile::PCM;
                break;
            case 1:
                m_audioRecFileFormat = RIFFAudioFile::FLOAT;
                break;
            default:
#ifdef DEBUG_ALSA

                std::cerr << "AlsaDriver::processEventsOut - "
                << "MappedEvent::SystemAudioFileFormat - unexpected format number " << format
                << std::endl;
#endif

                break;
            }
#else
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::processEventsOut - "
            << "MappedEvent::SystemAudioFileFormat - no audio subsystem"
            << std::endl;
#endif
#endif

        }

        if ((*i)->getType() == MappedEvent::Panic) {
            for (MappedDeviceList::iterator i = m_devices.begin();
                    i != m_devices.end(); ++i) {
                if ((*i)->getDirection() == MidiDevice::Play) {
                    sendDeviceController((*i)->getId(),
                                         MIDI_CONTROLLER_SUSTAIN, 0);
                    sendDeviceController((*i)->getId(),
                                         MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
                    sendDeviceController((*i)->getId(),
                                         MIDI_CONTROLLER_RESET, 0);
                }
            }
        }
    }

    // Process Midi and Audio
    //
    processMidiOut(mC, sliceStart, sliceEnd);

#ifdef HAVE_LIBJACK

    if (m_jackDriver) {
        if (haveNewAudio) {
            if (now) {
                m_jackDriver->prebufferAudio();
                m_jackDriver->setHaveAsyncAudioEvent();
            }
            if (m_queueRunning) {
                m_jackDriver->kickAudio();
            }
        }
    }
#endif
}

bool
AlsaDriver::record(RecordStatus recordStatus,
                   const std::vector<InstrumentId> *armedInstruments,
                   const std::vector<QString> *audioFileNames)
{
    m_recordingInstruments.clear();

    if (recordStatus == RECORD_ON) {
        // start recording
        m_recordStatus = RECORD_ON;
        m_alsaRecordStartTime = RealTime::zeroTime;

        unsigned int audioCount = 0;

        if (armedInstruments) {

            for (unsigned int i = 0; i < armedInstruments->size(); ++i) {

                InstrumentId id = (*armedInstruments)[i];

                m_recordingInstruments.insert(id);
                if (!audioFileNames || (audioCount >= audioFileNames->size())) {
                    continue;
                }

                QString fileName = (*audioFileNames)[audioCount];

                if (id >= AudioInstrumentBase &&
                        id < MidiInstrumentBase) {

                    bool good = false;

#ifdef DEBUG_ALSA

                    std::cerr << "AlsaDriver::record: Requesting new record file \"" << fileName << "\" for instrument " << id << std::endl;
#endif

#ifdef HAVE_LIBJACK

                    if (m_jackDriver &&
                            m_jackDriver->openRecordFile(id, fileName.data())) {
                        good = true;
                    }
#endif

                    if (!good) {
                        m_recordStatus = RECORD_OFF;
                        std::cerr << "AlsaDriver::record: No JACK driver, or JACK driver failed to prepare for recording audio" << std::endl;
                        return false;
                    }

                    ++audioCount;
                }
            }
        }
    } else
        if (recordStatus == RECORD_OFF) {
            m_recordStatus = RECORD_OFF;
        }
#ifdef DEBUG_ALSA
        else {
            std::cerr << "AlsaDriver::record - unsupported recording mode"
            << std::endl;
        }
#endif

    return true;
}

ClientPortPair
AlsaDriver::getFirstDestination(bool duplex)
{
    ClientPortPair destPair( -1, -1);
    AlsaPortList::iterator it;

    for (it = m_alsaPorts.begin(); it != m_alsaPorts.end(); ++it) {
        destPair.first = (*it)->m_client;
        destPair.second = (*it)->m_port;

        // If duplex port is required then choose first one
        //
        if (duplex) {
            if ((*it)->m_direction == Duplex)
                return destPair;
        } else {
            // If duplex port isn't required then choose first
            // specifically non-duplex port (should be a synth)
            //
            if ((*it)->m_direction != Duplex)
                return destPair;
        }
    }

    return destPair;
}


// Sort through the ALSA client/port pairs for the range that
// matches the one we're querying.  If none matches then send
// back -1 for each.
//
ClientPortPair
AlsaDriver::getPairForMappedInstrument(InstrumentId id)
{
    MappedInstrument *instrument = getMappedInstrument(id);
    if (instrument) {
        DeviceId device = instrument->getDevice();
        DevicePortMap::iterator i = m_devicePortMap.find(device);
        if (i != m_devicePortMap.end()) {
            return i->second;
        }
    }
#ifdef DEBUG_ALSA
    /*
    else
    {
    cerr << "WARNING: AlsaDriver::getPairForMappedInstrument: couldn't find instrument for id " << id << ", falling through" << endl;
    }
    */
#endif

    return ClientPortPair( -1, -1);
}

int
AlsaDriver::getOutputPortForMappedInstrument(InstrumentId id)
{
    MappedInstrument *instrument = getMappedInstrument(id);
    if (instrument) {
        DeviceId device = instrument->getDevice();
        DeviceIntMap::iterator i = m_outputPorts.find(device);
        if (i != m_outputPorts.end()) {
            return i->second;
        }
#ifdef DEBUG_ALSA
        else {
            cerr << "WARNING: AlsaDriver::getOutputPortForMappedInstrument: couldn't find output port for device for instrument " << id << ", falling through" << endl;
        }
#endif

    }

    return -1;
}

// Send a direct controller to the specified port/client
//
void
AlsaDriver::sendDeviceController(DeviceId device,
                                 MidiByte controller,
                                 MidiByte value)
{
    snd_seq_event_t event;

    snd_seq_ev_clear(&event);

    snd_seq_ev_set_subs(&event);

    DeviceIntMap::iterator dimi = m_outputPorts.find(device);
    if (dimi == m_outputPorts.end())
        return ;

    snd_seq_ev_set_source(&event, dimi->second);
    snd_seq_ev_set_direct(&event);

    for (int i = 0; i < 16; i++) {
        snd_seq_ev_set_controller(&event,
                                  i,
                                  controller,
                                  value);
        snd_seq_event_output_direct(m_midiHandle, &event);
    }

    // we probably don't need this:
    checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendDeviceController(): draining");
}

void
AlsaDriver::processPending()
{
    if (!m_playing) {
        processNotesOff(getAlsaTime(), true);
        checkAlsaError(snd_seq_drain_output(m_midiHandle), "processPending(): draining");
    }

#ifdef HAVE_LIBJACK
    if (m_jackDriver) {
        m_jackDriver->updateAudioData();
    }
#endif

    scavengePlugins();
    m_audioQueueScavenger.scavenge();
}

void
AlsaDriver::insertMappedEventForReturn(MappedEvent *mE)
{
    // Insert the event ready for return at the next opportunity.
    //
    m_returnComposition.insert(mE);
}

// check for recording status on any ALSA Port
//
bool
AlsaDriver::isRecording(AlsaPortDescription *port)
{
    if (port->isReadable()) {

        snd_seq_query_subscribe_t *qSubs;
        snd_seq_addr_t rg_addr, sender_addr;
        snd_seq_query_subscribe_alloca(&qSubs);

        rg_addr.client = m_client;
        rg_addr.port = m_inputPort;

        snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
        snd_seq_query_subscribe_set_index(qSubs, 0);
        snd_seq_query_subscribe_set_root(qSubs, &rg_addr);

        while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
            sender_addr = *snd_seq_query_subscribe_get_addr(qSubs);
            if (sender_addr.client == port->m_client &&
                    sender_addr.port == port->m_port)
                return true;

            snd_seq_query_subscribe_set_index(qSubs,
                                              snd_seq_query_subscribe_get_index(qSubs) + 1);
        }
    }
    return false;
}

bool
AlsaDriver::checkForNewClients()
{
    Audit audit;
    bool madeChange = false;

    if (!m_portCheckNeeded)
        return false;

    AlsaPortList newPorts;
    generatePortList(&newPorts); // updates m_alsaPorts, returns new ports as well

    // If any devices have connections that no longer exist,
    // clear those connections and stick them in the suspended
    // port map in case they come back online later.

    for (MappedDeviceList::iterator i = m_devices.begin();
            i != m_devices.end(); ++i) {

        ClientPortPair pair(m_devicePortMap[(*i)->getId()]);

        bool found = false;
        for (AlsaPortList::iterator j = m_alsaPorts.begin();
                j != m_alsaPorts.end(); ++j) {
            if ((*j)->m_client == pair.first &&
                    (*j)->m_port == pair.second) {
                if ((*i)->getDirection() == MidiDevice::Record) {
                    bool recState = isRecording(*j);
                    if (recState != (*i)->isRecording()) {
                        madeChange = true;
                        (*i)->setRecording(recState);
                    }
                } else {
                    (*i)->setRecording(false);
                }
                found = true;
                break;
            }
        }

        if (!found) {
            m_suspendedPortMap[pair] = (*i)->getId();
            m_devicePortMap[(*i)->getId()] = ClientPortPair( -1, -1);
            setConnectionToDevice(**i, "");
            (*i)->setRecording(false);
            madeChange = true;
        }
    }

    // If we've increased the number of connections, we need
    // to assign the new connections to existing devices that
    // have none, where possible, and create new devices for
    // any left over.

    if (newPorts.size() > 0) {

        audit << "New ports:" << std::endl;

        for (AlsaPortList::iterator i = newPorts.begin();
                i != newPorts.end(); ++i) {

            if ((*i)->m_client == m_client) {
                audit << "(Ignoring own port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
                continue;
            } else if ((*i)->m_client == 0) {
                audit << "(Ignoring system port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
                continue;
            }

            audit << (*i)->m_name << std::endl;

            QString portName = (*i)->m_name.c_str();
            ClientPortPair portPair = ClientPortPair((*i)->m_client,
                                      (*i)->m_port);

            if (m_suspendedPortMap.find(portPair) != m_suspendedPortMap.end()) {

                DeviceId id = m_suspendedPortMap[portPair];

                audit << "(Reusing suspended device " << id << ")" << std::endl;

                for (MappedDeviceList::iterator j = m_devices.begin();
                        j != m_devices.end(); ++j) {
                    if ((*j)->getId() == id) {
                        setConnectionToDevice(**j, portName);
                    }
                }

                m_suspendedPortMap.erase(m_suspendedPortMap.find(portPair));
                m_devicePortMap[id] = portPair;
                madeChange = true;
                continue;
            }

            bool needPlayDevice = true, needRecordDevice = true;

            if ((*i)->isReadable()) {
                for (MappedDeviceList::iterator j = m_devices.begin();
                        j != m_devices.end(); ++j) {
                    if ((*j)->getType() == Device::Midi &&
                            (*j)->getConnection() == "" &&
                            (*j)->getDirection() == MidiDevice::Record) {
                        audit << "(Reusing record device " << (*j)->getId()
                        << ")" << std::endl;
                        m_devicePortMap[(*j)->getId()] = portPair;
                        setConnectionToDevice(**j, portName);
                        needRecordDevice = false;
                        madeChange = true;
                        break;
                    }
                }
            } else {
                needRecordDevice = false;
            }

            if ((*i)->isWriteable()) {
                for (MappedDeviceList::iterator j = m_devices.begin();
                        j != m_devices.end(); ++j) {
                    if ((*j)->getType() == Device::Midi &&
                            (*j)->getConnection() == "" &&
                            (*j)->getDirection() == MidiDevice::Play) {
                        audit << "(Reusing play device " << (*j)->getId()
                        << ")" << std::endl;
                        m_devicePortMap[(*j)->getId()] = portPair;
                        setConnectionToDevice(**j, portName);
                        needPlayDevice = false;
                        madeChange = true;
                        break;
                    }
                }
            } else {
                needPlayDevice = false;
            }

            if (needRecordDevice) {
                MappedDevice *device = createMidiDevice(*i, MidiDevice::Record);
                if (!device) {
#ifdef DEBUG_ALSA
                    std::cerr << "WARNING: Failed to create record device" << std::endl;
#else

                    ;
#endif

                } else {
                    audit << "(Created new record device " << device->getId() << ")" << std::endl;
                    addInstrumentsForDevice(device);
                    m_devices.push_back(device);
                    madeChange = true;
                }
            }

            if (needPlayDevice) {
                MappedDevice *device = createMidiDevice(*i, MidiDevice::Play);
                if (!device) {
#ifdef DEBUG_ALSA
                    std::cerr << "WARNING: Failed to create play device" << std::endl;
#else

                    ;
#endif

                } else {
                    audit << "(Created new play device " << device->getId() << ")" << std::endl;
                    addInstrumentsForDevice(device);
                    m_devices.push_back(device);
                    madeChange = true;
                }
            }
        }
    }

    // If one of our ports is connected to a single other port and
    // it isn't the one we thought, we should update our connection

    for (MappedDeviceList::iterator i = m_devices.begin();
            i != m_devices.end(); ++i) {

        DevicePortMap::iterator j = m_devicePortMap.find((*i)->getId());

        snd_seq_addr_t addr;
        addr.client = m_client;

        DeviceIntMap::iterator ii = m_outputPorts.find((*i)->getId());
        if (ii == m_outputPorts.end())
            continue;
        addr.port = ii->second;

        snd_seq_query_subscribe_t *subs;
        snd_seq_query_subscribe_alloca(&subs);
        snd_seq_query_subscribe_set_root(subs, &addr);
        snd_seq_query_subscribe_set_index(subs, 0);

        bool haveOurs = false;
        int others = 0;
        ClientPortPair firstOther;

        while (!snd_seq_query_port_subscribers(m_midiHandle, subs)) {

            const snd_seq_addr_t *otherEnd =
                snd_seq_query_subscribe_get_addr(subs);

            if (!otherEnd)
                continue;

            if (j != m_devicePortMap.end() &&
                    otherEnd->client == j->second.first &&
                    otherEnd->port == j->second.second) {
                haveOurs = true;
            } else {
                ++others;
                firstOther = ClientPortPair(otherEnd->client, otherEnd->port);
            }

            snd_seq_query_subscribe_set_index
            (subs, snd_seq_query_subscribe_get_index(subs) + 1);
        }

        if (haveOurs) { // leave our own connection alone, and stop worrying
            continue;

        } else {
            if (others == 0) {
                if (j != m_devicePortMap.end()) {
                    j->second = ClientPortPair( -1, -1);
                    setConnectionToDevice(**i, "");
                    madeChange = true;
                }
            } else {
                for (AlsaPortList::iterator k = m_alsaPorts.begin();
                        k != m_alsaPorts.end(); ++k) {
                    if ((*k)->m_client == firstOther.first &&
                            (*k)->m_port == firstOther.second) {
                        m_devicePortMap[(*i)->getId()] = firstOther;
                        setConnectionToDevice(**i, (*k)->m_name.c_str(),
                                              firstOther);
                        madeChange = true;
                        break;
                    }
                }
            }
        }
    }

    if (madeChange) {
        MappedEvent *mE =
            new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
                            0, 0);
        // send completion event
        insertMappedEventForReturn(mE);
    }

    m_portCheckNeeded = false;

    return true;
}


// From a DeviceId get a client/port pair for connecting as the
// MIDI record device.
//
void
AlsaDriver::setRecordDevice(DeviceId id, bool connectAction)
{
    Audit audit;

    // Locate a suitable port
    //
    if (m_devicePortMap.find(id) == m_devicePortMap.end()) {
#ifdef DEBUG_ALSA
        audit << "AlsaDriver::setRecordDevice - "
        << "couldn't match device id (" << id << ") to ALSA port"
        << std::endl;
#endif

        return ;
    }

    ClientPortPair pair = m_devicePortMap[id];

    snd_seq_addr_t sender, dest;
    sender.client = pair.first;
    sender.port = pair.second;

    for (MappedDeviceList::iterator i = m_devices.begin();
            i != m_devices.end(); ++i) {
        if ((*i)->getId() == id) {
            if ((*i)->getDirection() == MidiDevice::Record) {
                if ((*i)->isRecording() && connectAction) {
#ifdef DEBUG_ALSA
                    audit << "AlsaDriver::setRecordDevice - "
                    << "attempting to subscribe (" << id
                    << ") already subscribed" << std::endl;
#endif

                    return ;
                }
                if (!(*i)->isRecording() && !connectAction) {
#ifdef DEBUG_ALSA
                    audit << "AlsaDriver::setRecordDevice - "
                    << "attempting to unsubscribe (" << id
                    << ") already unsubscribed" << std::endl;
#endif

                    return ;
                }
            } else {
#ifdef DEBUG_ALSA
                audit << "AlsaDriver::setRecordDevice - "
                << "attempting to set play device (" << id
                << ") to record device" << std::endl;
#endif

                return ;
            }
            break;
        }
    }

    snd_seq_port_subscribe_t *subs;
    snd_seq_port_subscribe_alloca(&subs);

    dest.client = m_client;
    dest.port = m_inputPort;

    // Set destinations and senders
    //
    snd_seq_port_subscribe_set_sender(subs, &sender);
    snd_seq_port_subscribe_set_dest(subs, &dest);

    // subscribe or unsubscribe the port
    //
    if (connectAction) {
        if (checkAlsaError(snd_seq_subscribe_port(m_midiHandle, subs),
                           "setRecordDevice - failed subscription of input port") < 0) {
            // Not the end of the world if this fails but we
            // have to flag it internally.
            //
            audit << "AlsaDriver::setRecordDevice - "
            << int(sender.client) << ":" << int(sender.port)
            << " failed to subscribe device "
            << id << " as record port" << std::endl;
        } else {
            m_midiInputPortConnected = true;
            audit << "AlsaDriver::setRecordDevice - "
            << "successfully subscribed device "
            << id << " as record port" << std::endl;
        }
    } else {
        if (checkAlsaError(snd_seq_unsubscribe_port(m_midiHandle, subs),
                           "setRecordDevice - failed to unsubscribe a device") == 0)
            audit << "AlsaDriver::setRecordDevice - "
            << "successfully unsubscribed device "
            << id << " as record port" << std::endl;

    }
}

// Clear any record device connections
//
void
AlsaDriver::unsetRecordDevices()
{
    snd_seq_addr_t dest;
    dest.client = m_client;
    dest.port = m_inputPort;

    snd_seq_query_subscribe_t *qSubs;
    snd_seq_addr_t tmp_addr;
    snd_seq_query_subscribe_alloca(&qSubs);

    tmp_addr.client = m_client;
    tmp_addr.port = m_inputPort;

    // Unsubsribe any existing connections
    //
    snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
    snd_seq_query_subscribe_set_index(qSubs, 0);
    snd_seq_query_subscribe_set_root(qSubs, &tmp_addr);

    while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
        tmp_addr = *snd_seq_query_subscribe_get_addr(qSubs);

        snd_seq_port_subscribe_t *dSubs;
        snd_seq_port_subscribe_alloca(&dSubs);

        snd_seq_addr_t dSender;
        dSender.client = tmp_addr.client;
        dSender.port = tmp_addr.port;

        snd_seq_port_subscribe_set_sender(dSubs, &dSender);
        snd_seq_port_subscribe_set_dest(dSubs, &dest);

        int error = snd_seq_unsubscribe_port(m_midiHandle, dSubs);

        if (error < 0) {
#ifdef DEBUG_ALSA
            std::cerr << "AlsaDriver::unsetRecordDevices - "
            << "can't unsubscribe record port" << std::endl;
#endif

        }

        snd_seq_query_subscribe_set_index(qSubs,
                                          snd_seq_query_subscribe_get_index(qSubs) + 1);
    }
}

void
AlsaDriver::sendMMC(MidiByte deviceArg,
                    MidiByte instruction,
                    bool isCommand,
                    const std::string &data)
{
    snd_seq_event_t event;

    snd_seq_ev_clear(&event);
    snd_seq_ev_set_source(&event, m_syncOutputPort);
    snd_seq_ev_set_subs(&event);

    unsigned char dataArr[10] =
        { MIDI_SYSTEM_EXCLUSIVE,
          MIDI_SYSEX_RT, deviceArg,
          (isCommand ? MIDI_SYSEX_RT_COMMAND : MIDI_SYSEX_RT_RESPONSE),
          instruction };

    std::string dataString = std::string((const char *)dataArr) +
                             data + (char)MIDI_END_OF_EXCLUSIVE;

    snd_seq_ev_set_sysex(&event, dataString.length(),
                         (char *)dataString.c_str());

    event.queue = SND_SEQ_QUEUE_DIRECT;

    checkAlsaError(snd_seq_event_output_direct(m_midiHandle, &event),
                   "sendMMC event send");

    if (m_queueRunning) {
        checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendMMC drain");
    }
}

// Send a system real-time message from the sync output port
//
void
AlsaDriver::sendSystemDirect(MidiByte command, int *args)
{
    snd_seq_event_t event;

    snd_seq_ev_clear(&event);
    snd_seq_ev_set_source(&event, m_syncOutputPort);
    snd_seq_ev_set_subs(&event);

    event.queue = SND_SEQ_QUEUE_DIRECT;

    // set the command
    event.type = command;

    // set args if we have them
    if (args) {
        event.data.control.value = *args;
    }

    int error = snd_seq_event_output_direct(m_midiHandle, &event);

    if (error < 0) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::sendSystemDirect - "
        << "can't send event (" << int(command) << ")"
        << std::endl;
#endif

    }

    //    checkAlsaError(snd_seq_drain_output(m_midiHandle),
    //		   "sendSystemDirect(): draining");
}


void
AlsaDriver::sendSystemQueued(MidiByte command,
                             const std::string &args,
                             const RealTime &time)
{
    snd_seq_event_t event;

    snd_seq_ev_clear(&event);
    snd_seq_ev_set_source(&event, m_syncOutputPort);
    snd_seq_ev_set_subs(&event);

    snd_seq_real_time_t sendTime = { time.sec, time.nsec };

    // Schedule the command
    //
    event.type = command;

    snd_seq_ev_schedule_real(&event, m_queue, 0, &sendTime);

    // set args if we have them
    switch (args.length()) {
    case 1:
        event.data.control.value = args[0];
        break;

    case 2:
        event.data.control.value = int(args[0]) | (int(args[1]) << 7);
        break;

    default:  // do nothing
        break;
    }

    int error = snd_seq_event_output(m_midiHandle, &event);

    if (error < 0) {
#ifdef DEBUG_ALSA
        std::cerr << "AlsaDriver::sendSystemQueued - "
        << "can't send event (" << int(command) << ")"
        << " - error = (" << error << ")"
        << std::endl;
#endif

    }

    //    if (m_queueRunning) {
    //	checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendSystemQueued(): draining");
    //    }
}


void
AlsaDriver::claimUnwantedPlugin(void *plugin)
{
    m_pluginScavenger.claim((RunnablePluginInstance *)plugin);
}


void
AlsaDriver::scavengePlugins()
{
    m_pluginScavenger.scavenge();
}


QString
AlsaDriver::getStatusLog()
{
    return QString::fromUtf8(Audit::getAudit().c_str());
}


void
AlsaDriver::sleep(const RealTime &rt)
{
    int npfd = snd_seq_poll_descriptors_count(m_midiHandle, POLLIN);
    struct pollfd *pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
    snd_seq_poll_descriptors(m_midiHandle, pfd, npfd, POLLIN);
    poll(pfd, npfd, rt.sec * 1000 + rt.msec());
}

void
AlsaDriver::runTasks()
{
#ifdef HAVE_LIBJACK
    if (m_jackDriver) {
        if (!m_jackDriver->isOK()) {
            m_jackDriver->restoreIfRestorable();
        }
    }

    if (m_doTimerChecks && m_timerRatioCalculated) {

        double ratio = m_timerRatio;
        m_timerRatioCalculated = false;

        snd_seq_queue_tempo_t *q_ptr;
        snd_seq_queue_tempo_alloca(&q_ptr);

        snd_seq_get_queue_tempo(m_midiHandle, m_queue, q_ptr);

        unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
#ifdef DEBUG_ALSA

        unsigned int t_base = snd_seq_queue_tempo_get_skew_base(q_ptr);
        if (!m_playing) {
            std::cerr << "Skew: " << t_skew << "/" << t_base;
        }
#endif

        unsigned int newSkew = t_skew + (unsigned int)(t_skew * ratio);

        if (newSkew != t_skew) {
#ifdef DEBUG_ALSA
            if (!m_playing) {
                std::cerr << " changed to " << newSkew << endl;
            }
#endif
            snd_seq_queue_tempo_set_skew(q_ptr, newSkew);
            snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
        } else {
#ifdef DEBUG_ALSA
            if (!m_playing) {
                std::cerr << endl;
            }
#endif

        }

        m_firstTimerCheck = true;
    }

#endif
}

void
AlsaDriver::reportFailure(MappedEvent::FailureCode code)
{
    //#define REPORT_XRUNS 1
#ifndef REPORT_XRUNS
    if (code == MappedEvent::FailureXRuns ||
            code == MappedEvent::FailureDiscUnderrun ||
            code == MappedEvent::FailureBussMixUnderrun ||
            code == MappedEvent::FailureMixUnderrun) {
        return ;
    }
#endif

    // Ignore consecutive duplicates
    if (_failureReportWriteIndex > 0 &&
            _failureReportWriteIndex != _failureReportReadIndex) {
        if (code == _failureReports[_failureReportWriteIndex - 1])
            return ;
    }

    _failureReports[_failureReportWriteIndex] = code;
    _failureReportWriteIndex =
        (_failureReportWriteIndex + 1) % FAILURE_REPORT_COUNT;
}

}


#endif // HAVE_ALSA
