/* Rosegarden A sequencer and musical notation editor. This program is Copyright 2000-2008 Guillaume Laurent , Chris Cannam , Richard Bown 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 #include "misc/Debug.h" #include #include #include #ifdef HAVE_ALSA // ALSA #include #include #include #include #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 #include //#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::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::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::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::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) { TQString portName; if (TQString(deviceName).startsWith("Anonymous MIDI device ")) { portName = TQString("out %1") .arg(m_outputPorts.size() + 1); } else { portName = TQString("out %1 - %2") .arg(m_outputPorts.size() + 1) .arg(deviceName); } int outputPort = checkAlsaError(snd_seq_create_simple_port (m_midiHandle, portName.ascii(), 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.ascii() << " 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 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, TQString 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); TQString oldName = snd_seq_port_info_get_name(pinfo); int sep = oldName.find(" - "); TQString 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.ascii()); 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.ascii()); break; } } std::cerr << "Renamed " << m_client << ":" << i->second << " to " << name.ascii() << 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; } TQString 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, TQString connection) { ClientPortPair pair( -1, -1); if (!connection.isNull() && connection != "") { pair = getPortByName(connection.ascii()); } setConnectionToDevice(device, connection, pair); } void AlsaDriver::setConnectionToDevice(MappedDevice &device, TQString connection, const ClientPortPair &pair) { TQString prevConnection = device.getConnection().c_str(); device.setConnection(connection.ascii()); if (device.getDirection() == MidiDevice::Play) { DeviceIntMap::iterator j = m_outputPorts.find(device.getId()); if (j != m_outputPorts.end()) { if (prevConnection != "") { ClientPortPair prevPair = getPortByName(prevConnection.ascii()); 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.ascii()) { 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, TQString connection) { Audit audit; ClientPortPair port(getPortByName(connection.ascii())); 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, TQString idealConnection) { Audit audit; ClientPortPair port(getPortByName(idealConnection.ascii())); audit << "AlsaDriver::setPlausibleConnection: connection like " << idealConnection.ascii() << " 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) { TQString 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(TQRegExp("[^\\w ]"), firstSpace); TQString 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 != "" && !TQString(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 } TQString AlsaDriver::getTimer(unsigned int n) { if (n == 0) return AUTO_TIMER_NAME; else return m_timers[n -1].name.c_str(); } TQString AlsaDriver::getCurrentTimer() { return m_currentTimer.c_str(); } void AlsaDriver::setCurrentTimer(TQString timer) { Audit audit; if (timer == getCurrentTimer()) return ; std::cerr << "AlsaDriver::setCurrentTimer(" << timer.ascii() << ")" << std::endl; std::string name(timer.ascii()); 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, ""); // 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, (unsigned char)(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 = { (unsigned int)offTime.sec, (unsigned int)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) { handleMTCTQFrame(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::handleMTCTQFrame(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 = { (unsigned int)time.sec, (unsigned int)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::insertMTCTQFrames(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::insertMTCTQFrames(" << 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 = { (unsigned int)scheduleTime.sec, (unsigned int)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), "insertMTCTQFrames 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 = { (unsigned int)outputTime.sec, (unsigned int)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) { insertMTCTQFrames(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 (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 ports; std::vector::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 *armedInstruments, const std::vector *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; } TQString 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.ascii())) { 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; TQString 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 = { (unsigned int)time.sec, (unsigned int)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(); } TQString AlsaDriver::getStatusLog() { return TQString::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