summaryrefslogtreecommitdiffstats
path: root/src/sound/AlsaDriver.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
commit145364a8af6a1fec06556221e66d4b724a62fc9a (patch)
tree53bd71a544008c518034f208d64c932dc2883f50 /src/sound/AlsaDriver.cpp
downloadrosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz
rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/sound/AlsaDriver.cpp')
-rw-r--r--src/sound/AlsaDriver.cpp5476
1 files changed, 5476 insertions, 0 deletions
diff --git a/src/sound/AlsaDriver.cpp b/src/sound/AlsaDriver.cpp
new file mode 100644
index 0000000..9d512d9
--- /dev/null
+++ b/src/sound/AlsaDriver.cpp
@@ -0,0 +1,5476 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ 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 DEBUG_PROCESS_SOFT_SYNTH_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 library version ") +
+ std::string(SND_LIB_VERSION_STR) +
+ std::string(", module version ") +
+ getAlsaModuleVersionString() +
+ std::string(", kernel version ") +
+ getKernelVersionString() +
+ "]"),
+ 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
+
+ processNotesOff(getAlsaTime(), true, true);
+
+#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.
+
+ bool pcmTimerAccepted = false;
+ wantTimerChecks = false; // for most options
+
+ bool rtcCouldBeOK = false;
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ wantTimerChecks = true;
+ pcmTimerAccepted = true;
+ }
+#endif
+
+ // look for a high frequency 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) {
+ long hz = 1000000000 / i->resolution;
+ if (hz >= 750) {
+ return i->name;
+ }
+ }
+ }
+ }
+
+ // Look for the system RTC timer if available. This has been
+ // known to hang some real-time kernels, but reports suggest that
+ // recent kernels are OK. Avoid if the kernel is older than
+ // 2.6.20 or the ALSA driver is older than 1.0.14.
+
+ if (versionIsAtLeast(getAlsaModuleVersionString(),
+ 1, 0, 14) &&
+ versionIsAtLeast(getKernelVersionString(),
+ 2, 6, 20)) {
+
+ rtcCouldBeOK = true;
+
+ 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;
+ }
+ }
+ }
+ }
+
+ // 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)
+
+ if (pcmTimerAccepted) {
+
+ 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) {
+ if (i->resolution != 0) {
+ long hz = 1000000000 / i->resolution;
+ if (hz >= 750) {
+ wantTimerChecks = false; // pointless with PCM timer
+ return i->name;
+ } else {
+ audit << "PCM timer: inadequate resolution " << i->resolution << std::endl;
+ }
+ }
+ }
+ }
+ }
+
+ // next look for slow, unpopular 100Hz (2.4) or 250Hz (2.6) 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;
+ if (rtcCouldBeOK) {
+ reportFailure(MappedEvent::WarningImpreciseTimerTryRTC);
+ } else {
+ 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 UNKNOWN = -1, 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;
+
+ int category = UNKNOWN;
+ bool noConnect = false;
+ bool isSynth = false;
+ bool synthKnown = false;
+
+ if (port->m_client < 16) {
+
+ category = SYSTEM;
+ noConnect = true;
+ isSynth = false;
+ synthKnown = true;
+
+ } else {
+
+#ifdef SND_SEQ_PORT_TYPE_HARDWARE
+ if (port->m_portType & SND_SEQ_PORT_TYPE_HARDWARE) {
+ category = HARDWARE;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_SOFTWARE
+ if (port->m_portType & SND_SEQ_PORT_TYPE_SOFTWARE) {
+ category = SOFTWARE;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_SYNTHESIZER
+ if (port->m_portType & SND_SEQ_PORT_TYPE_SYNTHESIZER) {
+ isSynth = true;
+ synthKnown = true;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_APPLICATION
+ if (port->m_portType & SND_SEQ_PORT_TYPE_APPLICATION) {
+ category = SOFTWARE;
+ isSynth = false;
+ synthKnown = true;
+ }
+#endif
+
+ if (category == UNKNOWN) {
+
+ if (port->m_client < 64) {
+
+ if (versionIsAtLeast(getAlsaModuleVersionString(),
+ 1, 0, 11)) {
+
+ category = HARDWARE;
+
+ } else {
+
+ category = SYSTEM;
+ noConnect = true;
+ }
+
+ } else if (port->m_client < 128) {
+
+ category = HARDWARE;
+
+ } else {
+
+ category = SOFTWARE;
+ }
+ }
+ }
+
+ bool haveName = false;
+
+ if (!synthKnown) {
+
+ if (category != SYSTEM && reqDirection == MidiDevice::Play) {
+
+ // 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));
+
+ 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;
+
+ } else {
+ 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 { // !port
+
+ 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(" << oldPosition << "," << 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));
+ }
+
+ RealTime formerStartPosition = m_playStartPosition;
+
+ m_playStartPosition = position;
+ m_alsaPlayStartTime = getAlsaTime();
+
+ // Reset note offs to correct positions
+ //
+ RealTime jump = position - oldPosition;
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Currently " << m_noteOffQueue.size() << " in note off queue" << std::endl;
+#endif
+
+ // 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) {
+
+ RealTime endTime = formerStartPosition + (*i)->getRealTime();
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Forward jump of " << jump << ": adjusting note off from "
+ << (*i)->getRealTime() << " (absolute " << endTime
+ << ") to ";
+#endif
+ (*i)->setRealTime(endTime - position);
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << (*i)->getRealTime() << std::endl;
+#endif
+ } else // we're rewinding - kill the note immediately
+ {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Rewind by " << jump << ": setting note off to zero" << std::endl;
+#endif
+ (*i)->setRealTime(RealTime::zeroTime);
+ }
+ }
+
+ pushRecentNoteOffs();
+ processNotesOff(getAlsaTime(), true);
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "resetPlayback(): draining");
+
+ // 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_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_jackDriver->clearSynthPluginEvents();
+ 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::pushRecentNoteOffs()
+{
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::pushRecentNoteOffs: have " << m_recentNoteOffs.size() << " in queue" << std::endl;
+#endif
+
+ for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
+ i != m_recentNoteOffs.end(); ++i) {
+ (*i)->setRealTime(RealTime::zeroTime);
+ m_noteOffQueue.insert(*i);
+ }
+
+ m_recentNoteOffs.clear();
+}
+
+void
+AlsaDriver::cropRecentNoteOffs(const RealTime &t)
+{
+ while (!m_recentNoteOffs.empty()) {
+ NoteOffEvent *ev = *m_recentNoteOffs.begin();
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::cropRecentNoteOffs: " << ev->getRealTime() << " vs " << t << std::endl;
+#endif
+ if (ev->getRealTime() >= t) break;
+ delete ev;
+ m_recentNoteOffs.erase(m_recentNoteOffs.begin());
+ }
+}
+
+void
+AlsaDriver::weedRecentNoteOffs(unsigned int pitch, MidiByte channel,
+ InstrumentId instrument)
+{
+ for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
+ i != m_recentNoteOffs.end(); ++i) {
+ if ((*i)->getPitch() == pitch &&
+ (*i)->getChannel() == channel &&
+ (*i)->getInstrument() == instrument) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::weedRecentNoteOffs: deleting one" << std::endl;
+#endif
+ delete *i;
+ m_recentNoteOffs.erase(i);
+ break;
+ }
+ }
+}
+
+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, bool everything)
+{
+ if (m_noteOffQueue.empty()) {
+ return;
+ }
+
+ snd_seq_event_t event;
+
+ ClientPortPair outputDevice;
+ RealTime offTime;
+
+ // prepare the event
+ snd_seq_ev_clear(&event);
+
+ RealTime alsaTime = getAlsaTime();
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff(" << time << "): alsaTime = " << alsaTime << ", now = " << now << std::endl;
+#endif
+
+ while (m_noteOffQueue.begin() != m_noteOffQueue.end()) {
+
+ NoteOffEvent *ev = *m_noteOffQueue.begin();
+
+ if (ev->getRealTime() > time) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Note off time " << ev->getRealTime() << " is beyond current time " << time << std::endl;
+#endif
+ if (!everything) break;
+ }
+
+#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();
+ if (offTime < RealTime::zeroTime) offTime = RealTime::zeroTime;
+ bool scheduled = (offTime > alsaTime) && !now;
+ if (!scheduled) offTime = RealTime::zeroTime;
+
+ snd_seq_real_time_t alsaOffTime = { offTime.sec,
+ offTime.nsec };
+
+ snd_seq_ev_set_noteoff(&event,
+ ev->getChannel(),
+ ev->getPitch(),
+ 127);
+
+ if (!isSoftSynth) {
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to instrument
+ //
+ int src = getOutputPortForMappedInstrument(ev->getInstrument());
+ if (src < 0) {
+ std::cerr << "note off has no output port (instr = " << ev->getInstrument() << ")" << std::endl;
+ delete ev;
+ m_noteOffQueue.erase(m_noteOffQueue.begin());
+ continue;
+ }
+
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_set_subs(&event);
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &alsaOffTime);
+
+ if (scheduled) {
+ snd_seq_event_output(m_midiHandle, &event);
+ } else {
+ snd_seq_event_output_direct(m_midiHandle, &event);
+ }
+
+ } else {
+
+ event.time.time = alsaOffTime;
+
+ processSoftSynthEventOut(ev->getInstrument(), &event, now);
+ }
+
+ if (!now) {
+ m_recentNoteOffs.insert(ev);
+ } else {
+ 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;
+ }
+
+ 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;
+
+#ifdef DEBUG_ALSA
+ if (!fromController) {
+ std::cerr << "Received normal event: type " << int(event->type) << ", chan " << channel << ", note " << int(event->data.note.note) << ", time " << eventTime << std::endl;
+ }
+#endif
+
+ 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();
+
+#ifdef DEBUG_ALSA
+ std::cerr << "NOTE OFF: found NOTE ON at " << mE->getEventTime() << std::endl;
+#endif
+
+ if (duration < RealTime::zeroTime) {
+ duration = RealTime::zeroTime;
+ mE->setEventTime(eventTime);
+ }
+
+ // 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);
+
+ if (!now) {
+ // This 0.5 sec is arbitrary, but it must be larger than the
+ // sequencer's read-ahead
+ RealTime diff = RealTime::fromSeconds(0.5);
+ RealTime cutoff = sliceStart - diff;
+ cropRecentNoteOffs(cutoff - m_playStartPosition + m_alsaPlayStartTime);
+ }
+
+ // 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);
+ }
+
+ weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
+ }
+ 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);
+ }
+
+ weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
+ } 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_PROCESS_SOFT_SYNTH_OUT
+ 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_PROCESS_SOFT_SYNTH_OUT
+
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: event time " << t << std::endl;
+#endif
+
+ synthPlugin->sendEvent(t, ev);
+
+ if (now) {
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+ 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;
+}
+
+std::string
+AlsaDriver::getAlsaModuleVersionString()
+{
+ FILE *v = fopen("/proc/asound/version", "r");
+
+ // Examples:
+ // Advanced Linux Sound Architecture Driver Version 1.0.14rc3.
+ // Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2008 UTC).
+
+ if (v) {
+ char buf[256];
+ fgets(buf, 256, v);
+ fclose(v);
+
+ std::string vs(buf);
+ std::string::size_type sp = vs.find_first_of('.');
+ if (sp > 0 && sp != std::string::npos) {
+ while (sp > 0 && isdigit(vs[sp-1])) --sp;
+ vs = vs.substr(sp);
+ if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ if (vs.length() > 0 && vs[vs.length()-1] == '.') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ return vs;
+ }
+ }
+
+ return "(unknown)";
+}
+
+std::string
+AlsaDriver::getKernelVersionString()
+{
+ FILE *v = fopen("/proc/version", "r");
+
+ if (v) {
+ char buf[256];
+ fgets(buf, 256, v);
+ fclose(v);
+
+ std::string vs(buf);
+ std::string key(" version ");
+ std::string::size_type sp = vs.find(key);
+ if (sp != std::string::npos) {
+ vs = vs.substr(sp + key.length());
+ sp = vs.find(' ');
+ if (sp != std::string::npos) {
+ vs = vs.substr(0, sp);
+ }
+ if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ return vs;
+ }
+ }
+
+ return "(unknown)";
+}
+
+void
+AlsaDriver::extractVersion(std::string v, int &major, int &minor, int &subminor, std::string &suffix)
+{
+ major = minor = subminor = 0;
+ suffix = "";
+ if (v == "(unknown)") return;
+
+ std::string::size_type sp, pp;
+
+ sp = v.find('.');
+ if (sp == std::string::npos) goto done;
+ major = atoi(v.substr(0, sp).c_str());
+ pp = sp + 1;
+
+ sp = v.find('.', pp);
+ if (sp == std::string::npos) goto done;
+ minor = atoi(v.substr(pp, sp - pp).c_str());
+ pp = sp + 1;
+
+ while (++sp < v.length() && (::isdigit(v[sp]) || v[sp] == '-'));
+ subminor = atoi(v.substr(pp, sp - pp).c_str());
+
+ if (sp >= v.length()) goto done;
+ suffix = v.substr(sp);
+
+done:
+ std::cerr << "extractVersion: major = " << major << ", minor = " << minor << ", subminor = " << subminor << ", suffix = \"" << suffix << "\"" << std::endl;
+}
+
+bool
+AlsaDriver::versionIsAtLeast(std::string v, int major, int minor, int subminor)
+{
+ int actualMajor, actualMinor, actualSubminor;
+ std::string actualSuffix;
+
+ extractVersion(v, actualMajor, actualMinor, actualSubminor, actualSuffix);
+
+ bool ok = false;
+
+ if (actualMajor > major) {
+ ok = true;
+ } else if (actualMajor == major) {
+ if (actualMinor > minor) {
+ ok = true;
+ } else if (actualMinor == minor) {
+ if (actualSubminor > subminor) {
+ ok = true;
+ } else if (actualSubminor == subminor) {
+ if (strncmp(actualSuffix.c_str(), "rc", 2) &&
+ strncmp(actualSuffix.c_str(), "pre", 3)) {
+ ok = true;
+ }
+ }
+ }
+ }
+
+ std::cerr << "AlsaDriver::versionIsAtLeast: is version " << v << " at least " << major << "." << minor << "." << subminor << "? " << (ok ? "yes" : "no") << std::endl;
+ return ok;
+}
+
+}
+
+
+#endif // HAVE_ALSA