From 145364a8af6a1fec06556221e66d4b724a62fc9a Mon Sep 17 00:00:00 2001 From: tpearson Date: Mon, 1 Mar 2010 18:37:05 +0000 Subject: Added old abandoned KDE3 version of the RoseGarden MIDI tool git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- src/sound/AlsaDriver.cpp | 5476 ++++++++++++++++++++++++++++++ src/sound/AlsaDriver.h | 561 +++ src/sound/AlsaPort.cpp | 192 ++ src/sound/AlsaPort.h | 86 + src/sound/AudioCache.cpp | 139 + src/sound/AudioCache.h | 98 + src/sound/AudioFile.cpp | 75 + src/sound/AudioFile.h | 216 ++ src/sound/AudioFileManager.cpp | 1257 +++++++ src/sound/AudioFileManager.h | 327 ++ src/sound/AudioFileTimeStretcher.cpp | 268 ++ src/sound/AudioFileTimeStretcher.h | 76 + src/sound/AudioPlayQueue.cpp | 501 +++ src/sound/AudioPlayQueue.h | 168 + src/sound/AudioProcess.cpp | 2463 ++++++++++++++ src/sound/AudioProcess.h | 390 +++ src/sound/AudioTimeStretcher.cpp | 667 ++++ src/sound/AudioTimeStretcher.h | 221 ++ src/sound/Audit.cpp | 30 + src/sound/Audit.h | 60 + src/sound/BWFAudioFile.cpp | 171 + src/sound/BWFAudioFile.h | 94 + src/sound/ControlBlock.cpp | 181 + src/sound/ControlBlock.h | 128 + src/sound/DSSIPluginFactory.cpp | 396 +++ src/sound/DSSIPluginFactory.h | 72 + src/sound/DSSIPluginInstance.cpp | 1208 +++++++ src/sound/DSSIPluginInstance.h | 193 ++ src/sound/DummyDriver.h | 166 + src/sound/ExternalTransport.h | 67 + src/sound/JackDriver.cpp | 2480 ++++++++++++++ src/sound/JackDriver.h | 297 ++ src/sound/LADSPAPluginFactory.cpp | 841 +++++ src/sound/LADSPAPluginFactory.h | 104 + src/sound/LADSPAPluginInstance.cpp | 435 +++ src/sound/LADSPAPluginInstance.h | 137 + src/sound/MP3AudioFile.cpp | 329 ++ src/sound/MP3AudioFile.h | 128 + src/sound/MappedCommon.h | 68 + src/sound/MappedComposition.cpp | 216 ++ src/sound/MappedComposition.h | 93 + src/sound/MappedDevice.cpp | 250 ++ src/sound/MappedDevice.h | 103 + src/sound/MappedEvent.cpp | 593 ++++ src/sound/MappedEvent.h | 546 +++ src/sound/MappedInstrument.cpp | 153 + src/sound/MappedInstrument.h | 106 + src/sound/MappedRealTime.cpp | 62 + src/sound/MappedRealTime.h | 56 + src/sound/MappedStudio.cpp | 1719 ++++++++++ src/sound/MappedStudio.h | 552 +++ src/sound/Midi.h | 184 + src/sound/MidiEvent.cpp | 289 ++ src/sound/MidiEvent.h | 141 + src/sound/MidiFile.cpp | 2261 ++++++++++++ src/sound/MidiFile.h | 173 + src/sound/MidiMapping.xml | 133 + src/sound/PeakFile.cpp | 1033 ++++++ src/sound/PeakFile.h | 196 ++ src/sound/PeakFileManager.cpp | 327 ++ src/sound/PeakFileManager.h | 162 + src/sound/PlayableAudioFile.cpp | 1086 ++++++ src/sound/PlayableAudioFile.h | 219 ++ src/sound/PluginFactory.cpp | 120 + src/sound/PluginFactory.h | 97 + src/sound/PluginIdentifier.cpp | 72 + src/sound/PluginIdentifier.h | 50 + src/sound/RIFFAudioFile.cpp | 686 ++++ src/sound/RIFFAudioFile.h | 168 + src/sound/RecordableAudioFile.cpp | 164 + src/sound/RecordableAudioFile.h | 68 + src/sound/RingBuffer.h | 572 ++++ src/sound/RosegardenMidiRecord.mcopclass | 5 + src/sound/RunnablePluginInstance.cpp | 42 + src/sound/RunnablePluginInstance.h | 114 + src/sound/SF2PatchExtractor.cpp | 217 ++ src/sound/SF2PatchExtractor.h | 58 + src/sound/SampleWindow.h | 192 ++ src/sound/Scavenger.h | 211 ++ src/sound/SequencerDataBlock.cpp | 361 ++ src/sound/SequencerDataBlock.h | 140 + src/sound/SoundDriver.cpp | 391 +++ src/sound/SoundDriver.h | 529 +++ src/sound/SoundDriverFactory.cpp | 66 + src/sound/SoundDriverFactory.h | 37 + src/sound/SoundFile.cpp | 295 ++ src/sound/SoundFile.h | 155 + src/sound/WAVAudioFile.cpp | 255 ++ src/sound/WAVAudioFile.h | 93 + 89 files changed, 36577 insertions(+) create mode 100644 src/sound/AlsaDriver.cpp create mode 100644 src/sound/AlsaDriver.h create mode 100644 src/sound/AlsaPort.cpp create mode 100644 src/sound/AlsaPort.h create mode 100644 src/sound/AudioCache.cpp create mode 100644 src/sound/AudioCache.h create mode 100644 src/sound/AudioFile.cpp create mode 100644 src/sound/AudioFile.h create mode 100644 src/sound/AudioFileManager.cpp create mode 100644 src/sound/AudioFileManager.h create mode 100644 src/sound/AudioFileTimeStretcher.cpp create mode 100644 src/sound/AudioFileTimeStretcher.h create mode 100644 src/sound/AudioPlayQueue.cpp create mode 100644 src/sound/AudioPlayQueue.h create mode 100644 src/sound/AudioProcess.cpp create mode 100644 src/sound/AudioProcess.h create mode 100644 src/sound/AudioTimeStretcher.cpp create mode 100644 src/sound/AudioTimeStretcher.h create mode 100644 src/sound/Audit.cpp create mode 100644 src/sound/Audit.h create mode 100644 src/sound/BWFAudioFile.cpp create mode 100644 src/sound/BWFAudioFile.h create mode 100644 src/sound/ControlBlock.cpp create mode 100644 src/sound/ControlBlock.h create mode 100644 src/sound/DSSIPluginFactory.cpp create mode 100644 src/sound/DSSIPluginFactory.h create mode 100644 src/sound/DSSIPluginInstance.cpp create mode 100644 src/sound/DSSIPluginInstance.h create mode 100644 src/sound/DummyDriver.h create mode 100644 src/sound/ExternalTransport.h create mode 100644 src/sound/JackDriver.cpp create mode 100644 src/sound/JackDriver.h create mode 100644 src/sound/LADSPAPluginFactory.cpp create mode 100644 src/sound/LADSPAPluginFactory.h create mode 100644 src/sound/LADSPAPluginInstance.cpp create mode 100644 src/sound/LADSPAPluginInstance.h create mode 100644 src/sound/MP3AudioFile.cpp create mode 100644 src/sound/MP3AudioFile.h create mode 100644 src/sound/MappedCommon.h create mode 100644 src/sound/MappedComposition.cpp create mode 100644 src/sound/MappedComposition.h create mode 100644 src/sound/MappedDevice.cpp create mode 100644 src/sound/MappedDevice.h create mode 100644 src/sound/MappedEvent.cpp create mode 100644 src/sound/MappedEvent.h create mode 100644 src/sound/MappedInstrument.cpp create mode 100644 src/sound/MappedInstrument.h create mode 100644 src/sound/MappedRealTime.cpp create mode 100644 src/sound/MappedRealTime.h create mode 100644 src/sound/MappedStudio.cpp create mode 100644 src/sound/MappedStudio.h create mode 100644 src/sound/Midi.h create mode 100644 src/sound/MidiEvent.cpp create mode 100644 src/sound/MidiEvent.h create mode 100644 src/sound/MidiFile.cpp create mode 100644 src/sound/MidiFile.h create mode 100644 src/sound/MidiMapping.xml create mode 100644 src/sound/PeakFile.cpp create mode 100644 src/sound/PeakFile.h create mode 100644 src/sound/PeakFileManager.cpp create mode 100644 src/sound/PeakFileManager.h create mode 100644 src/sound/PlayableAudioFile.cpp create mode 100644 src/sound/PlayableAudioFile.h create mode 100644 src/sound/PluginFactory.cpp create mode 100644 src/sound/PluginFactory.h create mode 100644 src/sound/PluginIdentifier.cpp create mode 100644 src/sound/PluginIdentifier.h create mode 100644 src/sound/RIFFAudioFile.cpp create mode 100644 src/sound/RIFFAudioFile.h create mode 100644 src/sound/RecordableAudioFile.cpp create mode 100644 src/sound/RecordableAudioFile.h create mode 100644 src/sound/RingBuffer.h create mode 100644 src/sound/RosegardenMidiRecord.mcopclass create mode 100644 src/sound/RunnablePluginInstance.cpp create mode 100644 src/sound/RunnablePluginInstance.h create mode 100644 src/sound/SF2PatchExtractor.cpp create mode 100644 src/sound/SF2PatchExtractor.h create mode 100644 src/sound/SampleWindow.h create mode 100644 src/sound/Scavenger.h create mode 100644 src/sound/SequencerDataBlock.cpp create mode 100644 src/sound/SequencerDataBlock.h create mode 100644 src/sound/SoundDriver.cpp create mode 100644 src/sound/SoundDriver.h create mode 100644 src/sound/SoundDriverFactory.cpp create mode 100644 src/sound/SoundDriverFactory.h create mode 100644 src/sound/SoundFile.cpp create mode 100644 src/sound/SoundFile.h create mode 100644 src/sound/WAVAudioFile.cpp create mode 100644 src/sound/WAVAudioFile.h (limited to 'src/sound') diff --git a/src/sound/AlsaDriver.cpp b/src/sound/AlsaDriver.cpp new file mode 100644 index 0000000..9d512d9 --- /dev/null +++ b/src/sound/AlsaDriver.cpp @@ -0,0 +1,5476 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + 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) { + + QString portName; + + if (QString(deviceName).startsWith("Anonymous MIDI device ")) { + portName = QString("out %1") + .arg(m_outputPorts.size() + 1); + } else { + portName = QString("out %1 - %2") + .arg(m_outputPorts.size() + 1) + .arg(deviceName); + } + + int outputPort = checkAlsaError(snd_seq_create_simple_port + (m_midiHandle, + portName, + SND_SEQ_PORT_CAP_READ | + SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_APPLICATION), + "createMidiDevice - can't create output port"); + + if (outputPort >= 0) { + + std::cerr << "CREATED OUTPUT PORT " << outputPort << ":" << portName << " for device " << deviceId << std::endl; + + m_outputPorts[deviceId] = outputPort; + + if (port) { + if (connectionName != "") { + std::cerr << "Connecting my port " << outputPort << " to " << port->m_client << ":" << port->m_port << " on initialisation" << std::endl; + snd_seq_connect_to(m_midiHandle, + outputPort, + port->m_client, + port->m_port); + if (m_midiSyncAutoConnect) { + snd_seq_connect_to(m_midiHandle, + m_syncOutputPort, + port->m_client, + port->m_port); + } + } + std::cerr << "done" << std::endl; + } + } + } + + MappedDevice *device = new MappedDevice(deviceId, + Device::Midi, + deviceName, + connectionName); + device->setDirection(reqDirection); + return device; +} + +DeviceId +AlsaDriver::getSpareDeviceId() +{ + std::set + ids; + for (unsigned int i = 0; i < m_devices.size(); ++i) { + ids.insert(m_devices[i]->getId()); + } + + DeviceId id = 0; + while (ids.find(id) != ids.end()) + ++id; + return id; +} + +void +AlsaDriver::addInstrumentsForDevice(MappedDevice *device) +{ + std::string channelName; + char number[100]; + + for (int channel = 0; channel < 16; ++channel) { + // Create MappedInstrument for export to GUI + // + // name is just number, derive rest from device at gui + sprintf(number, "#%d", channel + 1); + channelName = std::string(number); + + if (channel == 9) + channelName = std::string("#10[D]"); + MappedInstrument *instr = new MappedInstrument(Instrument::Midi, + channel, + m_midiRunningId++, + channelName, + device->getId()); + m_instruments.push_back(instr); + } +} + + +bool +AlsaDriver::canReconnect(Device::DeviceType type) +{ + return (type == Device::Midi); +} + +DeviceId +AlsaDriver::addDevice(Device::DeviceType type, + MidiDevice::DeviceDirection direction) +{ + if (type == Device::Midi) { + + MappedDevice *device = createMidiDevice(0, direction); + if (!device) { +#ifdef DEBUG_ALSA + std::cerr << "WARNING: Device creation failed" << std::endl; +#else + + ; +#endif + + } else { + addInstrumentsForDevice(device); + m_devices.push_back(device); + + MappedEvent *mE = + new MappedEvent(0, MappedEvent::SystemUpdateInstruments, + 0, 0); + insertMappedEventForReturn(mE); + + return device->getId(); + } + } + + return Device::NO_DEVICE; +} + +void +AlsaDriver::removeDevice(DeviceId id) +{ + DeviceIntMap::iterator i1 = m_outputPorts.find(id); + if (i1 == m_outputPorts.end()) { + std::cerr << "WARNING: AlsaDriver::removeDevice: Cannot find device " + << id << " in port map" << std::endl; + return ; + } + checkAlsaError( snd_seq_delete_port(m_midiHandle, i1->second), + "removeDevice"); + m_outputPorts.erase(i1); + + for (MappedDeviceList::iterator i = m_devices.end(); + i != m_devices.begin(); ) { + + --i; + + if ((*i)->getId() == id) { + delete *i; + m_devices.erase(i); + } + } + + for (MappedInstrumentList::iterator i = m_instruments.end(); + i != m_instruments.begin(); ) { + + --i; + + if ((*i)->getDevice() == id) { + delete *i; + m_instruments.erase(i); + } + } + + MappedEvent *mE = + new MappedEvent(0, MappedEvent::SystemUpdateInstruments, + 0, 0); + insertMappedEventForReturn(mE); +} + +void +AlsaDriver::renameDevice(DeviceId id, QString name) +{ + DeviceIntMap::iterator i = m_outputPorts.find(id); + if (i == m_outputPorts.end()) { + std::cerr << "WARNING: AlsaDriver::renameDevice: Cannot find device " + << id << " in port map" << std::endl; + return ; + } + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + snd_seq_get_port_info(m_midiHandle, i->second, pinfo); + + QString oldName = snd_seq_port_info_get_name(pinfo); + int sep = oldName.find(" - "); + + QString newName; + + if (name.startsWith("Anonymous MIDI device ")) { + if (sep < 0) + sep = 0; + newName = oldName.left(sep); + } else if (sep < 0) { + newName = oldName + " - " + name; + } else { + newName = oldName.left(sep + 3) + name; + } + + snd_seq_port_info_set_name(pinfo, newName.data()); + checkAlsaError(snd_seq_set_port_info(m_midiHandle, i->second, pinfo), + "renameDevice"); + + for (unsigned int i = 0; i < m_devices.size(); ++i) { + if (m_devices[i]->getId() == id) { + m_devices[i]->setName(newName.data()); + break; + } + } + + std::cerr << "Renamed " << m_client << ":" << i->second << " to " << name << std::endl; +} + +ClientPortPair +AlsaDriver::getPortByName(std::string name) +{ + for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) { + if (m_alsaPorts[i]->m_name == name) { + return ClientPortPair(m_alsaPorts[i]->m_client, + m_alsaPorts[i]->m_port); + } + } + return ClientPortPair( -1, -1); +} + +std::string +AlsaDriver::getPortName(ClientPortPair port) +{ + for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) { + if (m_alsaPorts[i]->m_client == port.first && + m_alsaPorts[i]->m_port == port.second) { + return m_alsaPorts[i]->m_name; + } + } + return ""; +} + + +unsigned int +AlsaDriver::getConnections(Device::DeviceType type, + MidiDevice::DeviceDirection direction) +{ + if (type != Device::Midi) + return 0; + + int count = 0; + for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) { + if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) || + (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) { + ++count; + } + } + + return count; +} + +QString +AlsaDriver::getConnection(Device::DeviceType type, + MidiDevice::DeviceDirection direction, + unsigned int connectionNo) +{ + if (type != Device::Midi) + return ""; + + AlsaPortList tempList; + for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) { + if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) || + (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) { + tempList.push_back(m_alsaPorts[j]); + } + } + + if (connectionNo < tempList.size()) { + return tempList[connectionNo]->m_name.c_str(); + } + + return ""; +} + +void +AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection) +{ + ClientPortPair pair( -1, -1); + if (connection && connection != "") { + pair = getPortByName(connection.data()); + } + setConnectionToDevice(device, connection, pair); +} + +void +AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection, + const ClientPortPair &pair) +{ + QString prevConnection = device.getConnection().c_str(); + device.setConnection(connection.data()); + + if (device.getDirection() == MidiDevice::Play) { + + DeviceIntMap::iterator j = m_outputPorts.find(device.getId()); + + if (j != m_outputPorts.end()) { + + if (prevConnection != "") { + ClientPortPair prevPair = getPortByName(prevConnection.data()); + if (prevPair.first >= 0 && prevPair.second >= 0) { + + std::cerr << "Disconnecting my port " << j->second << " from " << prevPair.first << ":" << prevPair.second << " on reconnection" << std::endl; + snd_seq_disconnect_to(m_midiHandle, + j->second, + prevPair.first, + prevPair.second); + + if (m_midiSyncAutoConnect) { + bool foundElsewhere = false; + for (MappedDeviceList::iterator k = m_devices.begin(); + k != m_devices.end(); ++k) { + if ((*k)->getId() != device.getId()) { + if ((*k)->getConnection() == prevConnection.data()) { + foundElsewhere = true; + break; + } + } + } + if (!foundElsewhere) { + snd_seq_disconnect_to(m_midiHandle, + m_syncOutputPort, + pair.first, + pair.second); + } + } + } + } + + if (pair.first >= 0 && pair.second >= 0) { + std::cerr << "Connecting my port " << j->second << " to " << pair.first << ":" << pair.second << " on reconnection" << std::endl; + snd_seq_connect_to(m_midiHandle, + j->second, + pair.first, + pair.second); + if (m_midiSyncAutoConnect) { + snd_seq_connect_to(m_midiHandle, + m_syncOutputPort, + pair.first, + pair.second); + } + } + } + } +} + +void +AlsaDriver::setConnection(DeviceId id, QString connection) +{ + Audit audit; + ClientPortPair port(getPortByName(connection.data())); + + if (port.first != -1 && port.second != -1) { + + m_devicePortMap[id] = port; + + for (unsigned int i = 0; i < m_devices.size(); ++i) { + + if (m_devices[i]->getId() == id) { + setConnectionToDevice(*m_devices[i], connection, port); + + MappedEvent *mE = + new MappedEvent(0, MappedEvent::SystemUpdateInstruments, + 0, 0); + insertMappedEventForReturn(mE); + + break; + } + } + } +} + +void +AlsaDriver::setPlausibleConnection(DeviceId id, QString idealConnection) +{ + Audit audit; + ClientPortPair port(getPortByName(idealConnection.data())); + + audit << "AlsaDriver::setPlausibleConnection: connection like " + << idealConnection << " requested for device " << id << std::endl; + + if (port.first != -1 && port.second != -1) { + + m_devicePortMap[id] = port; + + for (unsigned int i = 0; i < m_devices.size(); ++i) { + + if (m_devices[i]->getId() == id) { + setConnectionToDevice(*m_devices[i], idealConnection, port); + break; + } + } + + audit << "AlsaDriver::setPlausibleConnection: exact match available" + << std::endl; + return ; + } + + // What we want is a connection that: + // + // * is in the right "class" (the 0-63/64-127/128+ range of client id) + // * has at least some text in common + // * is not yet in use for any device. + // + // To do this, we exploit our privileged position as part of AlsaDriver + // and use our knowledge of how connection strings are made (see + // AlsaDriver::generatePortList above) to pick out the relevant parts + // of the requested string. + + int client = -1; + int colon = idealConnection.find(":"); + if (colon >= 0) + client = idealConnection.left(colon).toInt(); + + int portNo = -1; + if (client > 0) { + QString remainder = idealConnection.mid(colon + 1); + int space = remainder.find(" "); + if (space >= 0) + portNo = remainder.left(space).toInt(); + } + + int firstSpace = idealConnection.find(" "); + int endOfText = idealConnection.find(QRegExp("[^\\w ]"), firstSpace); + + QString text; + if (endOfText < 2) { + text = idealConnection.mid(firstSpace + 1); + } else { + text = idealConnection.mid(firstSpace + 1, endOfText - firstSpace - 2); + } + + for (int testUsed = 1; testUsed >= 0; --testUsed) { + + for (int testNumbers = 1; testNumbers >= 0; --testNumbers) { + + for (int testName = 1; testName >= 0; --testName) { + + int fitness = + (testName << 3) + + (testNumbers << 2) + + (testUsed << 1) + 1; + + for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) { + + AlsaPortDescription *port = m_alsaPorts[i]; + + if (client > 0) { + + if (port->m_client / 64 != client / 64) + continue; + + if (testNumbers) { + // We always check the client class (above). + // But we also prefer to have something in + // common with client or port number, at least + // for ports that aren't used elsewhere + // already. We don't check both because the + // chances are the entire string would already + // have matched if both figures did; instead + // we check the port if it's > 0 (handy for + // e.g. matching the MIDI synth port on a + // multi-port soundcard) and the client + // otherwise. + if (portNo > 0) { + if (port->m_port != portNo) + continue; + } else { + if (port->m_client != client) + continue; + } + } + } + + if (testName && text != "" && + !QString(port->m_name.c_str()).contains(text)) + continue; + + if (testUsed) { + bool used = false; + for (DevicePortMap::iterator dpmi = m_devicePortMap.begin(); + dpmi != m_devicePortMap.end(); ++dpmi) { + if (dpmi->second.first == port->m_client && + dpmi->second.second == port->m_port) { + used = true; + break; + } + } + if (used) + continue; + } + + // OK, this one will do + + audit << "AlsaDriver::setPlausibleConnection: fuzzy match " + << port->m_name << " available with fitness " + << fitness << std::endl; + + m_devicePortMap[id] = ClientPortPair(port->m_client, port->m_port); + + for (unsigned int i = 0; i < m_devices.size(); ++i) { + + if (m_devices[i]->getId() == id) { + setConnectionToDevice(*m_devices[i], + port->m_name.c_str(), + m_devicePortMap[id]); + + // in this case we don't request a device resync, + // because this is only invoked at times such as + // file load when the GUI is well aware that the + // whole situation is in upheaval anyway + + return ; + } + } + } + } + } + } + + audit << "AlsaDriver::setPlausibleConnection: nothing suitable available" + << std::endl; +} + + +void +AlsaDriver::checkTimerSync(size_t frames) +{ + if (!m_doTimerChecks) + return ; + +#ifdef HAVE_LIBJACK + + if (!m_jackDriver || !m_queueRunning || frames == 0 || + (getMTCStatus() == TRANSPORT_SLAVE)) { + m_firstTimerCheck = true; + return ; + } + + static RealTime startAlsaTime; + static size_t startJackFrames = 0; + static size_t lastJackFrames = 0; + + size_t nowJackFrames = m_jackDriver->getFramesProcessed(); + RealTime nowAlsaTime = getAlsaTime(); + + if (m_firstTimerCheck || + (nowJackFrames <= lastJackFrames) || + (nowAlsaTime <= startAlsaTime)) { + + startAlsaTime = nowAlsaTime; + startJackFrames = nowJackFrames; + lastJackFrames = nowJackFrames; + + m_firstTimerCheck = false; + return ; + } + + RealTime jackDiff = RealTime::frame2RealTime + (nowJackFrames - startJackFrames, + m_jackDriver->getSampleRate()); + + RealTime alsaDiff = nowAlsaTime - startAlsaTime; + + if (alsaDiff > RealTime(10, 0)) { + +#ifdef DEBUG_ALSA + if (!m_playing) { + std::cout << "\nALSA:" << startAlsaTime << "\t->" << nowAlsaTime << "\nJACK: " << startJackFrames << "\t\t-> " << nowJackFrames << std::endl; + std::cout << "ALSA diff: " << alsaDiff << "\nJACK diff: " << jackDiff << std::endl; + } +#endif + + double ratio = (jackDiff - alsaDiff) / alsaDiff; + + if (fabs(ratio) > 0.1) { +#ifdef DEBUG_ALSA + if (!m_playing) { + std::cout << "Ignoring excessive ratio " << ratio + << ", hoping for a more likely result next time" + << std::endl; + } +#endif + + } else if (fabs(ratio) > 0.000001) { + +#ifdef DEBUG_ALSA + if (alsaDiff > RealTime::zeroTime && jackDiff > RealTime::zeroTime) { + if (!m_playing) { + if (jackDiff < alsaDiff) { + std::cout << "<<<< ALSA timer is faster by " << 100.0 * ((alsaDiff - jackDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl; + } else { + std::cout << ">>>> JACK timer is faster by " << 100.0 * ((jackDiff - alsaDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl; + } + } + } +#endif + + m_timerRatio = ratio; + m_timerRatioCalculated = true; + } + + m_firstTimerCheck = true; + } +#endif +} + + +unsigned int +AlsaDriver::getTimers() +{ + return m_timers.size() + 1; // one extra for auto +} + +QString +AlsaDriver::getTimer(unsigned int n) +{ + if (n == 0) + return AUTO_TIMER_NAME; + else + return m_timers[n -1].name.c_str(); +} + +QString +AlsaDriver::getCurrentTimer() +{ + return m_currentTimer.c_str(); +} + +void +AlsaDriver::setCurrentTimer(QString timer) +{ + Audit audit; + + if (timer == getCurrentTimer()) + return ; + + std::cerr << "AlsaDriver::setCurrentTimer(" << timer << ")" << std::endl; + + std::string name(timer.data()); + + if (name == AUTO_TIMER_NAME) { + name = getAutoTimer(m_doTimerChecks); + } else { + m_doTimerChecks = false; + } + m_timerRatioCalculated = false; + + // Stop and restart the queue around the timer change. We don't + // call stopClocks/startClocks here because they do the wrong + // thing if we're currently playing and on the JACK transport. + + m_queueRunning = false; + checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "setCurrentTimer(): stopping queue"); + checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to stop queue"); + + snd_seq_event_t event; + snd_seq_ev_clear(&event); + snd_seq_real_time_t z = { 0, 0 }; + snd_seq_ev_set_queue_pos_real(&event, m_queue, &z); + snd_seq_ev_set_direct(&event); + checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME, + 0, &event), "setCurrentTimer(): control queue"); + checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to control queue"); + m_alsaPlayStartTime = RealTime::zeroTime; + + for (unsigned int i = 0; i < m_timers.size(); ++i) { + if (m_timers[i].name == name) { + + snd_seq_queue_timer_t *timer; + snd_timer_id_t *timerid; + + snd_seq_queue_timer_alloca(&timer); + snd_seq_get_queue_timer(m_midiHandle, m_queue, timer); + + snd_timer_id_alloca(&timerid); + snd_timer_id_set_class(timerid, m_timers[i].clas); + snd_timer_id_set_sclass(timerid, m_timers[i].sclas); + snd_timer_id_set_card(timerid, m_timers[i].card); + snd_timer_id_set_device(timerid, m_timers[i].device); + snd_timer_id_set_subdevice(timerid, m_timers[i].subdevice); + + snd_seq_queue_timer_set_id(timer, timerid); + snd_seq_set_queue_timer(m_midiHandle, m_queue, timer); + + if (m_doTimerChecks) { + audit << " Current timer set to \"" << name << "\" with timer checks" + << std::endl; + } else { + audit << " Current timer set to \"" << name << "\"" + << std::endl; + } + + if (m_timers[i].clas == SND_TIMER_CLASS_GLOBAL && + m_timers[i].device == SND_TIMER_GLOBAL_SYSTEM) { + long hz = 1000000000 / m_timers[i].resolution; + if (hz < 900) { + audit << " WARNING: using system timer with only " + << hz << "Hz resolution!" << std::endl; + } + } + + break; + } + } + +#ifdef HAVE_LIBJACK + if (m_jackDriver) + m_jackDriver->prebufferAudio(); +#endif + + checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "checkAlsaError(): continue queue"); + checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to continue queue"); + m_queueRunning = true; + + m_firstTimerCheck = true; +} + +bool +AlsaDriver::initialise() +{ + bool result = true; + + initialiseAudio(); + result = initialiseMidi(); + + return result; +} + + + +// Set up queue, client and port +// +bool +AlsaDriver::initialiseMidi() +{ + Audit audit; + + // Create a non-blocking handle. + // + if (snd_seq_open(&m_midiHandle, + "default", + SND_SEQ_OPEN_DUPLEX, + SND_SEQ_NONBLOCK) < 0) { + audit << "AlsaDriver::initialiseMidi - " + << "couldn't open sequencer - " << snd_strerror(errno) + << " - perhaps you need to modprobe snd-seq-midi." + << std::endl; + reportFailure(MappedEvent::FailureALSACallFailed); + return false; + } + + snd_seq_set_client_name(m_midiHandle, "rosegarden"); + + if ((m_client = snd_seq_client_id(m_midiHandle)) < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::initialiseMidi - can't create client" + << std::endl; +#endif + + return false; + } + + // Create a queue + // + if ((m_queue = snd_seq_alloc_named_queue(m_midiHandle, + "Rosegarden queue")) < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::initialiseMidi - can't allocate queue" + << std::endl; +#endif + + return false; + } + + // Create the input port + // + snd_seq_port_info_t *pinfo; + + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_capability(pinfo, + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE ); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_midi_channels(pinfo, 16); + /* we want to know when the events got delivered to us */ + snd_seq_port_info_set_timestamping(pinfo, 1); + snd_seq_port_info_set_timestamp_real(pinfo, 1); + snd_seq_port_info_set_timestamp_queue(pinfo, m_queue); + snd_seq_port_info_set_name(pinfo, "record in"); + + if (checkAlsaError(snd_seq_create_port(m_midiHandle, pinfo), + "initialiseMidi - can't create input port") < 0) + return false; + m_inputPort = snd_seq_port_info_get_port(pinfo); + + // Subscribe the input port to the ALSA Announce port + // to receive notifications when clients, ports and subscriptions change + snd_seq_connect_from( m_midiHandle, m_inputPort, + SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE ); + + m_midiInputPortConnected = true; + + // Set the input queue size + // + if (snd_seq_set_client_pool_output(m_midiHandle, 2000) < 0 || + snd_seq_set_client_pool_input(m_midiHandle, 2000) < 0 || + snd_seq_set_client_pool_output_room(m_midiHandle, 2000) < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::initialiseMidi - " + << "can't modify pool parameters" + << std::endl; +#endif + + return false; + } + + // Create sync output now as well + m_syncOutputPort = checkAlsaError(snd_seq_create_simple_port + (m_midiHandle, + "sync out", + SND_SEQ_PORT_CAP_READ | + SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_APPLICATION), + "initialiseMidi - can't create sync output port"); + + // and port for hardware controller + m_controllerPort = checkAlsaError(snd_seq_create_simple_port + (m_midiHandle, + "external controller", + SND_SEQ_PORT_CAP_READ | + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_READ | + SND_SEQ_PORT_CAP_SUBS_WRITE, + SND_SEQ_PORT_TYPE_APPLICATION), + "initialiseMidi - can't create controller port"); + + getSystemInfo(); + + generatePortList(); + generateInstruments(); + + // Modify status with MIDI success + // + m_driverStatus |= MIDI_OK; + + generateTimerList(); + setCurrentTimer(AUTO_TIMER_NAME); + + // Start the timer + if (checkAlsaError(snd_seq_start_queue(m_midiHandle, m_queue, NULL), + "initialiseMidi(): couldn't start queue") < 0) { + reportFailure(MappedEvent::FailureALSACallFailed); + return false; + } + + m_queueRunning = true; + + // process anything pending + checkAlsaError(snd_seq_drain_output(m_midiHandle), "initialiseMidi(): couldn't drain output"); + + audit << "AlsaDriver::initialiseMidi - initialised MIDI subsystem" + << std::endl << std::endl; + + return true; +} + +// We don't even attempt to use ALSA audio. We just use JACK instead. +// See comment at the top of this file and jackProcess() for further +// information on how we use this. +// +void +AlsaDriver::initialiseAudio() +{ +#ifdef HAVE_LIBJACK + m_jackDriver = new JackDriver(this); + + if (m_jackDriver->isOK()) { + m_driverStatus |= AUDIO_OK; + } else { + delete m_jackDriver; + m_jackDriver = 0; + } +#endif +} + +void +AlsaDriver::initialisePlayback(const RealTime &position) +{ +#ifdef DEBUG_ALSA + std::cerr << "\n\nAlsaDriver - initialisePlayback" << std::endl; +#endif + + // now that we restart the queue at each play, the origin is always zero + m_alsaPlayStartTime = RealTime::zeroTime; + m_playStartPosition = position; + + m_startPlayback = true; + + m_mtcFirstTime = -1; + m_mtcSigmaE = 0; + m_mtcSigmaC = 0; + + if (getMMCStatus() == TRANSPORT_MASTER) { + sendMMC(127, MIDI_MMC_PLAY, true, ""); + m_eat_mtc = 0; + } + + if (getMTCStatus() == TRANSPORT_MASTER) { + insertMTCFullFrame(position); + } + + // If MIDI Sync is enabled then adjust for the MIDI Clock to + // synchronise the sequencer with the clock. + // + if (getMIDISyncStatus() == TRANSPORT_MASTER) { + // Send the Song Position Pointer for MIDI CLOCK positioning + // + // Get time from current alsa time to start of alsa timing - + // add the initial starting point and divide by the MIDI Beat + // length. The SPP is is the MIDI Beat upon which to start the song. + // Songs are always assumed to start on a MIDI Beat of 0. Each MIDI + // Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th + // note (since there are 24 MIDI Clocks in a quarter note). + // + long spp = + long(((getAlsaTime() - m_alsaPlayStartTime + m_playStartPosition) / + m_midiClockInterval) / 6.0 ); + + // Ok now we have the new SPP - stop the transport and restart with the + // new value. + // + sendSystemDirect(SND_SEQ_EVENT_STOP, NULL); + + signed int args = spp; + sendSystemDirect(SND_SEQ_EVENT_SONGPOS, &args); + + // Now send the START/CONTINUE + // + if (m_playStartPosition == RealTime::zeroTime) + sendSystemQueued(SND_SEQ_EVENT_START, "", + m_alsaPlayStartTime); + else + sendSystemQueued(SND_SEQ_EVENT_CONTINUE, "", + m_alsaPlayStartTime); + } + +#ifdef HAVE_LIBJACK + if (m_jackDriver) { + m_needJackStart = NeedJackStart; + } +#endif +} + + +void +AlsaDriver::stopPlayback() +{ +#ifdef DEBUG_ALSA + std::cerr << "\n\nAlsaDriver - stopPlayback" << std::endl; +#endif + + if (getMIDISyncStatus() == TRANSPORT_MASTER) { + sendSystemDirect(SND_SEQ_EVENT_STOP, NULL); + } + + if (getMMCStatus() == TRANSPORT_MASTER) { + sendMMC(127, MIDI_MMC_STOP, true, ""); + // need to throw away the next MTC event + m_eat_mtc = 3; + } + + allNotesOff(); + m_playing = false; + +#ifdef HAVE_LIBJACK + if (m_jackDriver) { + m_jackDriver->stopTransport(); + m_needJackStart = NeedNoJackStart; + } +#endif + + // Flush the output and input queues + // + snd_seq_remove_events_t *info; + snd_seq_remove_events_alloca(&info); + snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_INPUT | + SND_SEQ_REMOVE_OUTPUT); + snd_seq_remove_events(m_midiHandle, info); + + // send sounds-off to all play devices + // + for (MappedDeviceList::iterator i = m_devices.begin(); i != m_devices.end(); ++i) { + if ((*i)->getDirection() == MidiDevice::Play) { + sendDeviceController((*i)->getId(), + MIDI_CONTROLLER_SUSTAIN, 0); + sendDeviceController((*i)->getId(), + MIDI_CONTROLLER_ALL_NOTES_OFF, 0); + } + } + + punchOut(); + + stopClocks(); // Resets ALSA timer to zero + + clearAudioQueue(); + + startClocksApproved(); // restarts ALSA timer without starting JACK transport +} + +void +AlsaDriver::punchOut() +{ +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::punchOut" << std::endl; +#endif + +#ifdef HAVE_LIBJACK + // Close any recording file + if (m_recordStatus == RECORD_ON) { + for (InstrumentSet::const_iterator i = m_recordingInstruments.begin(); + i != m_recordingInstruments.end(); ++i) { + + InstrumentId id = *i; + + if (id >= AudioInstrumentBase && + id < MidiInstrumentBase) { + + AudioFileId auid = 0; + if (m_jackDriver && m_jackDriver->closeRecordFile(id, auid)) { + +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::stopPlayback: sending back to GUI for instrument " << id << std::endl; +#endif + + // Create event to return to gui to say that we've + // completed an audio file and we can generate a + // preview for it now. + // + // nasty hack -- don't have right audio id here, and + // the sequencer will wipe out the instrument id and + // replace it with currently-selected one in gui -- + // so use audio id slot to pass back instrument id + // and handle accordingly in gui + try { + MappedEvent *mE = + new MappedEvent(id, + MappedEvent::AudioGeneratePreview, + id % 256, + id / 256); + + // send completion event + insertMappedEventForReturn(mE); + } catch (...) { + ; + } + } + } + } + } +#endif + + // Change recorded state if any set + // + if (m_recordStatus == RECORD_ON) + m_recordStatus = RECORD_OFF; + + m_recordingInstruments.clear(); +} + +void +AlsaDriver::resetPlayback(const RealTime &oldPosition, const RealTime &position) +{ +#ifdef DEBUG_ALSA + std::cerr << "\n\nAlsaDriver - resetPlayback(" << oldPosition << "," << position << ")" << std::endl; +#endif + + if (getMMCStatus() == TRANSPORT_MASTER) { + unsigned char t_sec = (unsigned char) position.sec % 60; + unsigned char t_min = (unsigned char) (position.sec / 60) % 60; + unsigned char t_hrs = (unsigned char) (position.sec / 3600); +#define STUPID_BROKEN_EQUIPMENT +#ifdef STUPID_BROKEN_EQUIPMENT + // Some recorders assume you are talking in 30fps... + unsigned char t_frm = (unsigned char) (position.nsec / 33333333U); + unsigned char t_sbf = (unsigned char) ((position.nsec / 333333U) % 100U); +#else + // We always send at 25fps, it's the easiest to avoid rounding problems + unsigned char t_frm = (unsigned char) (position.nsec / 40000000U); + unsigned char t_sbf = (unsigned char) ((position.nsec / 400000U) % 100U); +#endif + + std::cerr << "\n Jump using MMC LOCATE to" << position << std::endl; + std::cerr << "\t which is " << int(t_hrs) << ":" << int(t_min) << ":" << int(t_sec) << "." << int(t_frm) << "." << int(t_sbf) << std::endl; + unsigned char locateDataArr[7] = { + 0x06, + 0x01, + 0x60 + t_hrs, // (30fps flag) + hh + t_min, // mm + t_sec, // ss + t_frm, // frames + t_sbf // subframes + }; + + sendMMC(127, MIDI_MMC_LOCATE, true, std::string((const char *) locateDataArr, 7)); + } + + RealTime formerStartPosition = m_playStartPosition; + + m_playStartPosition = position; + m_alsaPlayStartTime = getAlsaTime(); + + // Reset note offs to correct positions + // + RealTime jump = position - oldPosition; + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "Currently " << m_noteOffQueue.size() << " in note off queue" << std::endl; +#endif + + // modify the note offs that exist as they're relative to the + // playStartPosition terms. + // + for (NoteOffQueue::iterator i = m_noteOffQueue.begin(); + i != m_noteOffQueue.end(); ++i) { + + // if we're fast forwarding then we bring the note off closer + if (jump >= RealTime::zeroTime) { + + RealTime endTime = formerStartPosition + (*i)->getRealTime(); + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "Forward jump of " << jump << ": adjusting note off from " + << (*i)->getRealTime() << " (absolute " << endTime + << ") to "; +#endif + (*i)->setRealTime(endTime - position); +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << (*i)->getRealTime() << std::endl; +#endif + } else // we're rewinding - kill the note immediately + { +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "Rewind by " << jump << ": setting note off to zero" << std::endl; +#endif + (*i)->setRealTime(RealTime::zeroTime); + } + } + + pushRecentNoteOffs(); + processNotesOff(getAlsaTime(), true); + checkAlsaError(snd_seq_drain_output(m_midiHandle), "resetPlayback(): draining"); + + // Ensure we clear down output queue on reset - in the case of + // MIDI clock where we might have a long queue of events already + // posted. + // + snd_seq_remove_events_t *info; + snd_seq_remove_events_alloca(&info); + snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT); + snd_seq_remove_events(m_midiHandle, info); + + if (getMTCStatus() == TRANSPORT_MASTER) { + m_mtcFirstTime = -1; + m_mtcSigmaE = 0; + m_mtcSigmaC = 0; + insertMTCFullFrame(position); + } + +#ifdef HAVE_LIBJACK + if (m_jackDriver) { + m_jackDriver->clearSynthPluginEvents(); + m_needJackStart = NeedJackReposition; + } +#endif +} + +void +AlsaDriver::setMIDIClockInterval(RealTime interval) +{ +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::setMIDIClockInterval(" << interval << ")" << endl; +#endif + + // Reset the value + // + SoundDriver::setMIDIClockInterval(interval); + + // Return if the clock isn't enabled + // + if (!m_midiClockEnabled) + return ; + + if (false) // don't remove any events quite yet + { + + // Remove all queued events (although we should filter this + // down to just the clock events. + // + snd_seq_remove_events_t *info; + snd_seq_remove_events_alloca(&info); + + //if (snd_seq_type_check(SND_SEQ_EVENT_CLOCK, SND_SEQ_EVFLG_CONTROL)) + //snd_seq_remove_events_set_event_type(info, + snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT); + snd_seq_remove_events_set_event_type(info, SND_SEQ_EVFLG_CONTROL); + std::cout << "AlsaDriver::setMIDIClockInterval - " + << "MIDI CLOCK TYPE IS CONTROL" << std::endl; + snd_seq_remove_events(m_midiHandle, info); + } + +} + + +void +AlsaDriver::pushRecentNoteOffs() +{ +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::pushRecentNoteOffs: have " << m_recentNoteOffs.size() << " in queue" << std::endl; +#endif + + for (NoteOffQueue::iterator i = m_recentNoteOffs.begin(); + i != m_recentNoteOffs.end(); ++i) { + (*i)->setRealTime(RealTime::zeroTime); + m_noteOffQueue.insert(*i); + } + + m_recentNoteOffs.clear(); +} + +void +AlsaDriver::cropRecentNoteOffs(const RealTime &t) +{ + while (!m_recentNoteOffs.empty()) { + NoteOffEvent *ev = *m_recentNoteOffs.begin(); +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::cropRecentNoteOffs: " << ev->getRealTime() << " vs " << t << std::endl; +#endif + if (ev->getRealTime() >= t) break; + delete ev; + m_recentNoteOffs.erase(m_recentNoteOffs.begin()); + } +} + +void +AlsaDriver::weedRecentNoteOffs(unsigned int pitch, MidiByte channel, + InstrumentId instrument) +{ + for (NoteOffQueue::iterator i = m_recentNoteOffs.begin(); + i != m_recentNoteOffs.end(); ++i) { + if ((*i)->getPitch() == pitch && + (*i)->getChannel() == channel && + (*i)->getInstrument() == instrument) { +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::weedRecentNoteOffs: deleting one" << std::endl; +#endif + delete *i; + m_recentNoteOffs.erase(i); + break; + } + } +} + +void +AlsaDriver::allNotesOff() +{ + snd_seq_event_t event; + ClientPortPair outputDevice; + RealTime offTime; + + // drop any pending notes + snd_seq_drop_output_buffer(m_midiHandle); + snd_seq_drop_output(m_midiHandle); + + // prepare the event + snd_seq_ev_clear(&event); + offTime = getAlsaTime(); + + for (NoteOffQueue::iterator it = m_noteOffQueue.begin(); + it != m_noteOffQueue.end(); ++it) { + // Set destination according to connection for instrument + // + outputDevice = getPairForMappedInstrument((*it)->getInstrument()); + if (outputDevice.first < 0 || outputDevice.second < 0) + continue; + + snd_seq_ev_set_subs(&event); + + // Set source according to port for device + // + int src = getOutputPortForMappedInstrument((*it)->getInstrument()); + if (src < 0) + continue; + snd_seq_ev_set_source(&event, src); + + snd_seq_ev_set_noteoff(&event, + (*it)->getChannel(), + (*it)->getPitch(), + 127); + + //snd_seq_event_output(m_midiHandle, &event); + int error = snd_seq_event_output_direct(m_midiHandle, &event); + + if (error < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::allNotesOff - " + << "can't send event" << std::endl; +#endif + + } + + delete(*it); + } + + m_noteOffQueue.erase(m_noteOffQueue.begin(), m_noteOffQueue.end()); + + /* + std::cerr << "AlsaDriver::allNotesOff - " + << " queue size = " << m_noteOffQueue.size() << std::endl; + */ + + // flush + checkAlsaError(snd_seq_drain_output(m_midiHandle), "allNotesOff(): draining"); +} + +void +AlsaDriver::processNotesOff(const RealTime &time, bool now, bool everything) +{ + if (m_noteOffQueue.empty()) { + return; + } + + snd_seq_event_t event; + + ClientPortPair outputDevice; + RealTime offTime; + + // prepare the event + snd_seq_ev_clear(&event); + + RealTime alsaTime = getAlsaTime(); + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::processNotesOff(" << time << "): alsaTime = " << alsaTime << ", now = " << now << std::endl; +#endif + + while (m_noteOffQueue.begin() != m_noteOffQueue.end()) { + + NoteOffEvent *ev = *m_noteOffQueue.begin(); + + if (ev->getRealTime() > time) { +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "Note off time " << ev->getRealTime() << " is beyond current time " << time << std::endl; +#endif + if (!everything) break; + } + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::processNotesOff(" << time << "): found event at " << ev->getRealTime() << ", instr " << ev->getInstrument() << ", channel " << int(ev->getChannel()) << ", pitch " << int(ev->getPitch()) << std::endl; +#endif + + bool isSoftSynth = (ev->getInstrument() >= SoftSynthInstrumentBase); + + offTime = ev->getRealTime(); + if (offTime < RealTime::zeroTime) offTime = RealTime::zeroTime; + bool scheduled = (offTime > alsaTime) && !now; + if (!scheduled) offTime = RealTime::zeroTime; + + snd_seq_real_time_t alsaOffTime = { offTime.sec, + offTime.nsec }; + + snd_seq_ev_set_noteoff(&event, + ev->getChannel(), + ev->getPitch(), + 127); + + if (!isSoftSynth) { + + snd_seq_ev_set_subs(&event); + + // Set source according to instrument + // + int src = getOutputPortForMappedInstrument(ev->getInstrument()); + if (src < 0) { + std::cerr << "note off has no output port (instr = " << ev->getInstrument() << ")" << std::endl; + delete ev; + m_noteOffQueue.erase(m_noteOffQueue.begin()); + continue; + } + + snd_seq_ev_set_source(&event, src); + + snd_seq_ev_set_subs(&event); + + snd_seq_ev_schedule_real(&event, m_queue, 0, &alsaOffTime); + + if (scheduled) { + snd_seq_event_output(m_midiHandle, &event); + } else { + snd_seq_event_output_direct(m_midiHandle, &event); + } + + } else { + + event.time.time = alsaOffTime; + + processSoftSynthEventOut(ev->getInstrument(), &event, now); + } + + if (!now) { + m_recentNoteOffs.insert(ev); + } else { + delete ev; + } + m_noteOffQueue.erase(m_noteOffQueue.begin()); + } + + // We don't flush the queue here, as this is called nested from + // processMidiOut, which does the flushing + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::processNotesOff - " + << " queue size now: " << m_noteOffQueue.size() << std::endl; +#endif +} + +// Get the queue time and convert it to RealTime for the gui +// to use. +// +RealTime +AlsaDriver::getSequencerTime() +{ + RealTime t(0, 0); + + t = getAlsaTime() + m_playStartPosition - m_alsaPlayStartTime; + + // std::cerr << "AlsaDriver::getSequencerTime: alsa time is " + // << getAlsaTime() << ", start time is " << m_alsaPlayStartTime << ", play start position is " << m_playStartPosition << endl; + + return t; +} + +// Gets the time of the ALSA queue +// +RealTime +AlsaDriver::getAlsaTime() +{ + RealTime sequencerTime(0, 0); + + snd_seq_queue_status_t *status; + snd_seq_queue_status_alloca(&status); + + if (snd_seq_get_queue_status(m_midiHandle, m_queue, status) < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::getAlsaTime - can't get queue status" + << std::endl; +#endif + + return sequencerTime; + } + + sequencerTime.sec = snd_seq_queue_status_get_real_time(status)->tv_sec; + sequencerTime.nsec = snd_seq_queue_status_get_real_time(status)->tv_nsec; + + // std::cerr << "AlsaDriver::getAlsaTime: alsa time is " << sequencerTime << std::endl; + + return sequencerTime; +} + + +// Get all pending input events and turn them into a MappedComposition. +// +// +MappedComposition* +AlsaDriver::getMappedComposition() +{ + m_recordComposition.clear(); + + while (_failureReportReadIndex != _failureReportWriteIndex) { + MappedEvent::FailureCode code = _failureReports[_failureReportReadIndex]; + // std::cerr << "AlsaDriver::reportFailure(" << code << ")" << std::endl; + MappedEvent *mE = new MappedEvent + (0, MappedEvent::SystemFailure, code, 0); + m_returnComposition.insert(mE); + _failureReportReadIndex = + (_failureReportReadIndex + 1) % FAILURE_REPORT_COUNT; + } + + if (!m_returnComposition.empty()) { + for (MappedComposition::iterator i = m_returnComposition.begin(); + i != m_returnComposition.end(); ++i) { + m_recordComposition.insert(new MappedEvent(**i)); + } + m_returnComposition.clear(); + } + + // If the input port hasn't connected we shouldn't poll it + // + if (m_midiInputPortConnected == false) { + return &m_recordComposition; + } + + RealTime eventTime(0, 0); + + snd_seq_event_t *event; + + while (snd_seq_event_input(m_midiHandle, &event) > 0) { + + unsigned int channel = (unsigned int)event->data.note.channel; + unsigned int chanNoteKey = ( channel << 8 ) + + (unsigned int) event->data.note.note; + + bool fromController = false; + + if (event->dest.client == m_client && + event->dest.port == m_controllerPort) { +#ifdef DEBUG_ALSA + std::cerr << "Received an external controller event" << std::endl; +#endif + + fromController = true; + } + + unsigned int deviceId = Device::NO_DEVICE; + + if (fromController) { + deviceId = Device::CONTROL_DEVICE; + } else { + for (MappedDeviceList::iterator i = m_devices.begin(); + i != m_devices.end(); ++i) { + ClientPortPair pair(m_devicePortMap[(*i)->getId()]); + if (((*i)->getDirection() == MidiDevice::Record) && + ( pair.first == event->source.client ) && + ( pair.second == event->source.port )) { + deviceId = (*i)->getId(); + break; + } + } + } + + eventTime.sec = event->time.time.tv_sec; + eventTime.nsec = event->time.time.tv_nsec; + eventTime = eventTime - m_alsaRecordStartTime + m_playStartPosition; + +#ifdef DEBUG_ALSA + if (!fromController) { + std::cerr << "Received normal event: type " << int(event->type) << ", chan " << channel << ", note " << int(event->data.note.note) << ", time " << eventTime << std::endl; + } +#endif + + switch (event->type) { + case SND_SEQ_EVENT_NOTE: + case SND_SEQ_EVENT_NOTEON: + if (fromController) + continue; + if (event->data.note.velocity > 0) { + MappedEvent *mE = new MappedEvent(); + mE->setPitch(event->data.note.note); + mE->setVelocity(event->data.note.velocity); + mE->setEventTime(eventTime); + mE->setRecordedChannel(channel); + mE->setRecordedDevice(deviceId); + + // Negative duration - we need to hear the NOTE ON + // so we must insert it now with a negative duration + // and pick and mix against the following NOTE OFF + // when we create the recorded segment. + // + mE->setDuration(RealTime( -1, 0)); + + // Create a copy of this when we insert the NOTE ON - + // keeping a copy alive on the m_noteOnMap. + // + // We shake out the two NOTE Ons after we've recorded + // them. + // + m_recordComposition.insert(new MappedEvent(mE)); + m_noteOnMap[deviceId][chanNoteKey] = mE; + + break; + } + + case SND_SEQ_EVENT_NOTEOFF: + if (fromController) + continue; + + if (m_noteOnMap[deviceId][chanNoteKey] != 0) { + + // Set duration correctly on the NOTE OFF + // + MappedEvent *mE = m_noteOnMap[deviceId][chanNoteKey]; + RealTime duration = eventTime - mE->getEventTime(); + +#ifdef DEBUG_ALSA + std::cerr << "NOTE OFF: found NOTE ON at " << mE->getEventTime() << std::endl; +#endif + + if (duration < RealTime::zeroTime) { + duration = RealTime::zeroTime; + mE->setEventTime(eventTime); + } + + // Velocity 0 - NOTE OFF. Set duration correctly + // for recovery later. + // + mE->setVelocity(0); + mE->setDuration(duration); + + // force shut off of note + m_recordComposition.insert(mE); + + // reset the reference + // + m_noteOnMap[deviceId][chanNoteKey] = 0; + + } + break; + + case SND_SEQ_EVENT_KEYPRESS: { + if (fromController) + continue; + + // Fix for 632964 by Pedro Lopez-Cabanillas (20030523) + // + MappedEvent *mE = new MappedEvent(); + mE->setType(MappedEvent::MidiKeyPressure); + mE->setEventTime(eventTime); + mE->setData1(event->data.note.note); + mE->setData2(event->data.note.velocity); + mE->setRecordedChannel(channel); + mE->setRecordedDevice(deviceId); + m_recordComposition.insert(mE); + } + break; + + case SND_SEQ_EVENT_CONTROLLER: { + MappedEvent *mE = new MappedEvent(); + mE->setType(MappedEvent::MidiController); + mE->setEventTime(eventTime); + mE->setData1(event->data.control.param); + mE->setData2(event->data.control.value); + mE->setRecordedChannel(channel); + mE->setRecordedDevice(deviceId); + m_recordComposition.insert(mE); + } + break; + + case SND_SEQ_EVENT_PGMCHANGE: { + MappedEvent *mE = new MappedEvent(); + mE->setType(MappedEvent::MidiProgramChange); + mE->setEventTime(eventTime); + mE->setData1(event->data.control.value); + mE->setRecordedChannel(channel); + mE->setRecordedDevice(deviceId); + m_recordComposition.insert(mE); + + } + break; + + case SND_SEQ_EVENT_PITCHBEND: { + if (fromController) + continue; + + // Fix for 711889 by Pedro Lopez-Cabanillas (20030523) + // + int s = event->data.control.value + 8192; + int d1 = (s >> 7) & 0x7f; // data1 = MSB + int d2 = s & 0x7f; // data2 = LSB + MappedEvent *mE = new MappedEvent(); + mE->setType(MappedEvent::MidiPitchBend); + mE->setEventTime(eventTime); + mE->setData1(d1); + mE->setData2(d2); + mE->setRecordedChannel(channel); + mE->setRecordedDevice(deviceId); + m_recordComposition.insert(mE); + } + break; + + case SND_SEQ_EVENT_CHANPRESS: { + if (fromController) + continue; + + // Fixed by Pedro Lopez-Cabanillas (20030523) + // + int s = event->data.control.value & 0x7f; + MappedEvent *mE = new MappedEvent(); + mE->setType(MappedEvent::MidiChannelPressure); + mE->setEventTime(eventTime); + mE->setData1(s); + mE->setRecordedChannel(channel); + mE->setRecordedDevice(deviceId); + m_recordComposition.insert(mE); + } + break; + + case SND_SEQ_EVENT_SYSEX: + + if (fromController) + continue; + + if (!testForMTCSysex(event) && + !testForMMCSysex(event)) { + + // Bundle up the data into a block on the MappedEvent + // + std::string data; + char *ptr = (char*)(event->data.ext.ptr); + for (unsigned int i = 0; i < event->data.ext.len; ++i) + data += *(ptr++); + +#ifdef DEBUG_ALSA + + if ((MidiByte)(data[1]) == MIDI_SYSEX_RT) { + std::cerr << "REALTIME SYSEX" << endl; + for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) { + printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]); + } + } else { + std::cerr << "NON-REALTIME SYSEX" << endl; + for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) { + printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]); + } + } +#endif + + MappedEvent *mE = new MappedEvent(); + mE->setType(MappedEvent::MidiSystemMessage); + mE->setData1(MIDI_SYSTEM_EXCLUSIVE); + mE->setRecordedDevice(deviceId); + // chop off SYX and EOX bytes from data block + // Fix for 674731 by Pedro Lopez-Cabanillas (20030601) + DataBlockRepository::setDataBlockForEvent(mE, data.substr(1, data.length() - 2)); + mE->setEventTime(eventTime); + m_recordComposition.insert(mE); + } + break; + + + case SND_SEQ_EVENT_SENSING: // MIDI device is still there + break; + + case SND_SEQ_EVENT_QFRAME: + if (fromController) + continue; + if (getMTCStatus() == TRANSPORT_SLAVE) { + handleMTCQFrame(event->data.control.value, eventTime); + } + break; + + case SND_SEQ_EVENT_CLOCK: +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::getMappedComposition - " + << "got realtime MIDI clock" << std::endl; +#endif + + break; + + case SND_SEQ_EVENT_START: + if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) { + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportJump(ExternalTransport::TransportStopAtTime, + RealTime::zeroTime); + transport->transportChange(ExternalTransport::TransportStart); + } + } +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::getMappedComposition - " + << "START" << std::endl; +#endif + + break; + + case SND_SEQ_EVENT_CONTINUE: + if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) { + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportChange(ExternalTransport::TransportPlay); + } + } +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::getMappedComposition - " + << "CONTINUE" << std::endl; +#endif + + break; + + case SND_SEQ_EVENT_STOP: + if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && isPlaying()) { + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportChange(ExternalTransport::TransportStop); + } + } +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::getMappedComposition - " + << "STOP" << std::endl; +#endif + + break; + + case SND_SEQ_EVENT_SONGPOS: +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::getMappedComposition - " + << "SONG POSITION" << std::endl; +#endif + + break; + + // these cases are handled by checkForNewClients + // + case SND_SEQ_EVENT_CLIENT_START: + case SND_SEQ_EVENT_CLIENT_EXIT: + case SND_SEQ_EVENT_CLIENT_CHANGE: + case SND_SEQ_EVENT_PORT_START: + case SND_SEQ_EVENT_PORT_EXIT: + case SND_SEQ_EVENT_PORT_CHANGE: + case SND_SEQ_EVENT_PORT_SUBSCRIBED: + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: + m_portCheckNeeded = true; +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::getMappedComposition - " + << "got announce event (" + << int(event->type) << ")" << std::endl; +#endif + + break; + case SND_SEQ_EVENT_TICK: + default: +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::getMappedComposition - " + << "got unhandled MIDI event type from ALSA sequencer" + << "(" << int(event->type) << ")" << std::endl; +#endif + + break; + + + } + } + + if (getMTCStatus() == TRANSPORT_SLAVE && isPlaying()) { +#ifdef MTC_DEBUG + std::cerr << "seq time is " << getSequencerTime() << ", last MTC receive " + << m_mtcLastReceive << ", first time " << m_mtcFirstTime << std::endl; +#endif + + if (m_mtcFirstTime == 0) { // have received _some_ MTC quarter-frame info + RealTime seqTime = getSequencerTime(); + if (m_mtcLastReceive < seqTime && + seqTime - m_mtcLastReceive > RealTime(0, 500000000L)) { + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportJump(ExternalTransport::TransportStopAtTime, + m_mtcLastEncoded); + } + } + } + } + + return &m_recordComposition; +} + +static int lock_count = 0; + +void +AlsaDriver::handleMTCQFrame(unsigned int data_byte, RealTime the_time) +{ + if (getMTCStatus() != TRANSPORT_SLAVE) + return ; + + switch (data_byte & 0xF0) { + /* Frame */ + case 0x00: + /* + * Reset everything + */ + m_mtcReceiveTime = the_time; + m_mtcFrames = data_byte & 0x0f; + m_mtcSeconds = 0; + m_mtcMinutes = 0; + m_mtcHours = 0; + m_mtcSMPTEType = 0; + + break; + + case 0x10: + m_mtcFrames |= (data_byte & 0x0f) << 4; + break; + + /* Seconds */ + case 0x20: + m_mtcSeconds = data_byte & 0x0f; + break; + case 0x30: + m_mtcSeconds |= (data_byte & 0x0f) << 4; + break; + + /* Minutes */ + case 0x40: + m_mtcMinutes = data_byte & 0x0f; + break; + case 0x50: + m_mtcMinutes |= (data_byte & 0x0f) << 4; + break; + + /* Hours and SMPTE type */ + case 0x60: + m_mtcHours = data_byte & 0x0f; + break; + + case 0x70: { + m_mtcHours |= (data_byte & 0x01) << 4; + m_mtcSMPTEType = (data_byte & 0x06) >> 1; + + int fps = 30; + if (m_mtcSMPTEType == 0) + fps = 24; + else if (m_mtcSMPTEType == 1) + fps = 25; + + /* + * Ok, got all the bits now + * (Assuming time is rolling forward) + */ + + /* correct for 2-frame lag */ + m_mtcFrames += 2; + if (m_mtcFrames >= fps) { + m_mtcFrames -= fps; + if (++m_mtcSeconds == 60) { + m_mtcSeconds = 0; + if (++m_mtcMinutes == 60) { + m_mtcMinutes = 0; + ++m_mtcHours; + } + } + } + +#ifdef MTC_DEBUG + printf("RG MTC: Got a complete sequence: %02d:%02d:%02d.%02d (type %d)\n", + m_mtcHours, + m_mtcMinutes, + m_mtcSeconds, + m_mtcFrames, + m_mtcSMPTEType); +#endif + + /* compute encoded time */ + m_mtcEncodedTime.sec = m_mtcSeconds + + m_mtcMinutes * 60 + + m_mtcHours * 60 * 60; + + switch (fps) { + case 24: + m_mtcEncodedTime.nsec = (int) + ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3); + break; + case 25: + m_mtcEncodedTime.nsec = (int) + (40000000UL * (unsigned)m_mtcFrames); + break; + case 30: + default: + m_mtcEncodedTime.nsec = (int) + ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3); + break; + } + + /* + * We only mess with the clock if we are playing + */ + if (m_playing) { +#ifdef MTC_DEBUG + std::cerr << "RG MTC: Tstamp " << m_mtcEncodedTime; + std::cerr << " Received @ " << m_mtcReceiveTime << endl; +#endif + + calibrateMTC(); + + RealTime t_diff = m_mtcEncodedTime - m_mtcReceiveTime; +#ifdef MTC_DEBUG + + std::cerr << "Diff: " << t_diff << endl; +#endif + + /* -ve diff means ALSA time ahead of MTC time */ + + if (t_diff.sec > 0) { + tweakSkewForMTC(60000); + } else if (t_diff.sec < 0) { + tweakSkewForMTC( -60000); + } else { + /* "small" diff - use adaptive technique */ + tweakSkewForMTC(t_diff.nsec / 1400); + if ((t_diff.nsec / 1000000) == 0) { + if (++lock_count == 3) { + printf("Got a lock @ %02d:%02d:%02d.%02d (type %d)\n", + m_mtcHours, + m_mtcMinutes, + m_mtcSeconds, + m_mtcFrames, + m_mtcSMPTEType); + } + } else { + lock_count = 0; + } + } + + } else if (m_eat_mtc > 0) { +#ifdef MTC_DEBUG + std::cerr << "MTC: Received quarter frame just after issuing MMC stop - ignore it" << std::endl; +#endif + + --m_eat_mtc; + } else { + /* If we're not playing, we should be. */ +#ifdef MTC_DEBUG + std::cerr << "MTC: Received quarter frame while not playing - starting now" << std::endl; +#endif + + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportJump + (ExternalTransport::TransportStartAtTime, + m_mtcEncodedTime); + } + } + + break; + } + + /* Oh dear, demented device! */ + default: + break; + } +} + +void +AlsaDriver::insertMTCFullFrame(RealTime time) +{ + snd_seq_event_t event; + + snd_seq_ev_clear(&event); + snd_seq_ev_set_source(&event, m_syncOutputPort); + snd_seq_ev_set_subs(&event); + + m_mtcEncodedTime = time; + m_mtcSeconds = m_mtcEncodedTime.sec % 60; + m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60; + m_mtcHours = (m_mtcEncodedTime.sec / 3600); + + // We always send at 25fps, it's the easiest to avoid rounding problems + m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U; + + time = time + m_alsaPlayStartTime - m_playStartPosition; + snd_seq_real_time_t atime = { time.sec, time.nsec }; + + unsigned char data[10] = + { MIDI_SYSTEM_EXCLUSIVE, + MIDI_SYSEX_RT, 127, 1, 1, + 0, 0, 0, 0, + MIDI_END_OF_EXCLUSIVE }; + + data[5] = ((unsigned char)m_mtcHours & 0x1f) + (1 << 5); // 1 indicates 25fps + data[6] = (unsigned char)m_mtcMinutes; + data[7] = (unsigned char)m_mtcSeconds; + data[8] = (unsigned char)m_mtcFrames; + + snd_seq_ev_schedule_real(&event, m_queue, 0, &atime); + snd_seq_ev_set_sysex(&event, 10, data); + + checkAlsaError(snd_seq_event_output(m_midiHandle, &event), + "insertMTCFullFrame event send"); + + if (m_queueRunning) { + checkAlsaError(snd_seq_drain_output(m_midiHandle), "insertMTCFullFrame drain"); + } +} + +void +AlsaDriver::insertMTCQFrames(RealTime sliceStart, RealTime sliceEnd) +{ + if (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime) { + // not a real slice + return ; + } + + // We send at 25fps, it's the easiest to avoid rounding problems + RealTime twoFrames(0, 80000000U); + RealTime quarterFrame(0, 10000000U); + int fps = 25; + +#ifdef MTC_DEBUG + + std::cout << "AlsaDriver::insertMTCQFrames(" << sliceStart << "," + << sliceEnd << "): first time " << m_mtcFirstTime << std::endl; +#endif + + RealTime t; + + if (m_mtcFirstTime != 0) { // first time through, reset location + m_mtcEncodedTime = sliceStart; + t = sliceStart; + m_mtcFirstTime = 0; + } else { + t = m_mtcEncodedTime + quarterFrame; + } + + m_mtcSeconds = m_mtcEncodedTime.sec % 60; + m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60; + m_mtcHours = (m_mtcEncodedTime.sec / 3600); + m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U; // 25fps + + std::string bytes = " "; + + int type = 0; + + while (m_mtcEncodedTime < sliceEnd) { + + snd_seq_event_t event; + snd_seq_ev_clear(&event); + snd_seq_ev_set_source(&event, m_syncOutputPort); + snd_seq_ev_set_subs(&event); + +#ifdef MTC_DEBUG + + std::cout << "Sending MTC quarter frame at " << t << std::endl; +#endif + + unsigned char c = (type << 4); + + switch (type) { + case 0: + c += ((unsigned char)m_mtcFrames & 0x0f); + break; + case 1: + c += (((unsigned char)m_mtcFrames & 0xf0) >> 4); + break; + case 2: + c += ((unsigned char)m_mtcSeconds & 0x0f); + break; + case 3: + c += (((unsigned char)m_mtcSeconds & 0xf0) >> 4); + break; + case 4: + c += ((unsigned char)m_mtcMinutes & 0x0f); + break; + case 5: + c += (((unsigned char)m_mtcMinutes & 0xf0) >> 4); + break; + case 6: + c += ((unsigned char)m_mtcHours & 0x0f); + break; + case 7: // hours high nibble + smpte type + c += (m_mtcHours >> 4) & 0x01; + c += (1 << 1); // type 1 indicates 25fps + break; + } + + RealTime scheduleTime = t + m_alsaPlayStartTime - m_playStartPosition; + snd_seq_real_time_t atime = { scheduleTime.sec, scheduleTime.nsec }; + + event.type = SND_SEQ_EVENT_QFRAME; + event.data.control.value = c; + + snd_seq_ev_schedule_real(&event, m_queue, 0, &atime); + + checkAlsaError(snd_seq_event_output(m_midiHandle, &event), + "insertMTCQFrames sending qframe event"); + + if (++type == 8) { + m_mtcFrames += 2; + if (m_mtcFrames >= fps) { + m_mtcFrames -= fps; + if (++m_mtcSeconds == 60) { + m_mtcSeconds = 0; + if (++m_mtcMinutes == 60) { + m_mtcMinutes = 0; + ++m_mtcHours; + } + } + } + m_mtcEncodedTime = t; + type = 0; + } + + t = t + quarterFrame; + } +} + +bool +AlsaDriver::testForMTCSysex(const snd_seq_event_t *event) +{ + if (getMTCStatus() != TRANSPORT_SLAVE) + return false; + + // At this point, and possibly for the foreseeable future, the only + // sysex we're interested in is full-frame transport location + +#ifdef MTC_DEBUG + + std::cerr << "MTC: testing sysex of length " << event->data.ext.len << ":" << std::endl; + for (int i = 0; i < event->data.ext.len; ++i) { + std::cerr << (int)*((unsigned char *)event->data.ext.ptr + i) << " "; + } + std::cerr << endl; +#endif + + if (event->data.ext.len != 10) + return false; + + unsigned char *ptr = (unsigned char *)(event->data.ext.ptr); + + if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE) + return false; + if (*ptr++ != MIDI_SYSEX_RT) + return false; + if (*ptr++ > 127) + return false; + + // 01 01 for MTC full frame + + if (*ptr++ != 1) + return false; + if (*ptr++ != 1) + return false; + + int htype = *ptr++; + int min = *ptr++; + int sec = *ptr++; + int frame = *ptr++; + + if (*ptr != MIDI_END_OF_EXCLUSIVE) + return false; + + int hour = (htype & 0x1f); + int type = (htype & 0xe0) >> 5; + + m_mtcFrames = frame; + m_mtcSeconds = sec; + m_mtcMinutes = min; + m_mtcHours = hour; + m_mtcSMPTEType = type; + + int fps = 30; + if (m_mtcSMPTEType == 0) + fps = 24; + else if (m_mtcSMPTEType == 1) + fps = 25; + + m_mtcEncodedTime.sec = sec + min * 60 + hour * 60 * 60; + + switch (fps) { + case 24: + m_mtcEncodedTime.nsec = (int) + ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3); + break; + case 25: + m_mtcEncodedTime.nsec = (int) + (40000000UL * (unsigned)m_mtcFrames); + break; + case 30: + default: + m_mtcEncodedTime.nsec = (int) + ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3); + break; + } + +#ifdef MTC_DEBUG + std::cerr << "MTC: MTC sysex found (frame type " << type + << "), jumping to " << m_mtcEncodedTime << std::endl; +#endif + + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportJump + (ExternalTransport::TransportJumpToTime, + m_mtcEncodedTime); + } + + return true; +} + +static int last_factor = 0; +static int bias_factor = 0; + +void +AlsaDriver::calibrateMTC() +{ + if (m_mtcFirstTime < 0) + return ; + else if (m_mtcFirstTime > 0) { + --m_mtcFirstTime; + m_mtcSigmaC = 0; + m_mtcSigmaE = 0; + } else { + RealTime diff_e = m_mtcEncodedTime - m_mtcLastEncoded; + RealTime diff_c = m_mtcReceiveTime - m_mtcLastReceive; + +#ifdef MTC_DEBUG + + printf("RG MTC: diffs %d %d %d\n", diff_c.nsec, diff_e.nsec, m_mtcSkew); +#endif + + m_mtcSigmaE += ((long long int) diff_e.nsec) * m_mtcSkew; + m_mtcSigmaC += diff_c.nsec; + + + int t_bias = (m_mtcSigmaE / m_mtcSigmaC) - 0x10000; + +#ifdef MTC_DEBUG + + printf("RG MTC: sigmas %lld %lld %d\n", m_mtcSigmaE, m_mtcSigmaC, t_bias); +#endif + + bias_factor = t_bias; + } + + m_mtcLastReceive = m_mtcReceiveTime; + m_mtcLastEncoded = m_mtcEncodedTime; + +} + +void +AlsaDriver::tweakSkewForMTC(int factor) +{ + if (factor > 50000) { + factor = 50000; + } else if (factor < -50000) { + factor = -50000; + } else if (factor == last_factor) { + return ; + } else { + if (m_mtcFirstTime == -1) + m_mtcFirstTime = 5; + } + last_factor = factor; + + snd_seq_queue_tempo_t *q_ptr; + snd_seq_queue_tempo_alloca(&q_ptr); + + snd_seq_get_queue_tempo( m_midiHandle, m_queue, q_ptr); + + unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr); +#ifdef MTC_DEBUG + + std::cerr << "RG MTC: skew: " << t_skew; +#endif + + t_skew = 0x10000 + factor + bias_factor; + +#ifdef MTC_DEBUG + + std::cerr << " changed to " << factor << "+" << bias_factor << endl; +#endif + + snd_seq_queue_tempo_set_skew(q_ptr, t_skew); + snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr); + + m_mtcSkew = t_skew; +} + +bool +AlsaDriver::testForMMCSysex(const snd_seq_event_t *event) +{ + if (getMMCStatus() != TRANSPORT_SLAVE) + return false; + + if (event->data.ext.len != 6) + return false; + + unsigned char *ptr = (unsigned char *)(event->data.ext.ptr); + + if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE) + return false; + if (*ptr++ != MIDI_SYSEX_RT) + return false; + if (*ptr++ > 127) + return false; + if (*ptr++ != MIDI_SYSEX_RT_COMMAND) + return false; + + int instruction = *ptr++; + + if (*ptr != MIDI_END_OF_EXCLUSIVE) + return false; + + if (instruction == MIDI_MMC_PLAY || + instruction == MIDI_MMC_DEFERRED_PLAY) { + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportChange(ExternalTransport::TransportPlay); + } + } else if (instruction == MIDI_MMC_STOP) { + ExternalTransport *transport = getExternalTransportControl(); + if (transport) { + transport->transportChange(ExternalTransport::TransportStop); + } + } + + return true; +} + +void +AlsaDriver::processMidiOut(const MappedComposition &mC, + const RealTime &sliceStart, + const RealTime &sliceEnd) +{ + RealTime outputTime; + RealTime outputStopTime; + MappedInstrument *instrument; + ClientPortPair outputDevice; + MidiByte channel; + snd_seq_event_t event; + + // special case for unqueued events + bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime); + + if (!now) { + // This 0.5 sec is arbitrary, but it must be larger than the + // sequencer's read-ahead + RealTime diff = RealTime::fromSeconds(0.5); + RealTime cutoff = sliceStart - diff; + cropRecentNoteOffs(cutoff - m_playStartPosition + m_alsaPlayStartTime); + } + + // These won't change in this slice + // + snd_seq_ev_clear(&event); + + if ((mC.begin() != mC.end()) && getSequencerDataBlock()) { + getSequencerDataBlock()->setVisual(*mC.begin()); + } + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "AlsaDriver::processMidiOut(" << sliceStart << "," << sliceEnd + << "), " << mC.size() << " events, now is " << now << std::endl; +#endif + + // NB the MappedComposition is implicitly ordered by time (std::multiset) + + for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) { + if ((*i)->getType() >= MappedEvent::Audio) + continue; + + bool isControllerOut = ((*i)->getRecordedDevice() == + Device::CONTROL_DEVICE); + + bool isSoftSynth = (!isControllerOut && + ((*i)->getInstrument() >= SoftSynthInstrumentBase)); + + outputTime = (*i)->getEventTime() - m_playStartPosition + + m_alsaPlayStartTime; + + if (now && !m_playing && m_queueRunning) { + // stop queue to ensure exact timing and make sure the + // event gets through right now +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut: stopping queue for now-event" << std::endl; +#endif + + checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): stop queue"); + checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining"); + } + + RealTime alsaTimeNow = getAlsaTime(); + + if (now) { + if (!m_playing) { + outputTime = alsaTimeNow; + } else if (outputTime < alsaTimeNow) { + outputTime = alsaTimeNow + RealTime(0, 10000000); + } + } + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut[" << now << "]: event is at " << outputTime << " (" << outputTime - alsaTimeNow << " ahead of queue time), type " << int((*i)->getType()) << ", duration " << (*i)->getDuration() << std::endl; +#endif + + if (!m_queueRunning && outputTime < alsaTimeNow) { + RealTime adjust = alsaTimeNow - outputTime; + if ((*i)->getDuration() > RealTime::zeroTime) { + if ((*i)->getDuration() <= adjust) { +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut[" << now << "]: too late for this event, abandoning it" << std::endl; +#endif + + continue; + } else { +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut[" << now << "]: pushing event forward and reducing duration by " << adjust << std::endl; +#endif + + (*i)->setDuration((*i)->getDuration() - adjust); + } + } else { +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut[" << now << "]: pushing zero-duration event forward by " << adjust << std::endl; +#endif + + } + outputTime = alsaTimeNow; + } + + processNotesOff(outputTime, now); + +#ifdef HAVE_LIBJACK + + if (m_jackDriver) { + size_t frameCount = m_jackDriver->getFramesProcessed(); + size_t elapsed = frameCount - _debug_jack_frame_count; + RealTime rt = RealTime::frame2RealTime(elapsed, m_jackDriver->getSampleRate()); + rt = rt - getAlsaTime(); +#ifdef DEBUG_PROCESS_MIDI_OUT + + std::cerr << "processMidiOut[" << now << "]: JACK time is " << rt << " ahead of ALSA time" << std::endl; +#endif + + } +#endif + + // Second and nanoseconds for ALSA + // + snd_seq_real_time_t time = { outputTime.sec, outputTime.nsec }; + + if (!isSoftSynth) { + +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cout << "processMidiOut[" << now << "]: instrument " << (*i)->getInstrument() << std::endl; + std::cout << "pitch: " << (int)(*i)->getPitch() << ", velocity " << (int)(*i)->getVelocity() << ", duration " << (*i)->getDuration() << std::endl; +#endif + + snd_seq_ev_set_subs(&event); + + // Set source according to port for device + // + int src; + + if (isControllerOut) { + src = m_controllerPort; + } else { + src = getOutputPortForMappedInstrument((*i)->getInstrument()); + } + + if (src < 0) continue; + snd_seq_ev_set_source(&event, src); + + snd_seq_ev_schedule_real(&event, m_queue, 0, &time); + + } else { + event.time.time = time; + } + + instrument = getMappedInstrument((*i)->getInstrument()); + + // set the stop time for Note Off + // + outputStopTime = outputTime + (*i)->getDuration() + - RealTime(0, 1); // notch it back 1nsec just to ensure + // correct ordering against any other + // note-ons at the same nominal time + bool needNoteOff = false; + + if (isControllerOut) { + channel = (*i)->getRecordedChannel(); +#ifdef DEBUG_ALSA + + std::cerr << "processMidiOut() - Event of type " << (int)((*i)->getType()) << " (data1 " << (int)(*i)->getData1() << ", data2 " << (int)(*i)->getData2() << ") for external controller channel " << (int)channel << std::endl; +#endif + + } else if (instrument != 0) { + channel = instrument->getChannel(); + } else { +#ifdef DEBUG_ALSA + std::cerr << "processMidiOut() - No instrument for event of type " + << (int)(*i)->getType() << " at " << (*i)->getEventTime() + << std::endl; +#endif + + channel = 0; + } + + switch ((*i)->getType()) { + + case MappedEvent::MidiNoteOneShot: + { + snd_seq_ev_set_noteon(&event, + channel, + (*i)->getPitch(), + (*i)->getVelocity()); + needNoteOff = true; + + if (!isSoftSynth && getSequencerDataBlock()) { + LevelInfo info; + info.level = (*i)->getVelocity(); + info.levelRight = 0; + getSequencerDataBlock()->setInstrumentLevel + ((*i)->getInstrument(), info); + } + + weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument()); + } + break; + + case MappedEvent::MidiNote: + // We always use plain NOTE ON here, not ALSA + // time+duration notes, because we have our own NOTE + // OFF stack (which will be augmented at the bottom of + // this function) and we want to ensure it gets used + // for the purposes of e.g. soft synths + // + if ((*i)->getVelocity() > 0) { + snd_seq_ev_set_noteon(&event, + channel, + (*i)->getPitch(), + (*i)->getVelocity()); + + if (!isSoftSynth && getSequencerDataBlock()) { + LevelInfo info; + info.level = (*i)->getVelocity(); + info.levelRight = 0; + getSequencerDataBlock()->setInstrumentLevel + ((*i)->getInstrument(), info); + } + + weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument()); + } else { + snd_seq_ev_set_noteoff(&event, + channel, + (*i)->getPitch(), + (*i)->getVelocity()); + } + + break; + + case MappedEvent::MidiProgramChange: + snd_seq_ev_set_pgmchange(&event, + channel, + (*i)->getData1()); + break; + + case MappedEvent::MidiKeyPressure: + snd_seq_ev_set_keypress(&event, + channel, + (*i)->getData1(), + (*i)->getData2()); + break; + + case MappedEvent::MidiChannelPressure: + snd_seq_ev_set_chanpress(&event, + channel, + (*i)->getData1()); + break; + + case MappedEvent::MidiPitchBend: { + int d1 = (int)((*i)->getData1()); + int d2 = (int)((*i)->getData2()); + int value = ((d1 << 7) | d2) - 8192; + + // keep within -8192 to +8192 + // + // if (value & 0x4000) + // value -= 0x8000; + + snd_seq_ev_set_pitchbend(&event, + channel, + value); + } + break; + + case MappedEvent::MidiSystemMessage: { + switch ((*i)->getData1()) { + case MIDI_SYSTEM_EXCLUSIVE: { + char out[2]; + sprintf(out, "%c", MIDI_SYSTEM_EXCLUSIVE); + std::string data = out; + + data += DataBlockRepository::getDataBlockForEvent((*i)); + + sprintf(out, "%c", MIDI_END_OF_EXCLUSIVE); + data += out; + + snd_seq_ev_set_sysex(&event, + data.length(), + (char*)(data.c_str())); + } + break; + + case MIDI_TIMING_CLOCK: { + RealTime rt = + RealTime(time.tv_sec, time.tv_nsec); + + /* + std::cerr << "AlsaDriver::processMidiOut - " + << "send clock @ " << rt << std::endl; + */ + + sendSystemQueued(SND_SEQ_EVENT_CLOCK, "", rt); + + continue; + + } + break; + + default: + std::cerr << "AlsaDriver::processMidiOut - " + << "unrecognised system message" + << std::endl; + break; + } + } + break; + + case MappedEvent::MidiController: + snd_seq_ev_set_controller(&event, + channel, + (*i)->getData1(), + (*i)->getData2()); + break; + + case MappedEvent::Audio: + case MappedEvent::AudioCancel: + case MappedEvent::AudioLevel: + case MappedEvent::AudioStopped: + case MappedEvent::SystemUpdateInstruments: + case MappedEvent::SystemJackTransport: //??? + case MappedEvent::SystemMMCTransport: + case MappedEvent::SystemMIDIClock: + case MappedEvent::SystemMIDISyncAuto: + break; + + default: + case MappedEvent::InvalidMappedEvent: +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::processMidiOut - " + << "skipping unrecognised or invalid MappedEvent type" + << std::endl; +#endif + + continue; + } + + if (isSoftSynth) { + + processSoftSynthEventOut((*i)->getInstrument(), &event, now); + + } else { + checkAlsaError(snd_seq_event_output(m_midiHandle, &event), + "processMidiOut(): output queued"); + + if (now) { + if (m_queueRunning && !m_playing) { + // restart queue +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut: restarting queue after now-event" << std::endl; +#endif + + checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue"); + } + checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining"); + } + } + + // Add note to note off stack + // + if (needNoteOff) { + NoteOffEvent *noteOffEvent = + new NoteOffEvent(outputStopTime, // already calculated + (*i)->getPitch(), + channel, + (*i)->getInstrument()); + +#ifdef DEBUG_ALSA + + std::cerr << "Adding NOTE OFF at " << outputStopTime + << std::endl; +#endif + + m_noteOffQueue.insert(noteOffEvent); + } + } + + processNotesOff(sliceEnd - m_playStartPosition + m_alsaPlayStartTime, now); + + if (getMTCStatus() == TRANSPORT_MASTER) { + insertMTCQFrames(sliceStart, sliceEnd); + } + + if (m_queueRunning) { + + if (now && !m_playing) { + // just to be sure +#ifdef DEBUG_PROCESS_MIDI_OUT + std::cerr << "processMidiOut: restarting queue after all now-events" << std::endl; +#endif + + checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue"); + } + +#ifdef DEBUG_PROCESS_MIDI_OUT + // std::cerr << "processMidiOut: m_queueRunning " << m_queueRunning + // << ", now " << now << std::endl; +#endif + checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining"); + } +} + +void +AlsaDriver::processSoftSynthEventOut(InstrumentId id, const snd_seq_event_t *ev, bool now) +{ +#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT + std::cerr << "AlsaDriver::processSoftSynthEventOut: instrument " << id << ", now " << now << std::endl; +#endif + +#ifdef HAVE_LIBJACK + + if (!m_jackDriver) + return ; + RunnablePluginInstance *synthPlugin = m_jackDriver->getSynthPlugin(id); + + if (synthPlugin) { + + RealTime t(ev->time.time.tv_sec, ev->time.time.tv_nsec); + + if (now) + t = RealTime::zeroTime; + else + t = t + m_playStartPosition - m_alsaPlayStartTime; + +#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT + + std::cerr << "AlsaDriver::processSoftSynthEventOut: event time " << t << std::endl; +#endif + + synthPlugin->sendEvent(t, ev); + + if (now) { +#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT + std::cerr << "AlsaDriver::processSoftSynthEventOut: setting haveAsyncAudioEvent" << std::endl; +#endif + + m_jackDriver->setHaveAsyncAudioEvent(); + } + } +#endif +} + +void +AlsaDriver::startClocks() +{ + int result; + +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::startClocks" << std::endl; +#endif + + if (m_needJackStart) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::startClocks: Need JACK start (m_playing = " << m_playing << ")" << std::endl; +#endif + + } + +#ifdef HAVE_LIBJACK + + // New JACK transport scheme: The initialisePlayback, + // resetPlayback and stopPlayback methods set m_needJackStart, and + // then this method checks it and calls the appropriate JACK + // transport start or relocate method, which calls back on + // startClocksApproved when ready. (Previously this method always + // called the JACK transport start method, so we couldn't handle + // moving the pointer when not playing, and we had to stop the + // transport explicitly from resetPlayback when repositioning + // during playback.) + + if (m_jackDriver) { + + // Don't need any locks on this, except for those that the + // driver methods take and hold for themselves + + if (m_needJackStart != NeedNoJackStart) { + if (m_needJackStart == NeedJackStart || + m_playing) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::startClocks: playing, prebuffer audio" << std::endl; +#endif + + m_jackDriver->prebufferAudio(); + } else { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::startClocks: prepare audio only" << std::endl; +#endif + + m_jackDriver->prepareAudio(); + } + bool rv; + if (m_needJackStart == NeedJackReposition) { + rv = m_jackDriver->relocateTransport(); + } else { + rv = m_jackDriver->startTransport(); + if (!rv) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::startClocks: Waiting for startClocksApproved" << std::endl; +#endif + // need to wait for transport sync + _debug_jack_frame_count = m_jackDriver->getFramesProcessed(); + return ; + } + } + } + } +#endif + + // Restart the timer + if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) { + std::cerr << "AlsaDriver::startClocks - couldn't start queue - " + << snd_strerror(result) + << std::endl; + reportFailure(MappedEvent::FailureALSACallFailed); + } + +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::startClocks: started clocks" << std::endl; +#endif + + m_queueRunning = true; + +#ifdef HAVE_LIBJACK + + if (m_jackDriver) { + _debug_jack_frame_count = m_jackDriver->getFramesProcessed(); + } +#endif + + // process pending MIDI events + checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocks(): draining"); +} + +void +AlsaDriver::startClocksApproved() +{ +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::startClocks: startClocksApproved" << std::endl; +#endif + + //!!! + m_needJackStart = NeedNoJackStart; + startClocks(); + return ; + + int result; + + // Restart the timer + if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) { + std::cerr << "AlsaDriver::startClocks - couldn't start queue - " + << snd_strerror(result) + << std::endl; + reportFailure(MappedEvent::FailureALSACallFailed); + } + + m_queueRunning = true; + + // process pending MIDI events + checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocksApproved(): draining"); +} + +void +AlsaDriver::stopClocks() +{ +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::stopClocks" << std::endl; +#endif + + if (checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "stopClocks(): stopping queue") < 0) { + reportFailure(MappedEvent::FailureALSACallFailed); + } + checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to stop queue"); + + m_queueRunning = false; + + // We used to call m_jackDriver->stop() from here, but we no + // longer do -- it's now called from stopPlayback() so as to + // handle repositioning during playback (when stopClocks is + // necessary but stopPlayback and m_jackDriver->stop() are not). + + snd_seq_event_t event; + snd_seq_ev_clear(&event); + snd_seq_real_time_t z = { 0, 0 }; + snd_seq_ev_set_queue_pos_real(&event, m_queue, &z); + snd_seq_ev_set_direct(&event); + checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME, + 0, &event), "stopClocks(): setting zpos to queue"); + // process that + checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to zpos queue"); + +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::stopClocks: ALSA time now is " << getAlsaTime() << std::endl; +#endif + + m_alsaPlayStartTime = RealTime::zeroTime; +} + + +void +AlsaDriver::processEventsOut(const MappedComposition &mC) +{ + processEventsOut(mC, RealTime::zeroTime, RealTime::zeroTime); +} + +void +AlsaDriver::processEventsOut(const MappedComposition &mC, + const RealTime &sliceStart, + const RealTime &sliceEnd) +{ + // special case for unqueued events + bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime); + + if (m_startPlayback) { + m_startPlayback = false; + // This only records whether we're playing in principle, + // not whether the clocks are actually ticking. Contrariwise, + // areClocksRunning tells us whether the clocks are ticking + // but not whether we're actually playing (the clocks go even + // when we're not). Check both if you want to know whether + // we're really rolling. + m_playing = true; + + if (getMTCStatus() == TRANSPORT_SLAVE) { + tweakSkewForMTC(0); + } + } + + AudioFile *audioFile = 0; + bool haveNewAudio = false; + + // insert audio events if we find them + for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) { +#ifdef HAVE_LIBJACK + + // Play an audio file + // + if ((*i)->getType() == MappedEvent::Audio) { + if (!m_jackDriver) + continue; + + // This is used for handling asynchronous + // (i.e. unexpected) audio events only + + if ((*i)->getEventTime() > RealTime( -120, 0)) { + // Not an asynchronous event + continue; + } + + // Check for existence of file - if the sequencer has died + // and been restarted then we're not always loaded up with + // the audio file references we should have. In the future + // we could make this just get the gui to reload our files + // when (or before) this fails. + // + audioFile = getAudioFile((*i)->getAudioID()); + + if (audioFile) { + MappedAudioFader *fader = + dynamic_cast + (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; + } + + QString fileName = (*audioFileNames)[audioCount]; + + if (id >= AudioInstrumentBase && + id < MidiInstrumentBase) { + + bool good = false; + +#ifdef DEBUG_ALSA + + std::cerr << "AlsaDriver::record: Requesting new record file \"" << fileName << "\" for instrument " << id << std::endl; +#endif + +#ifdef HAVE_LIBJACK + + if (m_jackDriver && + m_jackDriver->openRecordFile(id, fileName.data())) { + good = true; + } +#endif + + if (!good) { + m_recordStatus = RECORD_OFF; + std::cerr << "AlsaDriver::record: No JACK driver, or JACK driver failed to prepare for recording audio" << std::endl; + return false; + } + + ++audioCount; + } + } + } + } else + if (recordStatus == RECORD_OFF) { + m_recordStatus = RECORD_OFF; + } +#ifdef DEBUG_ALSA + else { + std::cerr << "AlsaDriver::record - unsupported recording mode" + << std::endl; + } +#endif + + return true; +} + +ClientPortPair +AlsaDriver::getFirstDestination(bool duplex) +{ + ClientPortPair destPair( -1, -1); + AlsaPortList::iterator it; + + for (it = m_alsaPorts.begin(); it != m_alsaPorts.end(); ++it) { + destPair.first = (*it)->m_client; + destPair.second = (*it)->m_port; + + // If duplex port is required then choose first one + // + if (duplex) { + if ((*it)->m_direction == Duplex) + return destPair; + } else { + // If duplex port isn't required then choose first + // specifically non-duplex port (should be a synth) + // + if ((*it)->m_direction != Duplex) + return destPair; + } + } + + return destPair; +} + + +// Sort through the ALSA client/port pairs for the range that +// matches the one we're querying. If none matches then send +// back -1 for each. +// +ClientPortPair +AlsaDriver::getPairForMappedInstrument(InstrumentId id) +{ + MappedInstrument *instrument = getMappedInstrument(id); + if (instrument) { + DeviceId device = instrument->getDevice(); + DevicePortMap::iterator i = m_devicePortMap.find(device); + if (i != m_devicePortMap.end()) { + return i->second; + } + } +#ifdef DEBUG_ALSA + /* + else + { + cerr << "WARNING: AlsaDriver::getPairForMappedInstrument: couldn't find instrument for id " << id << ", falling through" << endl; + } + */ +#endif + + return ClientPortPair( -1, -1); +} + +int +AlsaDriver::getOutputPortForMappedInstrument(InstrumentId id) +{ + MappedInstrument *instrument = getMappedInstrument(id); + if (instrument) { + DeviceId device = instrument->getDevice(); + DeviceIntMap::iterator i = m_outputPorts.find(device); + if (i != m_outputPorts.end()) { + return i->second; + } +#ifdef DEBUG_ALSA + else { + cerr << "WARNING: AlsaDriver::getOutputPortForMappedInstrument: couldn't find output port for device for instrument " << id << ", falling through" << endl; + } +#endif + + } + + return -1; +} + +// Send a direct controller to the specified port/client +// +void +AlsaDriver::sendDeviceController(DeviceId device, + MidiByte controller, + MidiByte value) +{ + snd_seq_event_t event; + + snd_seq_ev_clear(&event); + + snd_seq_ev_set_subs(&event); + + DeviceIntMap::iterator dimi = m_outputPorts.find(device); + if (dimi == m_outputPorts.end()) + return ; + + snd_seq_ev_set_source(&event, dimi->second); + snd_seq_ev_set_direct(&event); + + for (int i = 0; i < 16; i++) { + snd_seq_ev_set_controller(&event, + i, + controller, + value); + snd_seq_event_output_direct(m_midiHandle, &event); + } + + // we probably don't need this: + checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendDeviceController(): draining"); +} + +void +AlsaDriver::processPending() +{ + if (!m_playing) { + processNotesOff(getAlsaTime(), true); + checkAlsaError(snd_seq_drain_output(m_midiHandle), "processPending(): draining"); + } + +#ifdef HAVE_LIBJACK + if (m_jackDriver) { + m_jackDriver->updateAudioData(); + } +#endif + + scavengePlugins(); + m_audioQueueScavenger.scavenge(); +} + +void +AlsaDriver::insertMappedEventForReturn(MappedEvent *mE) +{ + // Insert the event ready for return at the next opportunity. + // + m_returnComposition.insert(mE); +} + +// check for recording status on any ALSA Port +// +bool +AlsaDriver::isRecording(AlsaPortDescription *port) +{ + if (port->isReadable()) { + + snd_seq_query_subscribe_t *qSubs; + snd_seq_addr_t rg_addr, sender_addr; + snd_seq_query_subscribe_alloca(&qSubs); + + rg_addr.client = m_client; + rg_addr.port = m_inputPort; + + snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE); + snd_seq_query_subscribe_set_index(qSubs, 0); + snd_seq_query_subscribe_set_root(qSubs, &rg_addr); + + while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) { + sender_addr = *snd_seq_query_subscribe_get_addr(qSubs); + if (sender_addr.client == port->m_client && + sender_addr.port == port->m_port) + return true; + + snd_seq_query_subscribe_set_index(qSubs, + snd_seq_query_subscribe_get_index(qSubs) + 1); + } + } + return false; +} + +bool +AlsaDriver::checkForNewClients() +{ + Audit audit; + bool madeChange = false; + + if (!m_portCheckNeeded) + return false; + + AlsaPortList newPorts; + generatePortList(&newPorts); // updates m_alsaPorts, returns new ports as well + + // If any devices have connections that no longer exist, + // clear those connections and stick them in the suspended + // port map in case they come back online later. + + for (MappedDeviceList::iterator i = m_devices.begin(); + i != m_devices.end(); ++i) { + + ClientPortPair pair(m_devicePortMap[(*i)->getId()]); + + bool found = false; + for (AlsaPortList::iterator j = m_alsaPorts.begin(); + j != m_alsaPorts.end(); ++j) { + if ((*j)->m_client == pair.first && + (*j)->m_port == pair.second) { + if ((*i)->getDirection() == MidiDevice::Record) { + bool recState = isRecording(*j); + if (recState != (*i)->isRecording()) { + madeChange = true; + (*i)->setRecording(recState); + } + } else { + (*i)->setRecording(false); + } + found = true; + break; + } + } + + if (!found) { + m_suspendedPortMap[pair] = (*i)->getId(); + m_devicePortMap[(*i)->getId()] = ClientPortPair( -1, -1); + setConnectionToDevice(**i, ""); + (*i)->setRecording(false); + madeChange = true; + } + } + + // If we've increased the number of connections, we need + // to assign the new connections to existing devices that + // have none, where possible, and create new devices for + // any left over. + + if (newPorts.size() > 0) { + + audit << "New ports:" << std::endl; + + for (AlsaPortList::iterator i = newPorts.begin(); + i != newPorts.end(); ++i) { + + if ((*i)->m_client == m_client) { + audit << "(Ignoring own port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl; + continue; + } else if ((*i)->m_client == 0) { + audit << "(Ignoring system port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl; + continue; + } + + audit << (*i)->m_name << std::endl; + + QString portName = (*i)->m_name.c_str(); + ClientPortPair portPair = ClientPortPair((*i)->m_client, + (*i)->m_port); + + if (m_suspendedPortMap.find(portPair) != m_suspendedPortMap.end()) { + + DeviceId id = m_suspendedPortMap[portPair]; + + audit << "(Reusing suspended device " << id << ")" << std::endl; + + for (MappedDeviceList::iterator j = m_devices.begin(); + j != m_devices.end(); ++j) { + if ((*j)->getId() == id) { + setConnectionToDevice(**j, portName); + } + } + + m_suspendedPortMap.erase(m_suspendedPortMap.find(portPair)); + m_devicePortMap[id] = portPair; + madeChange = true; + continue; + } + + bool needPlayDevice = true, needRecordDevice = true; + + if ((*i)->isReadable()) { + for (MappedDeviceList::iterator j = m_devices.begin(); + j != m_devices.end(); ++j) { + if ((*j)->getType() == Device::Midi && + (*j)->getConnection() == "" && + (*j)->getDirection() == MidiDevice::Record) { + audit << "(Reusing record device " << (*j)->getId() + << ")" << std::endl; + m_devicePortMap[(*j)->getId()] = portPair; + setConnectionToDevice(**j, portName); + needRecordDevice = false; + madeChange = true; + break; + } + } + } else { + needRecordDevice = false; + } + + if ((*i)->isWriteable()) { + for (MappedDeviceList::iterator j = m_devices.begin(); + j != m_devices.end(); ++j) { + if ((*j)->getType() == Device::Midi && + (*j)->getConnection() == "" && + (*j)->getDirection() == MidiDevice::Play) { + audit << "(Reusing play device " << (*j)->getId() + << ")" << std::endl; + m_devicePortMap[(*j)->getId()] = portPair; + setConnectionToDevice(**j, portName); + needPlayDevice = false; + madeChange = true; + break; + } + } + } else { + needPlayDevice = false; + } + + if (needRecordDevice) { + MappedDevice *device = createMidiDevice(*i, MidiDevice::Record); + if (!device) { +#ifdef DEBUG_ALSA + std::cerr << "WARNING: Failed to create record device" << std::endl; +#else + + ; +#endif + + } else { + audit << "(Created new record device " << device->getId() << ")" << std::endl; + addInstrumentsForDevice(device); + m_devices.push_back(device); + madeChange = true; + } + } + + if (needPlayDevice) { + MappedDevice *device = createMidiDevice(*i, MidiDevice::Play); + if (!device) { +#ifdef DEBUG_ALSA + std::cerr << "WARNING: Failed to create play device" << std::endl; +#else + + ; +#endif + + } else { + audit << "(Created new play device " << device->getId() << ")" << std::endl; + addInstrumentsForDevice(device); + m_devices.push_back(device); + madeChange = true; + } + } + } + } + + // If one of our ports is connected to a single other port and + // it isn't the one we thought, we should update our connection + + for (MappedDeviceList::iterator i = m_devices.begin(); + i != m_devices.end(); ++i) { + + DevicePortMap::iterator j = m_devicePortMap.find((*i)->getId()); + + snd_seq_addr_t addr; + addr.client = m_client; + + DeviceIntMap::iterator ii = m_outputPorts.find((*i)->getId()); + if (ii == m_outputPorts.end()) + continue; + addr.port = ii->second; + + snd_seq_query_subscribe_t *subs; + snd_seq_query_subscribe_alloca(&subs); + snd_seq_query_subscribe_set_root(subs, &addr); + snd_seq_query_subscribe_set_index(subs, 0); + + bool haveOurs = false; + int others = 0; + ClientPortPair firstOther; + + while (!snd_seq_query_port_subscribers(m_midiHandle, subs)) { + + const snd_seq_addr_t *otherEnd = + snd_seq_query_subscribe_get_addr(subs); + + if (!otherEnd) + continue; + + if (j != m_devicePortMap.end() && + otherEnd->client == j->second.first && + otherEnd->port == j->second.second) { + haveOurs = true; + } else { + ++others; + firstOther = ClientPortPair(otherEnd->client, otherEnd->port); + } + + snd_seq_query_subscribe_set_index + (subs, snd_seq_query_subscribe_get_index(subs) + 1); + } + + if (haveOurs) { // leave our own connection alone, and stop worrying + continue; + + } else { + if (others == 0) { + if (j != m_devicePortMap.end()) { + j->second = ClientPortPair( -1, -1); + setConnectionToDevice(**i, ""); + madeChange = true; + } + } else { + for (AlsaPortList::iterator k = m_alsaPorts.begin(); + k != m_alsaPorts.end(); ++k) { + if ((*k)->m_client == firstOther.first && + (*k)->m_port == firstOther.second) { + m_devicePortMap[(*i)->getId()] = firstOther; + setConnectionToDevice(**i, (*k)->m_name.c_str(), + firstOther); + madeChange = true; + break; + } + } + } + } + } + + if (madeChange) { + MappedEvent *mE = + new MappedEvent(0, MappedEvent::SystemUpdateInstruments, + 0, 0); + // send completion event + insertMappedEventForReturn(mE); + } + + m_portCheckNeeded = false; + + return true; +} + + +// From a DeviceId get a client/port pair for connecting as the +// MIDI record device. +// +void +AlsaDriver::setRecordDevice(DeviceId id, bool connectAction) +{ + Audit audit; + + // Locate a suitable port + // + if (m_devicePortMap.find(id) == m_devicePortMap.end()) { +#ifdef DEBUG_ALSA + audit << "AlsaDriver::setRecordDevice - " + << "couldn't match device id (" << id << ") to ALSA port" + << std::endl; +#endif + + return ; + } + + ClientPortPair pair = m_devicePortMap[id]; + + snd_seq_addr_t sender, dest; + sender.client = pair.first; + sender.port = pair.second; + + for (MappedDeviceList::iterator i = m_devices.begin(); + i != m_devices.end(); ++i) { + if ((*i)->getId() == id) { + if ((*i)->getDirection() == MidiDevice::Record) { + if ((*i)->isRecording() && connectAction) { +#ifdef DEBUG_ALSA + audit << "AlsaDriver::setRecordDevice - " + << "attempting to subscribe (" << id + << ") already subscribed" << std::endl; +#endif + + return ; + } + if (!(*i)->isRecording() && !connectAction) { +#ifdef DEBUG_ALSA + audit << "AlsaDriver::setRecordDevice - " + << "attempting to unsubscribe (" << id + << ") already unsubscribed" << std::endl; +#endif + + return ; + } + } else { +#ifdef DEBUG_ALSA + audit << "AlsaDriver::setRecordDevice - " + << "attempting to set play device (" << id + << ") to record device" << std::endl; +#endif + + return ; + } + break; + } + } + + snd_seq_port_subscribe_t *subs; + snd_seq_port_subscribe_alloca(&subs); + + dest.client = m_client; + dest.port = m_inputPort; + + // Set destinations and senders + // + snd_seq_port_subscribe_set_sender(subs, &sender); + snd_seq_port_subscribe_set_dest(subs, &dest); + + // subscribe or unsubscribe the port + // + if (connectAction) { + if (checkAlsaError(snd_seq_subscribe_port(m_midiHandle, subs), + "setRecordDevice - failed subscription of input port") < 0) { + // Not the end of the world if this fails but we + // have to flag it internally. + // + audit << "AlsaDriver::setRecordDevice - " + << int(sender.client) << ":" << int(sender.port) + << " failed to subscribe device " + << id << " as record port" << std::endl; + } else { + m_midiInputPortConnected = true; + audit << "AlsaDriver::setRecordDevice - " + << "successfully subscribed device " + << id << " as record port" << std::endl; + } + } else { + if (checkAlsaError(snd_seq_unsubscribe_port(m_midiHandle, subs), + "setRecordDevice - failed to unsubscribe a device") == 0) + audit << "AlsaDriver::setRecordDevice - " + << "successfully unsubscribed device " + << id << " as record port" << std::endl; + + } +} + +// Clear any record device connections +// +void +AlsaDriver::unsetRecordDevices() +{ + snd_seq_addr_t dest; + dest.client = m_client; + dest.port = m_inputPort; + + snd_seq_query_subscribe_t *qSubs; + snd_seq_addr_t tmp_addr; + snd_seq_query_subscribe_alloca(&qSubs); + + tmp_addr.client = m_client; + tmp_addr.port = m_inputPort; + + // Unsubsribe any existing connections + // + snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE); + snd_seq_query_subscribe_set_index(qSubs, 0); + snd_seq_query_subscribe_set_root(qSubs, &tmp_addr); + + while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) { + tmp_addr = *snd_seq_query_subscribe_get_addr(qSubs); + + snd_seq_port_subscribe_t *dSubs; + snd_seq_port_subscribe_alloca(&dSubs); + + snd_seq_addr_t dSender; + dSender.client = tmp_addr.client; + dSender.port = tmp_addr.port; + + snd_seq_port_subscribe_set_sender(dSubs, &dSender); + snd_seq_port_subscribe_set_dest(dSubs, &dest); + + int error = snd_seq_unsubscribe_port(m_midiHandle, dSubs); + + if (error < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::unsetRecordDevices - " + << "can't unsubscribe record port" << std::endl; +#endif + + } + + snd_seq_query_subscribe_set_index(qSubs, + snd_seq_query_subscribe_get_index(qSubs) + 1); + } +} + +void +AlsaDriver::sendMMC(MidiByte deviceArg, + MidiByte instruction, + bool isCommand, + const std::string &data) +{ + snd_seq_event_t event; + + snd_seq_ev_clear(&event); + snd_seq_ev_set_source(&event, m_syncOutputPort); + snd_seq_ev_set_subs(&event); + + unsigned char dataArr[10] = + { MIDI_SYSTEM_EXCLUSIVE, + MIDI_SYSEX_RT, deviceArg, + (isCommand ? MIDI_SYSEX_RT_COMMAND : MIDI_SYSEX_RT_RESPONSE), + instruction }; + + std::string dataString = std::string((const char *)dataArr) + + data + (char)MIDI_END_OF_EXCLUSIVE; + + snd_seq_ev_set_sysex(&event, dataString.length(), + (char *)dataString.c_str()); + + event.queue = SND_SEQ_QUEUE_DIRECT; + + checkAlsaError(snd_seq_event_output_direct(m_midiHandle, &event), + "sendMMC event send"); + + if (m_queueRunning) { + checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendMMC drain"); + } +} + +// Send a system real-time message from the sync output port +// +void +AlsaDriver::sendSystemDirect(MidiByte command, int *args) +{ + snd_seq_event_t event; + + snd_seq_ev_clear(&event); + snd_seq_ev_set_source(&event, m_syncOutputPort); + snd_seq_ev_set_subs(&event); + + event.queue = SND_SEQ_QUEUE_DIRECT; + + // set the command + event.type = command; + + // set args if we have them + if (args) { + event.data.control.value = *args; + } + + int error = snd_seq_event_output_direct(m_midiHandle, &event); + + if (error < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::sendSystemDirect - " + << "can't send event (" << int(command) << ")" + << std::endl; +#endif + + } + + // checkAlsaError(snd_seq_drain_output(m_midiHandle), + // "sendSystemDirect(): draining"); +} + + +void +AlsaDriver::sendSystemQueued(MidiByte command, + const std::string &args, + const RealTime &time) +{ + snd_seq_event_t event; + + snd_seq_ev_clear(&event); + snd_seq_ev_set_source(&event, m_syncOutputPort); + snd_seq_ev_set_subs(&event); + + snd_seq_real_time_t sendTime = { time.sec, time.nsec }; + + // Schedule the command + // + event.type = command; + + snd_seq_ev_schedule_real(&event, m_queue, 0, &sendTime); + + // set args if we have them + switch (args.length()) { + case 1: + event.data.control.value = args[0]; + break; + + case 2: + event.data.control.value = int(args[0]) | (int(args[1]) << 7); + break; + + default: // do nothing + break; + } + + int error = snd_seq_event_output(m_midiHandle, &event); + + if (error < 0) { +#ifdef DEBUG_ALSA + std::cerr << "AlsaDriver::sendSystemQueued - " + << "can't send event (" << int(command) << ")" + << " - error = (" << error << ")" + << std::endl; +#endif + + } + + // if (m_queueRunning) { + // checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendSystemQueued(): draining"); + // } +} + + +void +AlsaDriver::claimUnwantedPlugin(void *plugin) +{ + m_pluginScavenger.claim((RunnablePluginInstance *)plugin); +} + + +void +AlsaDriver::scavengePlugins() +{ + m_pluginScavenger.scavenge(); +} + + +QString +AlsaDriver::getStatusLog() +{ + return QString::fromUtf8(Audit::getAudit().c_str()); +} + + +void +AlsaDriver::sleep(const RealTime &rt) +{ + int npfd = snd_seq_poll_descriptors_count(m_midiHandle, POLLIN); + struct pollfd *pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); + snd_seq_poll_descriptors(m_midiHandle, pfd, npfd, POLLIN); + poll(pfd, npfd, rt.sec * 1000 + rt.msec()); +} + +void +AlsaDriver::runTasks() +{ +#ifdef HAVE_LIBJACK + if (m_jackDriver) { + if (!m_jackDriver->isOK()) { + m_jackDriver->restoreIfRestorable(); + } + } + + if (m_doTimerChecks && m_timerRatioCalculated) { + + double ratio = m_timerRatio; + m_timerRatioCalculated = false; + + snd_seq_queue_tempo_t *q_ptr; + snd_seq_queue_tempo_alloca(&q_ptr); + + snd_seq_get_queue_tempo(m_midiHandle, m_queue, q_ptr); + + unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr); +#ifdef DEBUG_ALSA + + unsigned int t_base = snd_seq_queue_tempo_get_skew_base(q_ptr); + if (!m_playing) { + std::cerr << "Skew: " << t_skew << "/" << t_base; + } +#endif + + unsigned int newSkew = t_skew + (unsigned int)(t_skew * ratio); + + if (newSkew != t_skew) { +#ifdef DEBUG_ALSA + if (!m_playing) { + std::cerr << " changed to " << newSkew << endl; + } +#endif + snd_seq_queue_tempo_set_skew(q_ptr, newSkew); + snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr); + } else { +#ifdef DEBUG_ALSA + if (!m_playing) { + std::cerr << endl; + } +#endif + + } + + m_firstTimerCheck = true; + } + +#endif +} + +void +AlsaDriver::reportFailure(MappedEvent::FailureCode code) +{ + //#define REPORT_XRUNS 1 +#ifndef REPORT_XRUNS + if (code == MappedEvent::FailureXRuns || + code == MappedEvent::FailureDiscUnderrun || + code == MappedEvent::FailureBussMixUnderrun || + code == MappedEvent::FailureMixUnderrun) { + return ; + } +#endif + + // Ignore consecutive duplicates + if (_failureReportWriteIndex > 0 && + _failureReportWriteIndex != _failureReportReadIndex) { + if (code == _failureReports[_failureReportWriteIndex - 1]) + return ; + } + + _failureReports[_failureReportWriteIndex] = code; + _failureReportWriteIndex = + (_failureReportWriteIndex + 1) % FAILURE_REPORT_COUNT; +} + +std::string +AlsaDriver::getAlsaModuleVersionString() +{ + FILE *v = fopen("/proc/asound/version", "r"); + + // Examples: + // Advanced Linux Sound Architecture Driver Version 1.0.14rc3. + // Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2008 UTC). + + if (v) { + char buf[256]; + fgets(buf, 256, v); + fclose(v); + + std::string vs(buf); + std::string::size_type sp = vs.find_first_of('.'); + if (sp > 0 && sp != std::string::npos) { + while (sp > 0 && isdigit(vs[sp-1])) --sp; + vs = vs.substr(sp); + if (vs.length() > 0 && vs[vs.length()-1] == '\n') { + vs = vs.substr(0, vs.length()-1); + } + if (vs.length() > 0 && vs[vs.length()-1] == '.') { + vs = vs.substr(0, vs.length()-1); + } + return vs; + } + } + + return "(unknown)"; +} + +std::string +AlsaDriver::getKernelVersionString() +{ + FILE *v = fopen("/proc/version", "r"); + + if (v) { + char buf[256]; + fgets(buf, 256, v); + fclose(v); + + std::string vs(buf); + std::string key(" version "); + std::string::size_type sp = vs.find(key); + if (sp != std::string::npos) { + vs = vs.substr(sp + key.length()); + sp = vs.find(' '); + if (sp != std::string::npos) { + vs = vs.substr(0, sp); + } + if (vs.length() > 0 && vs[vs.length()-1] == '\n') { + vs = vs.substr(0, vs.length()-1); + } + return vs; + } + } + + return "(unknown)"; +} + +void +AlsaDriver::extractVersion(std::string v, int &major, int &minor, int &subminor, std::string &suffix) +{ + major = minor = subminor = 0; + suffix = ""; + if (v == "(unknown)") return; + + std::string::size_type sp, pp; + + sp = v.find('.'); + if (sp == std::string::npos) goto done; + major = atoi(v.substr(0, sp).c_str()); + pp = sp + 1; + + sp = v.find('.', pp); + if (sp == std::string::npos) goto done; + minor = atoi(v.substr(pp, sp - pp).c_str()); + pp = sp + 1; + + while (++sp < v.length() && (::isdigit(v[sp]) || v[sp] == '-')); + subminor = atoi(v.substr(pp, sp - pp).c_str()); + + if (sp >= v.length()) goto done; + suffix = v.substr(sp); + +done: + std::cerr << "extractVersion: major = " << major << ", minor = " << minor << ", subminor = " << subminor << ", suffix = \"" << suffix << "\"" << std::endl; +} + +bool +AlsaDriver::versionIsAtLeast(std::string v, int major, int minor, int subminor) +{ + int actualMajor, actualMinor, actualSubminor; + std::string actualSuffix; + + extractVersion(v, actualMajor, actualMinor, actualSubminor, actualSuffix); + + bool ok = false; + + if (actualMajor > major) { + ok = true; + } else if (actualMajor == major) { + if (actualMinor > minor) { + ok = true; + } else if (actualMinor == minor) { + if (actualSubminor > subminor) { + ok = true; + } else if (actualSubminor == subminor) { + if (strncmp(actualSuffix.c_str(), "rc", 2) && + strncmp(actualSuffix.c_str(), "pre", 3)) { + ok = true; + } + } + } + } + + std::cerr << "AlsaDriver::versionIsAtLeast: is version " << v << " at least " << major << "." << minor << "." << subminor << "? " << (ok ? "yes" : "no") << std::endl; + return ok; +} + +} + + +#endif // HAVE_ALSA diff --git a/src/sound/AlsaDriver.h b/src/sound/AlsaDriver.h new file mode 100644 index 0000000..e80e30f --- /dev/null +++ b/src/sound/AlsaDriver.h @@ -0,0 +1,561 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- + +/* + 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. +*/ + +// Specialisation of SoundDriver to support ALSA (http://www.alsa-project.org) +// +// +#ifndef _ALSADRIVER_H_ +#define _ALSADRIVER_H_ + +#include +#include +#include + +#ifdef HAVE_ALSA + +#include // ALSA + +#include "SoundDriver.h" +#include "Instrument.h" +#include "Device.h" +#include "AlsaPort.h" +#include "Scavenger.h" +#include "RunnablePluginInstance.h" + +#ifdef HAVE_LIBJACK +#include "JackDriver.h" +#endif + +namespace Rosegarden +{ + +class AlsaDriver : public SoundDriver +{ +public: + AlsaDriver(MappedStudio *studio); + virtual ~AlsaDriver(); + + // shutdown everything that's currently open + void shutdown(); + + virtual bool initialise(); + virtual void initialisePlayback(const RealTime &position); + virtual void stopPlayback(); + virtual void punchOut(); + virtual void resetPlayback(const RealTime &oldPosition, const RealTime &position); + virtual void allNotesOff(); + virtual void processNotesOff(const RealTime &time, bool now, bool everything = false); + + virtual RealTime getSequencerTime(); + + virtual MappedComposition *getMappedComposition(); + + virtual bool record(RecordStatus recordStatus, + const std::vector *armedInstruments = 0, + const std::vector *audioFileNames = 0); + + virtual void startClocks(); + virtual void startClocksApproved(); // called by JACK driver in sync mode + virtual void stopClocks(); + virtual bool areClocksRunning() const { return m_queueRunning; } + + virtual void processEventsOut(const MappedComposition &mC); + virtual void processEventsOut(const MappedComposition &mC, + const RealTime &sliceStart, + const RealTime &sliceEnd); + + // Return the sample rate + // + virtual unsigned int getSampleRate() const { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getSampleRate(); + else return 0; +#else + return 0; +#endif + } + + // Define here to catch this being reset + // + virtual void setMIDIClockInterval(RealTime interval); + + // initialise subsystems + // + bool initialiseMidi(); + void initialiseAudio(); + + // Some stuff to help us debug this + // + void getSystemInfo(); + void showQueueStatus(int queue); + + // Process pending + // + virtual void processPending(); + + // We can return audio control signals to the gui using MappedEvents. + // Meter levels or audio file completions can go in here. + // + void insertMappedEventForReturn(MappedEvent *mE); + + + virtual RealTime getAudioPlayLatency() { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getAudioPlayLatency(); +#endif + return RealTime::zeroTime; + } + + virtual RealTime getAudioRecordLatency() { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getAudioRecordLatency(); +#endif + return RealTime::zeroTime; + } + + virtual RealTime getInstrumentPlayLatency(InstrumentId id) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getInstrumentPlayLatency(id); +#endif + return RealTime::zeroTime; + } + + virtual RealTime getMaximumPlayLatency() { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getMaximumPlayLatency(); +#endif + return RealTime::zeroTime; + } + + + // Plugin instance management + // + virtual void setPluginInstance(InstrumentId id, + QString identifier, + int position) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->setPluginInstance(id, identifier, position); +#endif + } + + virtual void removePluginInstance(InstrumentId id, int position) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->removePluginInstance(id, position); +#endif + } + + // Remove all plugin instances + // + virtual void removePluginInstances() { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->removePluginInstances(); +#endif + } + + virtual void setPluginInstancePortValue(InstrumentId id, + int position, + unsigned long portNumber, + float value) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->setPluginInstancePortValue(id, position, portNumber, value); +#endif + } + + virtual float getPluginInstancePortValue(InstrumentId id, + int position, + unsigned long portNumber) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getPluginInstancePortValue(id, position, portNumber); +#endif + return 0; + } + + virtual void setPluginInstanceBypass(InstrumentId id, + int position, + bool value) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->setPluginInstanceBypass(id, position, value); +#endif + } + + virtual QStringList getPluginInstancePrograms(InstrumentId id, + int position) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getPluginInstancePrograms(id, position); +#endif + return QStringList(); + } + + virtual QString getPluginInstanceProgram(InstrumentId id, + int position) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position); +#endif + return QString(); + } + + virtual QString getPluginInstanceProgram(InstrumentId id, + int position, + int bank, + int program) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position, bank, program); +#endif + return QString(); + } + + virtual unsigned long getPluginInstanceProgram(InstrumentId id, + int position, + QString name) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position, name); +#endif + return 0; + } + + virtual void setPluginInstanceProgram(InstrumentId id, + int position, + QString program) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->setPluginInstanceProgram(id, position, program); +#endif + } + + virtual QString configurePlugin(InstrumentId id, + int position, + QString key, + QString value) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) return m_jackDriver->configurePlugin(id, position, key, value); +#endif + return QString(); + } + + virtual void setAudioBussLevels(int bussId, + float dB, + float pan) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->setAudioBussLevels(bussId, dB, pan); +#endif + } + + virtual void setAudioInstrumentLevels(InstrumentId instrument, + float dB, + float pan) { +#ifdef HAVE_LIBJACK + if (m_jackDriver) m_jackDriver->setAudioInstrumentLevels(instrument, dB, pan); +#endif + } + + virtual void claimUnwantedPlugin(void *plugin); + virtual void scavengePlugins(); + + virtual bool checkForNewClients(); + + virtual void setLoop(const RealTime &loopStart, const RealTime &loopEnd); + + virtual void sleep(const RealTime &); + + // ----------------------- End of Virtuals ---------------------- + + // Create and send an MMC command + // + void sendMMC(MidiByte deviceId, + MidiByte instruction, + bool isCommand, + const std::string &data); + + // Check whether the given event is an MMC command we need to act on + // (and if so act on it) + // + bool testForMMCSysex(const snd_seq_event_t *event); + + // Create and enqueue a batch of MTC quarter-frame events + // + void insertMTCQFrames(RealTime sliceStart, RealTime sliceEnd); + + // Create and enqueue an MTC full-frame system exclusive event + // + void insertMTCFullFrame(RealTime time); + + // Parse and accept an incoming MTC quarter-frame event + // + void handleMTCQFrame(unsigned int data_byte, RealTime the_time); + + // Check whether the given event is an MTC sysex we need to act on + // (and if so act on it) + // + bool testForMTCSysex(const snd_seq_event_t *event); + + // Adjust the ALSA clock skew for MTC lock + // + void tweakSkewForMTC(int factor); + + // Recalibrate internal MTC factors + // + void calibrateMTC(); + + // Send a System message straight away + // + void sendSystemDirect(MidiByte command, int *arg); + + // Scheduled system message with arguments + // + void sendSystemQueued(MidiByte command, + const std::string &args, + const RealTime &time); + + // Set the record device + // + void setRecordDevice(DeviceId id, bool connectAction); + void unsetRecordDevices(); + + virtual bool canReconnect(Device::DeviceType type); + + virtual DeviceId addDevice(Device::DeviceType type, + MidiDevice::DeviceDirection direction); + virtual void removeDevice(DeviceId id); + virtual void renameDevice(DeviceId id, QString name); + + // Get available connections per device + // + virtual unsigned int getConnections(Device::DeviceType type, + MidiDevice::DeviceDirection direction); + virtual QString getConnection(Device::DeviceType type, + MidiDevice::DeviceDirection direction, + unsigned int connectionNo); + virtual void setConnection(DeviceId deviceId, QString connection); + virtual void setPlausibleConnection(DeviceId deviceId, QString connection); + + virtual unsigned int getTimers(); + virtual QString getTimer(unsigned int); + virtual QString getCurrentTimer(); + virtual void setCurrentTimer(QString); + + virtual void getAudioInstrumentNumbers(InstrumentId &audioInstrumentBase, + int &audioInstrumentCount) { + audioInstrumentBase = AudioInstrumentBase; +#ifdef HAVE_LIBJACK + audioInstrumentCount = AudioInstrumentCount; +#else + audioInstrumentCount = 0; +#endif + } + + virtual void getSoftSynthInstrumentNumbers(InstrumentId &ssInstrumentBase, + int &ssInstrumentCount) { + ssInstrumentBase = SoftSynthInstrumentBase; +#ifdef HAVE_DSSI + ssInstrumentCount = SoftSynthInstrumentCount; +#else + ssInstrumentCount = 0; +#endif + } + + virtual QString getStatusLog(); + + // To be called regularly from JACK driver when idle + void checkTimerSync(size_t frames); + + virtual void runTasks(); + + // Report a failure back to the GUI + // + virtual void reportFailure(MappedEvent::FailureCode code); + +protected: + typedef std::vector AlsaPortList; + + ClientPortPair getFirstDestination(bool duplex); + ClientPortPair getPairForMappedInstrument(InstrumentId id); + int getOutputPortForMappedInstrument(InstrumentId id); + std::map > m_noteOnMap; + + /** + * Bring m_alsaPorts up-to-date; if newPorts is non-null, also + * return the new ports (not previously in m_alsaPorts) through it + */ + virtual void generatePortList(AlsaPortList *newPorts = 0); + virtual void generateInstruments(); + + virtual void generateTimerList(); + virtual std::string getAutoTimer(bool &wantTimerChecks); + + void addInstrumentsForDevice(MappedDevice *device); + MappedDevice *createMidiDevice(AlsaPortDescription *, + MidiDevice::DeviceDirection); + + virtual void processMidiOut(const MappedComposition &mC, + const RealTime &sliceStart, + const RealTime &sliceEnd); + + virtual void processSoftSynthEventOut(InstrumentId id, + const snd_seq_event_t *event, + bool now); + + virtual bool isRecording(AlsaPortDescription *port); + + virtual void processAudioQueue(bool /* now */) { } + + virtual void setConnectionToDevice(MappedDevice &device, QString connection); + virtual void setConnectionToDevice(MappedDevice &device, QString connection, + const ClientPortPair &pair); + +private: + RealTime getAlsaTime(); + + // Locally convenient to control our devices + // + void sendDeviceController(DeviceId device, + MidiByte byte1, + MidiByte byte2); + + int checkAlsaError(int rc, const char *message); + + AlsaPortList m_alsaPorts; + + // ALSA MIDI/Sequencer stuff + // + snd_seq_t *m_midiHandle; + int m_client; + + int m_inputPort; + + typedef std::map DeviceIntMap; + DeviceIntMap m_outputPorts; + + int m_syncOutputPort; + int m_controllerPort; + + int m_queue; + int m_maxClients; + int m_maxPorts; + int m_maxQueues; + + // Because this can fail even if the driver's up (if + // another service is using the port say) + // + bool m_midiInputPortConnected; + + bool m_midiSyncAutoConnect; + + RealTime m_alsaPlayStartTime; + RealTime m_alsaRecordStartTime; + + RealTime m_loopStartTime; + RealTime m_loopEndTime; + + // MIDI Time Code handling: + + unsigned int m_eat_mtc; + // Received/emitted MTC data breakdown: + RealTime m_mtcReceiveTime; + RealTime m_mtcEncodedTime; + int m_mtcFrames; + int m_mtcSeconds; + int m_mtcMinutes; + int m_mtcHours; + int m_mtcSMPTEType; + + // Calculated MTC factors: + int m_mtcFirstTime; + RealTime m_mtcLastEncoded; + RealTime m_mtcLastReceive; + long long int m_mtcSigmaE; + long long int m_mtcSigmaC; + unsigned int m_mtcSkew; + + bool m_looping; + + bool m_haveShutdown; + +#ifdef HAVE_LIBJACK + JackDriver *m_jackDriver; +#endif + + Scavenger m_pluginScavenger; + + //!!! -- hoist to SoundDriver w/setter? + typedef std::set InstrumentSet; + InstrumentSet m_recordingInstruments; + + typedef std::map DevicePortMap; + DevicePortMap m_devicePortMap; + + typedef std::map PortDeviceMap; + PortDeviceMap m_suspendedPortMap; + + std::string getPortName(ClientPortPair port); + ClientPortPair getPortByName(std::string name); + + DeviceId getSpareDeviceId(); + + struct AlsaTimerInfo { + int clas; + int sclas; + int card; + int device; + int subdevice; + std::string name; + long resolution; + }; + std::vector m_timers; + std::string m_currentTimer; + + // This auxiliary queue is here as a hack, to avoid stuck notes if + // resetting playback while a note-off is currently in the ALSA + // queue. When playback is reset by ffwd or rewind etc, we drop + // all the queued events (which is generally what is desired, + // except for note offs) and reset the queue timer (so the note + // offs would have the wrong time stamps even if we hadn't dropped + // them). Thus, we need to re-send any recent note offs before + // continuing. This queue records which note offs have been + // added to the ALSA queue recently. + // + NoteOffQueue m_recentNoteOffs; + void pushRecentNoteOffs(); // move from recent to normal queue after reset + void cropRecentNoteOffs(const RealTime &t); // remove old note offs + void weedRecentNoteOffs(unsigned int pitch, MidiByte channel, + InstrumentId instrument); // on subsequent note on + + bool m_queueRunning; + + bool m_portCheckNeeded; + + enum { NeedNoJackStart, NeedJackReposition, NeedJackStart } m_needJackStart; + + bool m_doTimerChecks; + bool m_firstTimerCheck; + double m_timerRatio; + bool m_timerRatioCalculated; + + std::string getAlsaModuleVersionString(); + std::string getKernelVersionString(); + void extractVersion(std::string vstr, int &major, int &minor, int &subminor, std::string &suffix); + bool versionIsAtLeast(std::string vstr, int major, int minor, int subminor); +}; + +} + +#endif // HAVE_ALSA + +#endif // _ALSADRIVER_H_ + diff --git a/src/sound/AlsaPort.cpp b/src/sound/AlsaPort.cpp new file mode 100644 index 0000000..dc12610 --- /dev/null +++ b/src/sound/AlsaPort.cpp @@ -0,0 +1,192 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- +/* + 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 "AlsaPort.h" + +#include +#include +#include + +#ifdef HAVE_ALSA + +// ALSA +#include +#include +#include + +#include "MappedInstrument.h" +#include "Midi.h" +#include "WAVAudioFile.h" +#include "MappedStudio.h" +#include "misc/Strings.h" + +#ifdef HAVE_LIBJACK +#include +#include // for usleep +#include +#endif + +namespace Rosegarden +{ + +AlsaPortDescription::AlsaPortDescription(Instrument::InstrumentType type, + const std::string &name, + int client, + int port, + unsigned int clientType, + unsigned int portType, + unsigned int capability, + PortDirection direction): + m_type(type), + m_name(name), + m_client(client), + m_port(port), + m_clientType(clientType), + m_portType(portType), + m_capability(capability), + m_direction(direction) +{} + + +bool +AlsaPortCmp::operator()(AlsaPortDescription *a1, AlsaPortDescription *a2) +{ + // Ordering for ALSA ports in the list: + // + // * Hardware ports (client id 64-127) sorted by direction + // (write, duplex, read) then client id then port id + // + // * Software ports (client id 128+) sorted by client id + // then port id + // + // * System ports (client id 0-63) sorted by client id then + // port id + + + // See comment in AlsaDriver::createMidiDevice -- the client + // numbering scheme changed in ALSA driver 1.0.11rc1. + // We now order: + // + // * Write-only software ports (client id 128+) sorted by client + // id then port id + // + // * Probable hardware ports (client id 16-127) sorted by + // direction (write, duplex, read) then client id (64+ + // preferred) then port id + // + // * Read-write or read-only software ports (client id 128+) + // sorted by client id then port id + // + // * System ports (client id 0-15) sorted by client id then + // port id + // + // It's necessary to handle software ports ahead of + // hardware/system ports, because we want to keep all the hardware + // ports together (we don't want to change the priority of a + // hardware port relative to a software port based on its client + // ID) and we can't know for sure whether the 16-63 range are + // hardware or system ports. + + enum Category { + WRITE_ONLY_SOFTWARE, + HARDWARE_PROBABLY, + MIXED_SOFTWARE, + SYSTEM + }; + + bool oldScheme = (SND_LIB_MAJOR == 0 || + (SND_LIB_MAJOR == 1 && + SND_LIB_MINOR == 0 && + SND_LIB_SUBMINOR < 11)); + + Category a1cat; + if (a1->m_client < 16) + a1cat = SYSTEM; + else if (oldScheme && (a1->m_client < 64)) + a1cat = SYSTEM; + else if (a1->m_client < 128) + a1cat = HARDWARE_PROBABLY; + else + a1cat = MIXED_SOFTWARE; + + if (a1cat == MIXED_SOFTWARE) { + if (a1->m_direction == WriteOnly) + a1cat = WRITE_ONLY_SOFTWARE; + } + + Category a2cat; + if (a2->m_client < 16) + a2cat = SYSTEM; + else if (oldScheme && (a2->m_client < 64)) + a2cat = SYSTEM; + else if (a2->m_client < 128) + a2cat = HARDWARE_PROBABLY; + else + a2cat = MIXED_SOFTWARE; + + if (a2cat == MIXED_SOFTWARE) { + if (a2->m_direction == WriteOnly) + a2cat = WRITE_ONLY_SOFTWARE; + } + + if (a1cat != a2cat) + return int(a1cat) < int(a2cat); + + if (a1cat == HARDWARE_PROBABLY) { + + if (a1->m_direction == WriteOnly) { + if (a2->m_direction != WriteOnly) + return true; + } else if (a1->m_direction == Duplex) { + if (a2->m_direction == ReadOnly) + return true; + } + + int c1 = a1->m_client; + int c2 = a2->m_client; + if (c1 < 64) + c1 += 1000; + if (c2 < 64) + c2 += 1000; + if (c1 != c2) + return c1 < c2; + + } else if (a1cat == SYSTEM) { + + int c1 = a1->m_client; + int c2 = a2->m_client; + if (c1 < 16) + c1 += 1000; + if (c2 < 16) + c2 += 1000; + if (c1 != c2) + return c1 < c2; + } + + if (a1->m_client != a2->m_client) { + return a1->m_client < a2->m_client; + } else { + return a1->m_port < a2->m_port; + } +} + +} + +#endif // HAVE_ALSA diff --git a/src/sound/AlsaPort.h b/src/sound/AlsaPort.h new file mode 100644 index 0000000..39cf009 --- /dev/null +++ b/src/sound/AlsaPort.h @@ -0,0 +1,86 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- +/* + 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 +#include "Instrument.h" +#include "MappedCommon.h" + +#ifndef _ALSAPORT_H_ +#define _ALSAPORT_H_ + +#ifdef HAVE_ALSA +#include // ALSA + +namespace Rosegarden +{ + +typedef std::pair ClientPortPair; + +// Use this to hold all client information so that we can sort it +// before generating devices - we want to put non-duplex devices +// at the front of any device list (makes thing much easier at the +// GUI and we already have some backwards compatability issues with +// this). +// +class AlsaPortDescription +{ +public: + AlsaPortDescription(Instrument::InstrumentType type, + const std::string &name, + int client, + int port, + unsigned int clientType, + unsigned int portType, + unsigned int capability, + PortDirection direction); + + Instrument::InstrumentType m_type; + std::string m_name; + int m_client; + int m_port; + unsigned int m_clientType; + unsigned int m_portType; + unsigned int m_capability; + PortDirection m_direction; // or can deduce from capability + + bool isReadable() { return m_direction == ReadOnly || + m_direction == Duplex; } + + bool isWriteable() { return m_direction == WriteOnly || + m_direction == Duplex; } + +}; + +// Sort by checking direction +// +struct AlsaPortCmp +{ + bool operator()(AlsaPortDescription *a1, + AlsaPortDescription *a2); +}; + + +} + +#endif // HAVE_ALSA + +#endif // _RG_ALSA_PORT_H_ + diff --git a/src/sound/AudioCache.cpp b/src/sound/AudioCache.cpp new file mode 100644 index 0000000..e1e9000 --- /dev/null +++ b/src/sound/AudioCache.cpp @@ -0,0 +1,139 @@ + +// -*- c-basic-offset: 4 -*- + +/* + 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 "AudioCache.h" +#include + +//#define DEBUG_AUDIO_CACHE 1 + +namespace Rosegarden +{ + +AudioCache::~AudioCache() +{ + clear(); +} + +bool +AudioCache::has(void *index) +{ + return m_cache.find(index) != m_cache.end(); +} + +float ** +AudioCache::getData(void *index, size_t &channels, size_t &frames) +{ + if (m_cache.find(index) == m_cache.end()) + return 0; + CacheRec *rec = m_cache[index]; + channels = rec->channels; + frames = rec->nframes; + return rec->data; +} + +void +AudioCache::addData(void *index, size_t channels, size_t nframes, float **data) +{ +#ifdef DEBUG_AUDIO_CACHE + std::cerr << "AudioCache::addData(" << index << ")" << std::endl; +#endif + + if (m_cache.find(index) != m_cache.end()) { + std::cerr << "WARNING: AudioCache::addData(" << index << ", " + << channels << ", " << nframes + << ": already cached" << std::endl; + return ; + } + + m_cache[index] = new CacheRec(data, channels, nframes); +} + +void +AudioCache::incrementReference(void *index) +{ + if (m_cache.find(index) == m_cache.end()) { + std::cerr << "WARNING: AudioCache::incrementReference(" << index + << "): not found" << std::endl; + return ; + } + ++m_cache[index]->refCount; + +#ifdef DEBUG_AUDIO_CACHE + + std::cerr << "AudioCache::incrementReference(" << index << ") [to " << (m_cache[index]->refCount) << "]" << std::endl; +#endif +} + +void +AudioCache::decrementReference(void *index) +{ + std::map::iterator i = m_cache.find(index); + + if (i == m_cache.end()) { + std::cerr << "WARNING: AudioCache::decrementReference(" << index + << "): not found" << std::endl; + return ; + } + if (i->second->refCount <= 1) { + delete i->second; + m_cache.erase(i); +#ifdef DEBUG_AUDIO_CACHE + + std::cerr << "AudioCache::decrementReference(" << index << ") [deleting]" << std::endl; +#endif + + } else { + --i->second->refCount; +#ifdef DEBUG_AUDIO_CACHE + + std::cerr << "AudioCache::decrementReference(" << index << ") [to " << (m_cache[index]->refCount) << "]" << std::endl; +#endif + + } +} + +void +AudioCache::clear() +{ +#ifdef DEBUG_AUDIO_CACHE + std::cerr << "AudioCache::clear()" << std::endl; +#endif + + for (std::map::iterator i = m_cache.begin(); + i != m_cache.end(); ++i) { + if (i->second->refCount > 0) { + std::cerr << "WARNING: AudioCache::clear: deleting cached data with refCount " << i->second->refCount << std::endl; + } + } + m_cache.clear(); +} + +AudioCache::CacheRec::~CacheRec() +{ + for (size_t j = 0; j < channels; ++j) + delete[] data[j]; + delete[] data; +} + +} + + diff --git a/src/sound/AudioCache.h b/src/sound/AudioCache.h new file mode 100644 index 0000000..6dd55ff --- /dev/null +++ b/src/sound/AudioCache.h @@ -0,0 +1,98 @@ +// -*- c-basic-offset: 4 -*- + +/* + 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. +*/ + +#ifndef _AUDIO_CACHE_H_ +#define _AUDIO_CACHE_H_ + +#include + +namespace Rosegarden +{ + +/** + * A simple cache for smallish bits of audio data, indexed by some + * opaque pointer type. (The PlayableAudioFile uses this with an + * AudioFile* index type, for example.) With reference counting. + */ + +class AudioCache +{ +public: + AudioCache() { } + virtual ~AudioCache(); + + /** + * Look some audio data up in the cache and report whether it + * exists. + */ + bool has(void *index); + + /** + * Look some audio data up in the cache and return it if it + * exists, returning the number of channels in channels, the frame + * count in frames, and one pointer per channel to samples in the + * return value. Return 0 if the data is not in cache. Does not + * affect the reference count. Ownership of the returned data + * remains with the cache object. You should not call this unless + * you have already called incrementReference (or addData) to + * register your interest. + */ + float **getData(void *index, size_t &channels, size_t &frames); + + /** + * Add a piece of data to the cache, and increment the reference + * count for that data (to 1). Ownership of the data is passed + * to the cache, which will delete it with delete[] when done. + */ + void addData(void *index, size_t channels, size_t nframes, float **data); + + /** + * Increment the reference count for a given piece of data. + */ + void incrementReference(void *index); + + /** + * Decrement the reference count for a given piece of data, + * and delete the data from the cache if the reference count has + * reached zero. + */ + void decrementReference(void *index); + +protected: + void clear(); + + struct CacheRec { + CacheRec() : data(0), channels(0), nframes(0), refCount(0) { } + CacheRec(float **d, size_t c, size_t n) : + data(d), channels(c), nframes(n), refCount(1) { } + ~CacheRec(); + float **data; + size_t channels; + size_t nframes; + int refCount; + }; + + std::map m_cache; +}; + +} + +#endif diff --git a/src/sound/AudioFile.cpp b/src/sound/AudioFile.cpp new file mode 100644 index 0000000..6eba15a --- /dev/null +++ b/src/sound/AudioFile.cpp @@ -0,0 +1,75 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- +/* + 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 "AudioFile.h" + +namespace Rosegarden + +{ + +AudioFile::AudioFile(unsigned int id, + const std::string &name, + const std::string &fileName): + SoundFile(fileName), + m_type(UNKNOWN), + m_id(id), + m_name(name), + m_bitsPerSample(0), + m_sampleRate(0), + m_channels(0), + m_dataChunkIndex( -1) +{ + m_fileInfo = new QFileInfo(QString(fileName.c_str())); +} + +AudioFile::AudioFile(const std::string &fileName, + unsigned int channels = 1, + unsigned int sampleRate = 48000, + unsigned int bitsPerSample = 16): + SoundFile(fileName), + m_type(UNKNOWN), + m_id(0), + m_name(""), + m_bitsPerSample(bitsPerSample), + m_sampleRate(sampleRate), + m_channels(channels), + m_dataChunkIndex( -1) +{ + m_fileInfo = new QFileInfo(QString(fileName.c_str())); +} + +AudioFile::~AudioFile() +{ + delete m_fileInfo; +} + +QDateTime +AudioFile::getModificationDateTime() +{ + if (m_fileInfo) + return m_fileInfo->lastModified(); + else + return QDateTime(); +} + + +} + diff --git a/src/sound/AudioFile.h b/src/sound/AudioFile.h new file mode 100644 index 0000000..1acbe61 --- /dev/null +++ b/src/sound/AudioFile.h @@ -0,0 +1,216 @@ +// -*- c-basic-offset: 4 -*- + +/* + 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. +*/ + + +#ifndef _AUDIOFILE_H_ +#define _AUDIOFILE_H_ + +#include +#include +#include + +#include + +#include "SoundFile.h" +#include "RealTime.h" + +// An AudioFile maintains information pertaining to an audio sample. +// This is an abstract base class from which we derive our actual +// AudioFile types - WAV, BWF, AIFF etc. +// +// + +namespace Rosegarden +{ + +typedef unsigned int AudioFileId; + +// The different types of audio file we support. +// +typedef enum +{ + + UNKNOWN, // not yet known + WAV, // RIFF (Resource Interchange File Format) wav file + BWF, // RIFF Broadcast Wave File + AIFF, // Audio Interchange File Format + MP3 + +} AudioFileType; + +class AudioFile : public SoundFile +{ +public: + // The "read" constructor - open a file + // an assign a name and id to it. + // + AudioFile(AudioFileId id, + const std::string &name, + const std::string &fileName); + + // The "write" constructor - we only need to + // specify a filename and some parameters and + // then write it out. + // + AudioFile(const std::string &fileName, + unsigned int channels, + unsigned int sampleRate, + unsigned int bitsPerSample); + + ~AudioFile(); + + // Id of this audio file (used by AudioFileManager) + // + void setId(AudioFileId id) { m_id = id; } + AudioFileId getId() const { return m_id; } + + // Name of this sample - in addition to a filename + // + void setName(const std::string &name) { m_name = name; } + std::string getName() const { return m_name; } + + // Used for waveform interpolation at a point + // + float sinc(float value) { return sin(M_PI * value)/ M_PI * value; } + + // Audio file identifier - set in constructor of file type + // + AudioFileType getType() const { return m_type; } + + unsigned int getBitsPerSample() const { return m_bitsPerSample; } + unsigned int getSampleRate() const { return m_sampleRate; } + unsigned int getChannels() const { return m_channels; } + + // We must continue our main control abstract methods from SoundFile + // and add our own close() method that will add any relevant header + // information to an audio file that has been written and is now + // being closed. + // + virtual bool open() = 0; + virtual bool write() = 0; + virtual void close() = 0; + + // Show the information we have on this file + // + virtual void printStats() = 0; + + // Move file pointer to relative time in data chunk - shouldn't be + // less than zero. Returns true if the scan time was valid and + // successful. Need two interfaces because when playing we use an + // external file handle (one per playback instance - PlayableAudioFile) + // + virtual bool scanTo(const RealTime &time) = 0; + virtual bool scanTo(std::ifstream *file, const RealTime &time) = 0; + + // Scan forward in a file by a certain amount of time - same + // double interface (simple one for peak file generation, other + // for playback). + // + virtual bool scanForward(const RealTime &time) = 0; + virtual bool scanForward(std::ifstream *file, const RealTime &time) = 0; + + // Return a number of samples - caller will have to + // de-interleave n-channel samples themselves. + // + virtual std::string getSampleFrames(std::ifstream *file, + unsigned int frames) = 0; + + // Return a number of samples - caller will have to + // de-interleave n-channel samples themselves. Place + // results in buf, return actual number of frames read. + // + virtual unsigned int getSampleFrames(std::ifstream *file, + char *buf, + unsigned int frames) = 0; + + // Return a number of (possibly) interleaved samples + // over a time slice from current file pointer position. + // + virtual std::string getSampleFrameSlice(std::ifstream *file, + const RealTime &time) = 0; + + // Append a string of samples to an already open (for writing) + // audio file. Caller must have interleaved samples etc. + // + virtual bool appendSamples(const std::string &buffer) = 0; + + // Append a string of samples to an already open (for writing) + // audio file. Caller must have interleaved samples etc. + // + virtual bool appendSamples(const char *buffer, unsigned int frames) = 0; + + // Get the length of the sample file in RealTime + // + virtual RealTime getLength() = 0; + + // Offset to start of sample data + // + virtual std::streampos getDataOffset() = 0; + + // Return the peak file name + // + virtual std::string getPeakFilename() = 0; + + // Return the modification timestamp + // + QDateTime getModificationDateTime(); + + // Implement in actual file type + // + virtual unsigned int getBytesPerFrame() = 0; + + // Decode and de-interleave the given samples that were retrieved + // from this file or another with the same format as it. Place + // the results in the given float buffer. Return true for + // success. This function does crappy resampling if necessary. + // + virtual bool decode(const unsigned char *sourceData, + size_t sourceBytes, + size_t targetSampleRate, + size_t targetChannels, + size_t targetFrames, + std::vector &targetData, + bool addToResultBuffers = false) = 0; + +protected: + + AudioFileType m_type; // AudioFile type + AudioFileId m_id; // AudioFile ID + std::string m_name; // AudioFile name (not filename) + + unsigned int m_bitsPerSample; + unsigned int m_sampleRate; + unsigned int m_channels; + + // How many bytes do we read before we get to the data? + // Could be huge so we make it a long long. -1 means it + // hasn't been set yet. + // + long long m_dataChunkIndex; + + QFileInfo *m_fileInfo; + +}; + +} + + +#endif // _AUDIOFILE_H_ diff --git a/src/sound/AudioFileManager.cpp b/src/sound/AudioFileManager.cpp new file mode 100644 index 0000000..93be26c --- /dev/null +++ b/src/sound/AudioFileManager.cpp @@ -0,0 +1,1257 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- +/* + 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 +#include +#include +#include // for new recording file +#include // sprintf +#include +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "AudioFile.h" +#include "AudioFileManager.h" +#include "WAVAudioFile.h" +#include "BWFAudioFile.h" +#include "MP3AudioFile.h" +#include "misc/Strings.h" + +namespace Rosegarden +{ + +static pthread_mutex_t _audioFileManagerLock; + +class MutexLock +{ +public: + MutexLock(pthread_mutex_t *mutex) : m_mutex(mutex) + { + pthread_mutex_lock(m_mutex); + } + ~MutexLock() + { + pthread_mutex_unlock(m_mutex); + } +private: + pthread_mutex_t *m_mutex; +}; + +AudioFileManager::AudioFileManager() : + m_importProcess(0), + m_expectedSampleRate(0) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); +#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE + + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#else +#ifdef PTHREAD_MUTEX_RECURSIVE + + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#else + + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); +#endif +#endif + + pthread_mutex_init(&_audioFileManagerLock, &attr); + + // Set this through the set method so that the tilde gets + // shaken out. + // + setAudioPath("~/rosegarden"); + + // Retransmit progress + // + connect(&m_peakManager, SIGNAL(setProgress(int)), + this, SIGNAL(setProgress(int))); +} + +AudioFileManager::~AudioFileManager() +{ + clear(); +} + +// Add a file from an absolute path +// +AudioFileId +AudioFileManager::addFile(const std::string &filePath) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + QString ext; + + if (filePath.length() > 3) { + ext = QString(filePath.substr(filePath.length() - 3, 3).c_str()).lower(); + } + + // Check for file existing already in manager by path + // + int check = fileExists(filePath); + if (check != -1) { + return AudioFileId(check); + } + + // prepare for audio file + AudioFile *aF = 0; + AudioFileId id = getFirstUnusedID(); + + if (ext == "wav") { + // identify file type + AudioFileType subType = RIFFAudioFile::identifySubType(filePath); + + if (subType == BWF) { +#ifdef DEBUG_AUDIOFILEMANAGER + std::cout << "FOUND BWF" << std::endl; +#endif + + try { + aF = new BWFAudioFile(id, getShortFilename(filePath), filePath); + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + } else if (subType == WAV) { + try { + aF = new WAVAudioFile(id, getShortFilename(filePath), filePath); + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + } + + // Ensure we have a valid file handle + // + if (aF == 0) { + std::cerr << "AudioFileManager: Unknown WAV audio file subtype in " << filePath << std::endl; + throw BadAudioPathException(filePath, __FILE__, __LINE__); + } + + // Add file type on extension + try { + if (aF->open() == false) { + delete aF; + std::cerr << "AudioFileManager: Malformed audio file in " << filePath << std::endl; + throw BadAudioPathException(filePath, __FILE__, __LINE__); + } + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + } +#ifdef HAVE_LIBMAD + else if (ext == "mp3") { + try { + aF = new MP3AudioFile(id, getShortFilename(filePath), filePath); + + if (aF->open() == false) { + delete aF; + std::cerr << "AudioFileManager: Malformed mp3 audio file in " << filePath << std::endl; + throw BadAudioPathException(filePath, __FILE__, __LINE__); + } + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + } +#endif // HAVE_LIBMAD + else { + std::cerr << "AudioFileManager: Unsupported audio file extension in " << filePath << std::endl; + throw BadAudioPathException(filePath, __FILE__, __LINE__); + } + + if (aF) { + m_audioFiles.push_back(aF); + return id; + } + + return 0; +} + +// Convert long filename to shorter version +std::string +AudioFileManager::getShortFilename(const std::string &fileName) +{ + std::string rS = fileName; + unsigned int pos = rS.find_last_of("/"); + + if (pos > 0 && ( pos + 1 ) < rS.length()) + rS = rS.substr(pos + 1, rS.length()); + + return rS; +} + +// Turn a long path into a directory ending with a slash +// +std::string +AudioFileManager::getDirectory(const std::string &path) +{ + std::string rS = path; + unsigned int pos = rS.find_last_of("/"); + + if (pos > 0 && ( pos + 1 ) < rS.length()) + rS = rS.substr(0, pos + 1); + + return rS; +} + + +// Create a new AudioFile with unique ID and label - insert from +// our RG4 file +// +AudioFileId +AudioFileManager::insertFile(const std::string &name, + const std::string &fileName) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + // first try to expand any beginning tilde + // + std::string foundFileName = substituteTildeForHome(fileName); + + // If we've expanded and we can't find the file + // then try to find it in audio file directory. + // + QFileInfo info(foundFileName.c_str()); + if (!info.exists()) + foundFileName = getFileInPath(foundFileName); + +#ifdef DEBUG_AUDIOFILEMANAGER_INSERT_FILE + + std::cout << "AudioFileManager::insertFile - " + << "expanded fileName = \"" + << foundFileName << "\"" << std::endl; +#endif + + // bail if we haven't found any reasonable filename + if (foundFileName == "") + return false; + + AudioFileId id = getFirstUnusedID(); + + WAVAudioFile *aF = 0; + + try { + + aF = new WAVAudioFile(id, name, foundFileName); + + // if we don't recognise the file then don't insert it + // + if (aF->open() == false) { + delete aF; + std::cerr << "AudioFileManager::insertFile - don't recognise file type in " << foundFileName << std::endl; + throw BadAudioPathException(foundFileName, __FILE__, __LINE__); + } + m_audioFiles.push_back(aF); + + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + + return id; +} + + +bool +AudioFileManager::removeFile(AudioFileId id) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::vector::iterator it; + + for (it = m_audioFiles.begin(); + it != m_audioFiles.end(); + ++it) { + if ((*it)->getId() == id) { + m_peakManager.removeAudioFile(*it); + m_recordedAudioFiles.erase(*it); + m_derivedAudioFiles.erase(*it); + delete(*it); + m_audioFiles.erase(it); + return true; + } + } + + return false; +} + +AudioFileId +AudioFileManager::getFirstUnusedID() +{ + AudioFileId rI = 0; + + if (m_audioFiles.size() == 0) + return rI; + + std::vector::iterator it; + + for (it = m_audioFiles.begin(); + it != m_audioFiles.end(); + ++it) { + if (rI < (*it)->getId()) + rI = (*it)->getId(); + } + + rI++; + + return rI; +} + +bool +AudioFileManager::insertFile(const std::string &name, + const std::string &fileName, + AudioFileId id) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + // first try to expany any beginning tilde + std::string foundFileName = substituteTildeForHome(fileName); + + // If we've expanded and we can't find the file + // then try to find it in audio file directory. + // + QFileInfo info(foundFileName.c_str()); + if (!info.exists()) + foundFileName = getFileInPath(foundFileName); + +#ifdef DEBUG_AUDIOFILEMANAGER_INSERT_FILE + + std::cout << "AudioFileManager::insertFile - " + << "expanded fileName = \"" + << foundFileName << "\"" << std::endl; +#endif + + // If no joy here then we can't find this file + if (foundFileName == "") + return false; + + // make sure we don't have a file of this ID hanging around already + removeFile(id); + + // and insert + WAVAudioFile *aF = 0; + + try { + + aF = new WAVAudioFile(id, name, foundFileName); + + // Test the file + if (aF->open() == false) { + delete aF; + return false; + } + + m_audioFiles.push_back(aF); + + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + + return true; +} + +// Add a given path to our sample search path +// +void +AudioFileManager::setAudioPath(const std::string &path) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::string hPath = path; + + // add a trailing / if we don't have one + // + if (hPath[hPath.size() - 1] != '/') + hPath += std::string("/"); + + // get the home directory + if (hPath[0] == '~') { + hPath.erase(0, 1); + hPath = std::string(getenv("HOME")) + hPath; + } + + m_audioPath = hPath; + +} + +void +AudioFileManager::testAudioPath() throw (BadAudioPathException) +{ + QFileInfo info(m_audioPath.c_str()); + if (!(info.exists() && info.isDir() && !info.isRelative() && + info.isWritable() && info.isReadable())) + throw BadAudioPathException(m_audioPath.data()); +} + + +// See if we can find a given file in our search path +// return the first occurence of a match or the empty +// std::string if no match. +// +std::string +AudioFileManager::getFileInPath(const std::string &file) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + QFileInfo info(file.c_str()); + + if (info.exists()) + return file; + + // Build the search filename from the audio path and + // the file basename. + // + QString searchFile = QString(m_audioPath.c_str()) + info.fileName(); + QFileInfo searchInfo(searchFile); + + if (searchInfo.exists()) + return searchFile.latin1(); + + std::cout << "AudioFileManager::getFileInPath - " + << "searchInfo = " << searchFile << std::endl; + + return ""; +} + + +// Check for file path existence +// +int +AudioFileManager::fileExists(const std::string &path) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::vector::iterator it; + + for (it = m_audioFiles.begin(); + it != m_audioFiles.end(); + ++it) { + if ((*it)->getFilename() == path) + return (*it)->getId(); + } + + return -1; + +} + +// Does a specific file id exist on the manager? +// +bool +AudioFileManager::fileExists(AudioFileId id) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::vector::iterator it; + + for (it = m_audioFiles.begin(); + it != m_audioFiles.end(); + ++it) { + if ((*it)->getId() == id) + return true; + } + + return false; + +} + +void +AudioFileManager::clear() +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::vector::iterator it; + + for (it = m_audioFiles.begin(); + it != m_audioFiles.end(); + ++it) { + m_recordedAudioFiles.erase(*it); + m_derivedAudioFiles.erase(*it); + delete(*it); + } + + m_audioFiles.erase(m_audioFiles.begin(), m_audioFiles.end()); + + // Clear the PeakFileManager too + // + m_peakManager.clear(); +} + +AudioFile * +AudioFileManager::createRecordingAudioFile() +{ + MutexLock lock (&_audioFileManagerLock) + ; + + AudioFileId newId = getFirstUnusedID(); + QString fileName = ""; + + while (fileName == "") { + + fileName = QString("rg-%1-%2.wav") + .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")) + .arg(newId + 1); + + if (QFile(m_audioPath.c_str() + fileName).exists()) { + fileName = ""; + ++newId; + } + } + + // insert file into vector + WAVAudioFile *aF = 0; + + try { + aF = new WAVAudioFile(newId, fileName.data(), m_audioPath + fileName.data()); + m_audioFiles.push_back(aF); + m_recordedAudioFiles.insert(aF); + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + + return aF; +} + +std::vector +AudioFileManager::createRecordingAudioFiles(unsigned int n) +{ + std::vector v; + for (unsigned int i = 0; i < n; ++i) { + AudioFile *af = createRecordingAudioFile(); + if (af) + v.push_back(m_audioPath + af->getFilename().data()); + // !af should not happen, and we have no good recovery if it does + } + return v; +} + +bool +AudioFileManager::wasAudioFileRecentlyRecorded(AudioFileId id) +{ + AudioFile *file = getAudioFile(id); + if (file) + return (m_recordedAudioFiles.find(file) != + m_recordedAudioFiles.end()); + return false; +} + +bool +AudioFileManager::wasAudioFileRecentlyDerived(AudioFileId id) +{ + AudioFile *file = getAudioFile(id); + if (file) + return (m_derivedAudioFiles.find(file) != + m_derivedAudioFiles.end()); + return false; +} + +void +AudioFileManager::resetRecentlyCreatedFiles() +{ + m_recordedAudioFiles.clear(); + m_derivedAudioFiles.clear(); +} + +AudioFile * +AudioFileManager::createDerivedAudioFile(AudioFileId source, + const char *prefix) +{ + MutexLock lock (&_audioFileManagerLock); + + AudioFile *sourceFile = getAudioFile(source); + if (!sourceFile) return 0; + + AudioFileId newId = getFirstUnusedID(); + QString fileName = ""; + + std::string sourceBase = sourceFile->getShortFilename(); + if (sourceBase.length() > 4 && sourceBase.substr(0, 3) == "rg-") { + sourceBase = sourceBase.substr(3); + } + if (sourceBase.length() > 15) sourceBase = sourceBase.substr(0, 15); + + while (fileName == "") { + + fileName = QString("%1-%2-%3-%4.wav") + .arg(prefix) + .arg(sourceBase) + .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")) + .arg(newId + 1); + + if (QFile(m_audioPath.c_str() + fileName).exists()) { + fileName = ""; + ++newId; + } + } + + // insert file into vector + WAVAudioFile *aF = 0; + + try { + aF = new WAVAudioFile(newId, + fileName.data(), + m_audioPath + fileName.data()); + m_audioFiles.push_back(aF); + m_derivedAudioFiles.insert(aF); + } catch (SoundFile::BadSoundFileException e) { + delete aF; + throw BadAudioPathException(e); + } + + return aF; +} + +AudioFileId +AudioFileManager::importURL(const KURL &url, int sampleRate) +{ + if (url.isLocalFile()) return importFile(url.path(), sampleRate); + + std::cerr << "AudioFileManager::importURL("<< url.prettyURL() << ", " << sampleRate << ")" << std::endl; + + emit setOperationName(i18n("Downloading file %1").arg(url.prettyURL())); + + QString localPath = ""; + if (!KIO::NetAccess::download(url, localPath)) { + KMessageBox::error(0, i18n("Cannot download file %1").arg(url.prettyURL())); + throw SoundFile::BadSoundFileException(url.prettyURL()); + } + + AudioFileId id = 0; + + try { + id = importFile(localPath.data(), sampleRate); + } catch (BadAudioPathException ape) { + KIO::NetAccess::removeTempFile(localPath); + throw ape; + } catch (SoundFile::BadSoundFileException bse) { + KIO::NetAccess::removeTempFile(localPath); + throw bse; + } + + return id; +} + +bool +AudioFileManager::fileNeedsConversion(const std::string &fileName, + int sampleRate) +{ + KProcess *proc = new KProcess(); + *proc << "rosegarden-audiofile-importer"; + if (sampleRate > 0) { + *proc << "-r"; + *proc << QString("%1").arg(sampleRate); + } + *proc << "-w"; + *proc << fileName.c_str(); + + proc->start(KProcess::Block, KProcess::NoCommunication); + + int es = proc->exitStatus(); + delete proc; + + if (es == 0 || es == 1) { // 1 == "other error" -- wouldn't be able to convert + return false; + } + return true; +} + +AudioFileId +AudioFileManager::importFile(const std::string &fileName, int sampleRate) +{ + MutexLock lock (&_audioFileManagerLock); + + std::cerr << "AudioFileManager::importFile("<< fileName << ", " << sampleRate << ")" << std::endl; + + KProcess *proc = new KProcess(); + *proc << "rosegarden-audiofile-importer"; + if (sampleRate > 0) { + *proc << "-r"; + *proc << QString("%1").arg(sampleRate); + } + *proc << "-w"; + *proc << fileName.c_str(); + + proc->start(KProcess::Block, KProcess::NoCommunication); + + int es = proc->exitStatus(); + delete proc; + + if (es == 0) { + AudioFileId id = addFile(fileName); + m_expectedSampleRate = sampleRate; + return id; + } + + if (es == 2) { + emit setOperationName(i18n("Converting audio file...")); + } else if (es == 3) { + emit setOperationName(i18n("Resampling audio file...")); + } else if (es == 4) { + emit setOperationName(i18n("Converting and resampling audio file...")); + } else { + emit setOperationName(i18n("Importing audio file...")); + } + + AudioFileId newId = getFirstUnusedID(); + QString targetName = ""; + + QString sourceBase = QFileInfo(fileName.c_str()).baseName(); + if (sourceBase.length() > 3 && sourceBase.startsWith("rg-")) { + sourceBase = sourceBase.right(sourceBase.length() - 3); + } + if (sourceBase.length() > 15) sourceBase = sourceBase.left(15); + + while (targetName == "") { + + targetName = QString("conv-%2-%3-%4.wav") + .arg(sourceBase) + .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")) + .arg(newId + 1); + + if (QFile(m_audioPath.c_str() + targetName).exists()) { + targetName = ""; + ++newId; + } + } + + m_importProcess = new KProcess; + + *m_importProcess << "rosegarden-audiofile-importer"; + if (sampleRate > 0) { + *m_importProcess << "-r"; + *m_importProcess << QString("%1").arg(sampleRate); + } + *m_importProcess << "-c"; + *m_importProcess << fileName.c_str(); + *m_importProcess << (m_audioPath.c_str() + targetName); + + m_importProcess->start(KProcess::NotifyOnExit, KProcess::NoCommunication); + + while (m_importProcess->isRunning()) { + kapp->processEvents(100); + } + + if (!m_importProcess->normalExit()) { + // interrupted + throw SoundFile::BadSoundFileException(fileName, "Import cancelled"); + } + + es = m_importProcess->exitStatus(); + delete m_importProcess; + m_importProcess = 0; + + if (es) { + std::cerr << "audio file importer failed" << std::endl; + throw SoundFile::BadSoundFileException(fileName, i18n("Failed to convert or resample audio file on import")); + } else { + std::cerr << "audio file importer succeeded" << std::endl; + } + + // insert file into vector + WAVAudioFile *aF = 0; + + aF = new WAVAudioFile(newId, + targetName.data(), + m_audioPath + targetName.data()); + m_audioFiles.push_back(aF); + m_derivedAudioFiles.insert(aF); + // Don't catch SoundFile::BadSoundFileException + + m_expectedSampleRate = sampleRate; + + return aF->getId(); +} + +void +AudioFileManager::slotStopImport() +{ + if (m_importProcess) { + m_importProcess->kill(SIGTERM); + sleep(1); + m_importProcess->kill(SIGKILL); + } +} + +AudioFile* +AudioFileManager::getLastAudioFile() +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::vector::iterator it = m_audioFiles.begin(); + AudioFile* audioFile = 0; + + while (it != m_audioFiles.end()) { + audioFile = (*it); + it++; + } + + return audioFile; +} + +std::string +AudioFileManager::substituteHomeForTilde(const std::string &path) +{ + std::string rS = path; + std::string homePath = std::string(getenv("HOME")); + + // if path length is less than homePath then just return unchanged + if (rS.length() < homePath.length()) + return rS; + + // if the first section matches the path then substitute + if (rS.substr(0, homePath.length()) == homePath) { + rS.erase(0, homePath.length()); + rS = "~" + rS; + } + + return rS; +} + +std::string +AudioFileManager::substituteTildeForHome(const std::string &path) +{ + std::string rS = path; + std::string homePath = std::string(getenv("HOME")); + + if (rS.substr(0, 2) == std::string("~/")) { + rS.erase(0, 1); // erase tilde and prepend HOME env + rS = homePath + rS; + } + + return rS; +} + + + +// Export audio files and assorted bits and bobs - make sure +// that we store the files in a format that's user independent +// so that people can pack up and swap their songs (including +// audio files) and shift them about easily. +// +std::string +AudioFileManager::toXmlString() +{ + MutexLock lock (&_audioFileManagerLock) + ; + + std::stringstream audioFiles; + std::string audioPath = substituteHomeForTilde(m_audioPath); + + audioFiles << "" << std::endl; + audioFiles << " " << std::endl; + + std::string fileName; + std::vector::iterator it; + + for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) { + fileName = (*it)->getFilename(); + + // attempt two substitutions - If the prefix to the filename + // is the same as the audio path then we can dock the prefix + // as it'll be added again next time. If the path doesn't + // have the audio path in it but has our home directory in it + // then swap this out for a tilde '~' + // +#ifdef DEBUG_AUDIOFILEMANAGER + + std::cout << "DIR = " << getDirectory(fileName) << " : " + " PATH = " << m_audioPath << std::endl; +#endif + + if (getDirectory(fileName) == m_audioPath) + fileName = getShortFilename(fileName); + else + fileName = substituteHomeForTilde(fileName); + + audioFiles << "