diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/sound/AlsaDriver.cpp | |
download | rosegarden-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.cpp | 5476 |
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 |