summaryrefslogtreecommitdiffstats
path: root/src/sound
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
commit145364a8af6a1fec06556221e66d4b724a62fc9a (patch)
tree53bd71a544008c518034f208d64c932dc2883f50 /src/sound
downloadrosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz
rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/sound')
-rw-r--r--src/sound/AlsaDriver.cpp5476
-rw-r--r--src/sound/AlsaDriver.h561
-rw-r--r--src/sound/AlsaPort.cpp192
-rw-r--r--src/sound/AlsaPort.h86
-rw-r--r--src/sound/AudioCache.cpp139
-rw-r--r--src/sound/AudioCache.h98
-rw-r--r--src/sound/AudioFile.cpp75
-rw-r--r--src/sound/AudioFile.h216
-rw-r--r--src/sound/AudioFileManager.cpp1257
-rw-r--r--src/sound/AudioFileManager.h327
-rw-r--r--src/sound/AudioFileTimeStretcher.cpp268
-rw-r--r--src/sound/AudioFileTimeStretcher.h76
-rw-r--r--src/sound/AudioPlayQueue.cpp501
-rw-r--r--src/sound/AudioPlayQueue.h168
-rw-r--r--src/sound/AudioProcess.cpp2463
-rw-r--r--src/sound/AudioProcess.h390
-rw-r--r--src/sound/AudioTimeStretcher.cpp667
-rw-r--r--src/sound/AudioTimeStretcher.h221
-rw-r--r--src/sound/Audit.cpp30
-rw-r--r--src/sound/Audit.h60
-rw-r--r--src/sound/BWFAudioFile.cpp171
-rw-r--r--src/sound/BWFAudioFile.h94
-rw-r--r--src/sound/ControlBlock.cpp181
-rw-r--r--src/sound/ControlBlock.h128
-rw-r--r--src/sound/DSSIPluginFactory.cpp396
-rw-r--r--src/sound/DSSIPluginFactory.h72
-rw-r--r--src/sound/DSSIPluginInstance.cpp1208
-rw-r--r--src/sound/DSSIPluginInstance.h193
-rw-r--r--src/sound/DummyDriver.h166
-rw-r--r--src/sound/ExternalTransport.h67
-rw-r--r--src/sound/JackDriver.cpp2480
-rw-r--r--src/sound/JackDriver.h297
-rw-r--r--src/sound/LADSPAPluginFactory.cpp841
-rw-r--r--src/sound/LADSPAPluginFactory.h104
-rw-r--r--src/sound/LADSPAPluginInstance.cpp435
-rw-r--r--src/sound/LADSPAPluginInstance.h137
-rw-r--r--src/sound/MP3AudioFile.cpp329
-rw-r--r--src/sound/MP3AudioFile.h128
-rw-r--r--src/sound/MappedCommon.h68
-rw-r--r--src/sound/MappedComposition.cpp216
-rw-r--r--src/sound/MappedComposition.h93
-rw-r--r--src/sound/MappedDevice.cpp250
-rw-r--r--src/sound/MappedDevice.h103
-rw-r--r--src/sound/MappedEvent.cpp593
-rw-r--r--src/sound/MappedEvent.h546
-rw-r--r--src/sound/MappedInstrument.cpp153
-rw-r--r--src/sound/MappedInstrument.h106
-rw-r--r--src/sound/MappedRealTime.cpp62
-rw-r--r--src/sound/MappedRealTime.h56
-rw-r--r--src/sound/MappedStudio.cpp1719
-rw-r--r--src/sound/MappedStudio.h552
-rw-r--r--src/sound/Midi.h184
-rw-r--r--src/sound/MidiEvent.cpp289
-rw-r--r--src/sound/MidiEvent.h141
-rw-r--r--src/sound/MidiFile.cpp2261
-rw-r--r--src/sound/MidiFile.h173
-rw-r--r--src/sound/MidiMapping.xml133
-rw-r--r--src/sound/PeakFile.cpp1033
-rw-r--r--src/sound/PeakFile.h196
-rw-r--r--src/sound/PeakFileManager.cpp327
-rw-r--r--src/sound/PeakFileManager.h162
-rw-r--r--src/sound/PlayableAudioFile.cpp1086
-rw-r--r--src/sound/PlayableAudioFile.h219
-rw-r--r--src/sound/PluginFactory.cpp120
-rw-r--r--src/sound/PluginFactory.h97
-rw-r--r--src/sound/PluginIdentifier.cpp72
-rw-r--r--src/sound/PluginIdentifier.h50
-rw-r--r--src/sound/RIFFAudioFile.cpp686
-rw-r--r--src/sound/RIFFAudioFile.h168
-rw-r--r--src/sound/RecordableAudioFile.cpp164
-rw-r--r--src/sound/RecordableAudioFile.h68
-rw-r--r--src/sound/RingBuffer.h572
-rw-r--r--src/sound/RosegardenMidiRecord.mcopclass5
-rw-r--r--src/sound/RunnablePluginInstance.cpp42
-rw-r--r--src/sound/RunnablePluginInstance.h114
-rw-r--r--src/sound/SF2PatchExtractor.cpp217
-rw-r--r--src/sound/SF2PatchExtractor.h58
-rw-r--r--src/sound/SampleWindow.h192
-rw-r--r--src/sound/Scavenger.h211
-rw-r--r--src/sound/SequencerDataBlock.cpp361
-rw-r--r--src/sound/SequencerDataBlock.h140
-rw-r--r--src/sound/SoundDriver.cpp391
-rw-r--r--src/sound/SoundDriver.h529
-rw-r--r--src/sound/SoundDriverFactory.cpp66
-rw-r--r--src/sound/SoundDriverFactory.h37
-rw-r--r--src/sound/SoundFile.cpp295
-rw-r--r--src/sound/SoundFile.h155
-rw-r--r--src/sound/WAVAudioFile.cpp255
-rw-r--r--src/sound/WAVAudioFile.h93
89 files changed, 36577 insertions, 0 deletions
diff --git a/src/sound/AlsaDriver.cpp b/src/sound/AlsaDriver.cpp
new file mode 100644
index 0000000..9d512d9
--- /dev/null
+++ b/src/sound/AlsaDriver.cpp
@@ -0,0 +1,5476 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <iostream>
+#include "misc/Debug.h"
+#include <cstdlib>
+#include <cstdio>
+#include <algorithm>
+
+#ifdef HAVE_ALSA
+
+// ALSA
+#include <alsa/asoundlib.h>
+#include <alsa/seq_event.h>
+#include <alsa/version.h>
+#include <alsa/seq.h>
+
+#include "AlsaDriver.h"
+#include "AlsaPort.h"
+#include "ExternalTransport.h"
+#include "MappedInstrument.h"
+#include "Midi.h"
+#include "MappedStudio.h"
+#include "misc/Strings.h"
+#include "MappedCommon.h"
+#include "MappedEvent.h"
+#include "Audit.h"
+#include "AudioPlayQueue.h"
+#include "ExternalTransport.h"
+
+#include <qregexp.h>
+#include <pthread.h>
+
+
+//#define DEBUG_ALSA 1
+//#define DEBUG_PROCESS_MIDI_OUT 1
+//#define DEBUG_PROCESS_SOFT_SYNTH_OUT 1
+//#define MTC_DEBUG 1
+
+// This driver implements MIDI in and out via the ALSA (www.alsa-project.org)
+// sequencer interface.
+
+using std::cerr;
+using std::endl;
+
+static size_t _debug_jack_frame_count = 0;
+
+#define AUTO_TIMER_NAME "(auto)"
+
+
+namespace Rosegarden
+{
+
+#define FAILURE_REPORT_COUNT 256
+static MappedEvent::FailureCode _failureReports[FAILURE_REPORT_COUNT];
+static int _failureReportWriteIndex = 0;
+static int _failureReportReadIndex = 0;
+
+AlsaDriver::AlsaDriver(MappedStudio *studio):
+ SoundDriver(studio,
+ std::string("[ALSA library version ") +
+ std::string(SND_LIB_VERSION_STR) +
+ std::string(", module version ") +
+ getAlsaModuleVersionString() +
+ std::string(", kernel version ") +
+ getKernelVersionString() +
+ "]"),
+ m_client( -1),
+ m_inputPort( -1),
+ m_syncOutputPort( -1),
+ m_controllerPort( -1),
+ m_queue( -1),
+ m_maxClients( -1),
+ m_maxPorts( -1),
+ m_maxQueues( -1),
+ m_midiInputPortConnected(false),
+ m_midiSyncAutoConnect(false),
+ m_alsaPlayStartTime(0, 0),
+ m_alsaRecordStartTime(0, 0),
+ m_loopStartTime(0, 0),
+ m_loopEndTime(0, 0),
+ m_eat_mtc(0),
+ m_looping(false),
+ m_haveShutdown(false)
+#ifdef HAVE_LIBJACK
+ , m_jackDriver(0)
+#endif
+ , m_queueRunning(false)
+ , m_portCheckNeeded(false),
+ m_needJackStart(NeedNoJackStart),
+ m_doTimerChecks(false),
+ m_firstTimerCheck(true),
+ m_timerRatio(0),
+ m_timerRatioCalculated(false)
+
+{
+ Audit audit;
+ audit << "Rosegarden " << VERSION << " - AlsaDriver "
+ << m_name << std::endl;
+}
+
+AlsaDriver::~AlsaDriver()
+{
+ if (!m_haveShutdown) {
+ std::cerr << "WARNING: AlsaDriver::shutdown() was not called before destructor, calling now" << std::endl;
+ shutdown();
+ }
+}
+
+int
+AlsaDriver::checkAlsaError(int rc, const char *
+#ifdef DEBUG_ALSA
+ message
+#endif
+ )
+{
+#ifdef DEBUG_ALSA
+ if (rc < 0) {
+ std::cerr << "AlsaDriver::"
+ << message
+ << ": " << rc
+ << " (" << snd_strerror(rc) << ")"
+ << std::endl;
+ }
+#endif
+ return rc;
+}
+
+void
+AlsaDriver::shutdown()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::~AlsaDriver - shutting down" << std::endl;
+#endif
+
+ processNotesOff(getAlsaTime(), true, true);
+
+#ifdef HAVE_LIBJACK
+ delete m_jackDriver;
+ m_jackDriver = 0;
+#endif
+
+ if (m_midiHandle) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::shutdown - closing MIDI client" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, 0), "shutdown(): stopping queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "shutdown(): drain output");
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::shutdown - stopped queue" << std::endl;
+#endif
+
+ snd_seq_close(m_midiHandle);
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::shutdown - closed MIDI handle" << std::endl;
+#endif
+
+ m_midiHandle = 0;
+ }
+
+ DataBlockRepository::clear();
+
+ m_haveShutdown = true;
+}
+
+void
+AlsaDriver::setLoop(const RealTime &loopStart, const RealTime &loopEnd)
+{
+ m_loopStartTime = loopStart;
+ m_loopEndTime = loopEnd;
+
+ // currently we use this simple test for looping - it might need
+ // to get more sophisticated in the future.
+ //
+ if (m_loopStartTime != m_loopEndTime)
+ m_looping = true;
+ else
+ m_looping = false;
+}
+
+void
+AlsaDriver::getSystemInfo()
+{
+ int err;
+ snd_seq_system_info_t *sysinfo;
+
+ snd_seq_system_info_alloca(&sysinfo);
+
+ if ((err = snd_seq_system_info(m_midiHandle, sysinfo)) < 0) {
+ std::cerr << "System info error: " << snd_strerror(err)
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ m_maxQueues = 0;
+ m_maxClients = 0;
+ m_maxPorts = 0;
+ return ;
+ }
+
+ m_maxQueues = snd_seq_system_info_get_queues(sysinfo);
+ m_maxClients = snd_seq_system_info_get_clients(sysinfo);
+ m_maxPorts = snd_seq_system_info_get_ports(sysinfo);
+}
+
+void
+AlsaDriver::showQueueStatus(int queue)
+{
+ int err, idx, min, max;
+ snd_seq_queue_status_t *status;
+
+ snd_seq_queue_status_alloca(&status);
+ min = queue < 0 ? 0 : queue;
+ max = queue < 0 ? m_maxQueues : queue + 1;
+
+ for (idx = min; idx < max; ++idx) {
+ if ((err = snd_seq_get_queue_status(m_midiHandle, idx, status)) < 0) {
+
+ if (err == -ENOENT)
+ continue;
+
+ std::cerr << "Client " << idx << " info error: "
+ << snd_strerror(err) << std::endl;
+
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ return ;
+ }
+
+#ifdef DEBUG_ALSA
+ std::cerr << "Queue " << snd_seq_queue_status_get_queue(status)
+ << std::endl;
+
+ std::cerr << "Tick = "
+ << snd_seq_queue_status_get_tick_time(status)
+ << std::endl;
+
+ std::cerr << "Realtime = "
+ << snd_seq_queue_status_get_real_time(status)->tv_sec
+ << "."
+ << snd_seq_queue_status_get_real_time(status)->tv_nsec
+ << std::endl;
+
+ std::cerr << "Flags = 0x"
+ << snd_seq_queue_status_get_status(status)
+ << std::endl;
+#endif
+
+ }
+
+}
+
+
+void
+AlsaDriver::generateTimerList()
+{
+ // Enumerate the available timers
+
+ snd_timer_t *timerHandle;
+
+ snd_timer_id_t *timerId;
+ snd_timer_info_t *timerInfo;
+
+ snd_timer_id_alloca(&timerId);
+ snd_timer_info_alloca(&timerInfo);
+
+ snd_timer_query_t *timerQuery;
+ char timerName[64];
+
+ m_timers.clear();
+
+ if (snd_timer_query_open(&timerQuery, "hw", 0) >= 0) {
+
+ snd_timer_id_set_class(timerId, SND_TIMER_CLASS_NONE);
+
+ while (1) {
+
+ if (snd_timer_query_next_device(timerQuery, timerId) < 0)
+ break;
+ if (snd_timer_id_get_class(timerId) < 0)
+ break;
+
+ AlsaTimerInfo info = {
+ snd_timer_id_get_class(timerId),
+ snd_timer_id_get_sclass(timerId),
+ snd_timer_id_get_card(timerId),
+ snd_timer_id_get_device(timerId),
+ snd_timer_id_get_subdevice(timerId),
+ "",
+ 0
+ };
+
+ if (info.card < 0)
+ info.card = 0;
+ if (info.device < 0)
+ info.device = 0;
+ if (info.subdevice < 0)
+ info.subdevice = 0;
+
+ // std::cerr << "got timer: class " << info.clas << std::endl;
+
+ sprintf(timerName, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i",
+ info.clas, info.sclas, info.card, info.device, info.subdevice);
+
+ if (snd_timer_open(&timerHandle, timerName, SND_TIMER_OPEN_NONBLOCK) < 0) {
+ std::cerr << "Failed to open timer: " << timerName << std::endl;
+ continue;
+ }
+
+ if (snd_timer_info(timerHandle, timerInfo) < 0)
+ continue;
+
+ info.name = snd_timer_info_get_name(timerInfo);
+ info.resolution = snd_timer_info_get_resolution(timerInfo);
+ snd_timer_close(timerHandle);
+
+ // std::cerr << "adding timer: " << info.name << std::endl;
+
+ m_timers.push_back(info);
+ }
+
+ snd_timer_query_close(timerQuery);
+ }
+}
+
+
+std::string
+AlsaDriver::getAutoTimer(bool &wantTimerChecks)
+{
+ Audit audit;
+
+ // Look for the apparent best-choice timer.
+
+ if (m_timers.empty())
+ return "";
+
+ // The system RTC timer ought to be good, but it doesn't look like
+ // a very safe choice -- we've seen some system lockups apparently
+ // connected with use of this timer on 2.6 kernels. So we avoid
+ // using that as an auto option.
+
+ // Looks like our most reliable options for timers are, in order:
+ //
+ // 1. System timer if at 1000Hz, with timer checks (i.e. automatic
+ // drift correction against PCM frame count). Only available
+ // when JACK is running.
+ //
+ // 2. PCM playback timer currently in use by JACK (no drift, but
+ // suffers from jitter).
+ //
+ // 3. System timer if at 1000Hz.
+ //
+ // 4. System RTC timer.
+ //
+ // 5. System timer.
+
+ // As of Linux kernel 2.6.13 (?) the default system timer
+ // resolution has been reduced from 1000Hz to 250Hz, giving us
+ // only 4ms accuracy instead of 1ms. This may be better than the
+ // 10ms available from the stock 2.4 kernel, but it's not enough
+ // for really solid MIDI timing. If JACK is running at 44.1 or
+ // 48KHz with a buffer size less than 256 frames, then the PCM
+ // timer will give us less jitter. Even at 256 frames, it may be
+ // preferable in practice just because it's simpler.
+
+ // However, we can't safely choose the PCM timer over the system
+ // timer unless the latter has really awful resolution, because we
+ // don't know for certain which PCM JACK is using. We guess at
+ // hw:0 for the moment, which gives us a stuck timer problem if
+ // it's actually using something else. So if the system timer
+ // runs at 250Hz, we really have to choose it anyway and just give
+ // a warning.
+
+ bool pcmTimerAccepted = false;
+ wantTimerChecks = false; // for most options
+
+ bool rtcCouldBeOK = false;
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ wantTimerChecks = true;
+ pcmTimerAccepted = true;
+ }
+#endif
+
+ // look for a high frequency system timer
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE)
+ continue;
+ if (i->clas == SND_TIMER_CLASS_GLOBAL) {
+ if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
+ long hz = 1000000000 / i->resolution;
+ if (hz >= 750) {
+ return i->name;
+ }
+ }
+ }
+ }
+
+ // Look for the system RTC timer if available. This has been
+ // known to hang some real-time kernels, but reports suggest that
+ // recent kernels are OK. Avoid if the kernel is older than
+ // 2.6.20 or the ALSA driver is older than 1.0.14.
+
+ if (versionIsAtLeast(getAlsaModuleVersionString(),
+ 1, 0, 14) &&
+ versionIsAtLeast(getKernelVersionString(),
+ 2, 6, 20)) {
+
+ rtcCouldBeOK = true;
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE) continue;
+ if (i->clas == SND_TIMER_CLASS_GLOBAL) {
+ if (i->device == SND_TIMER_GLOBAL_RTC) {
+ return i->name;
+ }
+ }
+ }
+ }
+
+ // look for the first PCM playback timer; that's all we know about
+ // for now (until JACK becomes able to tell us which PCM it's on)
+
+ if (pcmTimerAccepted) {
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE)
+ continue;
+ if (i->clas == SND_TIMER_CLASS_PCM) {
+ if (i->resolution != 0) {
+ long hz = 1000000000 / i->resolution;
+ if (hz >= 750) {
+ wantTimerChecks = false; // pointless with PCM timer
+ return i->name;
+ } else {
+ audit << "PCM timer: inadequate resolution " << i->resolution << std::endl;
+ }
+ }
+ }
+ }
+ }
+
+ // next look for slow, unpopular 100Hz (2.4) or 250Hz (2.6) system timer
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE)
+ continue;
+ if (i->clas == SND_TIMER_CLASS_GLOBAL) {
+ if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
+ audit << "Using low-resolution system timer, sending a warning" << std::endl;
+ if (rtcCouldBeOK) {
+ reportFailure(MappedEvent::WarningImpreciseTimerTryRTC);
+ } else {
+ reportFailure(MappedEvent::WarningImpreciseTimer);
+ }
+ return i->name;
+ }
+ }
+ }
+
+ // falling back to something that almost certainly won't work,
+ // if for any reason all of the above failed
+
+ return m_timers.begin()->name;
+}
+
+
+
+void
+AlsaDriver::generatePortList(AlsaPortList *newPorts)
+{
+ Audit audit;
+ AlsaPortList alsaPorts;
+
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+ int client;
+ unsigned int writeCap = SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE;
+ unsigned int readCap = SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_READ;
+
+ snd_seq_client_info_alloca(&cinfo);
+ snd_seq_client_info_set_client(cinfo, -1);
+
+ audit << std::endl << " ALSA Client information:"
+ << std::endl << std::endl;
+
+ // Get only the client ports we're interested in and store them
+ // for sorting and then device creation.
+ //
+ while (snd_seq_query_next_client(m_midiHandle, cinfo) >= 0) {
+ client = snd_seq_client_info_get_client(cinfo);
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_port_info_set_client(pinfo, client);
+ snd_seq_port_info_set_port(pinfo, -1);
+
+ // Ignore ourselves and the system client
+ //
+ if (client == m_client || client == 0)
+ continue;
+
+ while (snd_seq_query_next_port(m_midiHandle, pinfo) >= 0) {
+ int client = snd_seq_port_info_get_client(pinfo);
+ int port = snd_seq_port_info_get_port(pinfo);
+ unsigned int clientType = snd_seq_client_info_get_type(cinfo);
+ unsigned int portType = snd_seq_port_info_get_type(pinfo);
+ unsigned int capability = snd_seq_port_info_get_capability(pinfo);
+
+
+ if ((((capability & writeCap) == writeCap) ||
+ ((capability & readCap) == readCap)) &&
+ ((capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0)) {
+ audit << " "
+ << client << ","
+ << port << " - ("
+ << snd_seq_client_info_get_name(cinfo) << ", "
+ << snd_seq_port_info_get_name(pinfo) << ")";
+
+ PortDirection direction;
+
+ if ((capability & SND_SEQ_PORT_CAP_DUPLEX) ||
+ ((capability & SND_SEQ_PORT_CAP_WRITE) &&
+ (capability & SND_SEQ_PORT_CAP_READ))) {
+ direction = Duplex;
+ audit << "\t\t\t(DUPLEX)";
+ } else if (capability & SND_SEQ_PORT_CAP_WRITE) {
+ direction = WriteOnly;
+ audit << "\t\t(WRITE ONLY)";
+ } else {
+ direction = ReadOnly;
+ audit << "\t\t(READ ONLY)";
+ }
+
+ audit << " [ctype " << clientType << ", ptype " << portType << ", cap " << capability << "]";
+
+ // Generate a unique name using the client id
+ //
+ char portId[40];
+ sprintf(portId, "%d:%d ", client, port);
+
+ std::string fullClientName =
+ std::string(snd_seq_client_info_get_name(cinfo));
+
+ std::string fullPortName =
+ std::string(snd_seq_port_info_get_name(pinfo));
+
+ std::string name;
+
+ // If the first part of the client name is the same as the
+ // start of the port name, just use the port name. otherwise
+ // concatenate.
+ //
+ int firstSpace = fullClientName.find(" ");
+
+ // If no space is found then we try to match the whole string
+ //
+ if (firstSpace < 0)
+ firstSpace = fullClientName.length();
+
+ if (firstSpace > 0 &&
+ int(fullPortName.length()) >= firstSpace &&
+ fullPortName.substr(0, firstSpace) ==
+ fullClientName.substr(0, firstSpace)) {
+ name = portId + fullPortName;
+ } else {
+ name = portId + fullClientName + ": " + fullPortName;
+ }
+
+ // Sanity check for length
+ //
+ if (name.length() > 35)
+ name = portId + fullPortName;
+
+ if (direction == WriteOnly) {
+ name += " (write)";
+ } else if (direction == ReadOnly) {
+ name += " (read)";
+ } else if (direction == Duplex) {
+ name += " (duplex)";
+ }
+
+ AlsaPortDescription *portDescription =
+ new AlsaPortDescription(
+ Instrument::Midi,
+ name,
+ client,
+ port,
+ clientType,
+ portType,
+ capability,
+ direction);
+
+ if (newPorts &&
+ (getPortName(ClientPortPair(client, port)) == "")) {
+ newPorts->push_back(portDescription);
+ }
+
+ alsaPorts.push_back(portDescription);
+
+ audit << std::endl;
+ }
+ }
+ }
+
+ audit << std::endl;
+
+ // Ok now sort by duplexicity
+ //
+ std::sort(alsaPorts.begin(), alsaPorts.end(), AlsaPortCmp());
+ m_alsaPorts = alsaPorts;
+}
+
+
+void
+AlsaDriver::generateInstruments()
+{
+ // Reset these before each Instrument hunt
+ //
+ int audioCount = 0;
+ getAudioInstrumentNumbers(m_audioRunningId, audioCount);
+ m_midiRunningId = MidiInstrumentBase;
+
+ // Clear these
+ //
+ m_instruments.clear();
+ m_devices.clear();
+ m_devicePortMap.clear();
+ m_suspendedPortMap.clear();
+
+ AlsaPortList::iterator it = m_alsaPorts.begin();
+ for (; it != m_alsaPorts.end(); it++) {
+ if ((*it)->m_client == m_client) {
+ std::cerr << "(Ignoring own port " << (*it)->m_client
+ << ":" << (*it)->m_port << ")" << std::endl;
+ continue;
+ } else if ((*it)->m_client == 0) {
+ std::cerr << "(Ignoring system port " << (*it)->m_client
+ << ":" << (*it)->m_port << ")" << std::endl;
+ continue;
+ }
+
+ if ((*it)->isWriteable()) {
+ MappedDevice *device = createMidiDevice(*it, MidiDevice::Play);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create play device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+ }
+ }
+ if ((*it)->isReadable()) {
+ MappedDevice *device = createMidiDevice(*it, MidiDevice::Record);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create record device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ m_devices.push_back(device);
+ }
+ }
+ }
+
+#ifdef HAVE_DSSI
+ // Create a number of soft synth Instruments
+ //
+ {
+ MappedInstrument *instr;
+ char number[100];
+ InstrumentId first;
+ int count;
+ getSoftSynthInstrumentNumbers(first, count);
+
+ DeviceId ssiDeviceId = getSpareDeviceId();
+
+ if (m_driverStatus & AUDIO_OK) {
+ for (int i = 0; i < count; ++i) {
+ sprintf(number, " #%d", i + 1);
+ std::string name = "Synth plugin" + std::string(number);
+ instr = new MappedInstrument(Instrument::SoftSynth,
+ i,
+ first + i,
+ name,
+ ssiDeviceId);
+ m_instruments.push_back(instr);
+
+ m_studio->createObject(MappedObject::AudioFader,
+ first + i);
+ }
+
+ MappedDevice *device =
+ new MappedDevice(ssiDeviceId,
+ Device::SoftSynth,
+ "Synth plugin",
+ "Soft synth connection");
+ m_devices.push_back(device);
+ }
+ }
+#endif
+
+#ifdef HAVE_LIBJACK
+
+ // Create a number of audio Instruments - these are just
+ // logical Instruments anyway and so we can create as
+ // many as we like and then use them as Tracks.
+ //
+ {
+ MappedInstrument *instr;
+ char number[100];
+ std::string audioName;
+
+ DeviceId audioDeviceId = getSpareDeviceId();
+
+ if (m_driverStatus & AUDIO_OK)
+ {
+ for (int channel = 0; channel < audioCount; ++channel) {
+ sprintf(number, " #%d", channel + 1);
+ audioName = "Audio" + std::string(number);
+ instr = new MappedInstrument(Instrument::Audio,
+ channel,
+ m_audioRunningId,
+ audioName,
+ audioDeviceId);
+ m_instruments.push_back(instr);
+
+ // Create a fader with a matching id - this is the starting
+ // point for all audio faders.
+ //
+ m_studio->createObject(MappedObject::AudioFader,
+ m_audioRunningId);
+
+ /*
+ std::cerr << "AlsaDriver::generateInstruments - "
+ << "added audio fader (id=" << m_audioRunningId
+ << ")" << std::endl;
+ */
+
+ m_audioRunningId++;
+ }
+
+ // Create audio device
+ //
+ MappedDevice *device =
+ new MappedDevice(audioDeviceId,
+ Device::Audio,
+ "Audio",
+ "Audio connection");
+ m_devices.push_back(device);
+ }
+ }
+#endif
+
+}
+
+MappedDevice *
+AlsaDriver::createMidiDevice(AlsaPortDescription *port,
+ MidiDevice::DeviceDirection reqDirection)
+{
+ char deviceName[100];
+ std::string connectionName("");
+ Audit audit;
+
+ static int unknownCounter;
+
+ static int counters[3][2]; // [system/hardware/software][out/in]
+ const int UNKNOWN = -1, SYSTEM = 0, HARDWARE = 1, SOFTWARE = 2;
+ static const char *firstNames[4][2] = {
+ { "MIDI output system device", "MIDI input system device"
+ },
+ { "MIDI external device", "MIDI hardware input device" },
+ { "MIDI software device", "MIDI software input" }
+ };
+ static const char *countedNames[4][2] = {
+ { "MIDI output system device %d", "MIDI input system device %d"
+ },
+ { "MIDI external device %d", "MIDI hardware input device %d" },
+ { "MIDI software device %d", "MIDI software input %d" }
+ };
+
+ static int specificCounters[2];
+ static const char *specificNames[2] = {
+ "MIDI soundcard synth", "MIDI soft synth",
+ };
+ static const char *specificCountedNames[2] = {
+ "MIDI soundcard synth %d", "MIDI soft synth %d",
+ };
+
+ DeviceId deviceId = getSpareDeviceId();
+
+ if (port) {
+
+ if (reqDirection == MidiDevice::Record && !port->isReadable())
+ return 0;
+ if (reqDirection == MidiDevice::Play && !port->isWriteable())
+ return 0;
+
+ int category = UNKNOWN;
+ bool noConnect = false;
+ bool isSynth = false;
+ bool synthKnown = false;
+
+ if (port->m_client < 16) {
+
+ category = SYSTEM;
+ noConnect = true;
+ isSynth = false;
+ synthKnown = true;
+
+ } else {
+
+#ifdef SND_SEQ_PORT_TYPE_HARDWARE
+ if (port->m_portType & SND_SEQ_PORT_TYPE_HARDWARE) {
+ category = HARDWARE;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_SOFTWARE
+ if (port->m_portType & SND_SEQ_PORT_TYPE_SOFTWARE) {
+ category = SOFTWARE;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_SYNTHESIZER
+ if (port->m_portType & SND_SEQ_PORT_TYPE_SYNTHESIZER) {
+ isSynth = true;
+ synthKnown = true;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_APPLICATION
+ if (port->m_portType & SND_SEQ_PORT_TYPE_APPLICATION) {
+ category = SOFTWARE;
+ isSynth = false;
+ synthKnown = true;
+ }
+#endif
+
+ if (category == UNKNOWN) {
+
+ if (port->m_client < 64) {
+
+ if (versionIsAtLeast(getAlsaModuleVersionString(),
+ 1, 0, 11)) {
+
+ category = HARDWARE;
+
+ } else {
+
+ category = SYSTEM;
+ noConnect = true;
+ }
+
+ } else if (port->m_client < 128) {
+
+ category = HARDWARE;
+
+ } else {
+
+ category = SOFTWARE;
+ }
+ }
+ }
+
+ bool haveName = false;
+
+ if (!synthKnown) {
+
+ if (category != SYSTEM && reqDirection == MidiDevice::Play) {
+
+ // We assume GM/GS/XG/MT32 devices are synths.
+
+ bool isSynth = (port->m_portType &
+ (SND_SEQ_PORT_TYPE_MIDI_GM |
+ SND_SEQ_PORT_TYPE_MIDI_GS |
+ SND_SEQ_PORT_TYPE_MIDI_XG |
+ SND_SEQ_PORT_TYPE_MIDI_MT32));
+
+ if (!isSynth &&
+ (port->m_name.find("ynth") < port->m_name.length()))
+ isSynth = true;
+ if (!isSynth &&
+ (port->m_name.find("nstrument") < port->m_name.length()))
+ isSynth = true;
+ if (!isSynth &&
+ (port->m_name.find("VSTi") < port->m_name.length()))
+ isSynth = true;
+
+ } else {
+ isSynth = false;
+ }
+ }
+
+ if (isSynth) {
+ int clientType = (category == SOFTWARE) ? 1 : 0;
+ if (specificCounters[clientType] == 0) {
+ sprintf(deviceName, specificNames[clientType]);
+ ++specificCounters[clientType];
+ } else {
+ sprintf(deviceName,
+ specificCountedNames[clientType],
+ ++specificCounters[clientType]);
+ }
+ haveName = true;
+ }
+
+ if (!haveName) {
+ if (counters[category][reqDirection] == 0) {
+ sprintf(deviceName, firstNames[category][reqDirection]);
+ ++counters[category][reqDirection];
+ } else {
+ sprintf(deviceName,
+ countedNames[category][reqDirection],
+ ++counters[category][reqDirection]);
+ }
+ }
+
+ if (!noConnect) {
+ m_devicePortMap[deviceId] = ClientPortPair(port->m_client,
+ port->m_port);
+ connectionName = port->m_name;
+ }
+
+ audit << "Creating device " << deviceId << " in "
+ << (reqDirection == MidiDevice::Play ? "Play" : "Record")
+ << " mode for connection " << port->m_name
+ << (noConnect ? " (not connecting)" : "")
+ << "\nDefault device name for this device is "
+ << deviceName << std::endl;
+
+ } else { // !port
+
+ sprintf(deviceName, "Anonymous MIDI device %d", ++unknownCounter);
+
+ audit << "Creating device " << deviceId << " in "
+ << (reqDirection == MidiDevice::Play ? "Play" : "Record")
+ << " mode -- no connection available "
+ << "\nDefault device name for this device is "
+ << deviceName << std::endl;
+ }
+
+ if (reqDirection == MidiDevice::Play) {
+
+ QString portName;
+
+ if (QString(deviceName).startsWith("Anonymous MIDI device ")) {
+ portName = QString("out %1")
+ .arg(m_outputPorts.size() + 1);
+ } else {
+ portName = QString("out %1 - %2")
+ .arg(m_outputPorts.size() + 1)
+ .arg(deviceName);
+ }
+
+ int outputPort = checkAlsaError(snd_seq_create_simple_port
+ (m_midiHandle,
+ portName,
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_APPLICATION),
+ "createMidiDevice - can't create output port");
+
+ if (outputPort >= 0) {
+
+ std::cerr << "CREATED OUTPUT PORT " << outputPort << ":" << portName << " for device " << deviceId << std::endl;
+
+ m_outputPorts[deviceId] = outputPort;
+
+ if (port) {
+ if (connectionName != "") {
+ std::cerr << "Connecting my port " << outputPort << " to " << port->m_client << ":" << port->m_port << " on initialisation" << std::endl;
+ snd_seq_connect_to(m_midiHandle,
+ outputPort,
+ port->m_client,
+ port->m_port);
+ if (m_midiSyncAutoConnect) {
+ snd_seq_connect_to(m_midiHandle,
+ m_syncOutputPort,
+ port->m_client,
+ port->m_port);
+ }
+ }
+ std::cerr << "done" << std::endl;
+ }
+ }
+ }
+
+ MappedDevice *device = new MappedDevice(deviceId,
+ Device::Midi,
+ deviceName,
+ connectionName);
+ device->setDirection(reqDirection);
+ return device;
+}
+
+DeviceId
+AlsaDriver::getSpareDeviceId()
+{
+ std::set
+ <DeviceId> ids;
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+ ids.insert(m_devices[i]->getId());
+ }
+
+ DeviceId id = 0;
+ while (ids.find(id) != ids.end())
+ ++id;
+ return id;
+}
+
+void
+AlsaDriver::addInstrumentsForDevice(MappedDevice *device)
+{
+ std::string channelName;
+ char number[100];
+
+ for (int channel = 0; channel < 16; ++channel) {
+ // Create MappedInstrument for export to GUI
+ //
+ // name is just number, derive rest from device at gui
+ sprintf(number, "#%d", channel + 1);
+ channelName = std::string(number);
+
+ if (channel == 9)
+ channelName = std::string("#10[D]");
+ MappedInstrument *instr = new MappedInstrument(Instrument::Midi,
+ channel,
+ m_midiRunningId++,
+ channelName,
+ device->getId());
+ m_instruments.push_back(instr);
+ }
+}
+
+
+bool
+AlsaDriver::canReconnect(Device::DeviceType type)
+{
+ return (type == Device::Midi);
+}
+
+DeviceId
+AlsaDriver::addDevice(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction)
+{
+ if (type == Device::Midi) {
+
+ MappedDevice *device = createMidiDevice(0, direction);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Device creation failed" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ insertMappedEventForReturn(mE);
+
+ return device->getId();
+ }
+ }
+
+ return Device::NO_DEVICE;
+}
+
+void
+AlsaDriver::removeDevice(DeviceId id)
+{
+ DeviceIntMap::iterator i1 = m_outputPorts.find(id);
+ if (i1 == m_outputPorts.end()) {
+ std::cerr << "WARNING: AlsaDriver::removeDevice: Cannot find device "
+ << id << " in port map" << std::endl;
+ return ;
+ }
+ checkAlsaError( snd_seq_delete_port(m_midiHandle, i1->second),
+ "removeDevice");
+ m_outputPorts.erase(i1);
+
+ for (MappedDeviceList::iterator i = m_devices.end();
+ i != m_devices.begin(); ) {
+
+ --i;
+
+ if ((*i)->getId() == id) {
+ delete *i;
+ m_devices.erase(i);
+ }
+ }
+
+ for (MappedInstrumentList::iterator i = m_instruments.end();
+ i != m_instruments.begin(); ) {
+
+ --i;
+
+ if ((*i)->getDevice() == id) {
+ delete *i;
+ m_instruments.erase(i);
+ }
+ }
+
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ insertMappedEventForReturn(mE);
+}
+
+void
+AlsaDriver::renameDevice(DeviceId id, QString name)
+{
+ DeviceIntMap::iterator i = m_outputPorts.find(id);
+ if (i == m_outputPorts.end()) {
+ std::cerr << "WARNING: AlsaDriver::renameDevice: Cannot find device "
+ << id << " in port map" << std::endl;
+ return ;
+ }
+
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_get_port_info(m_midiHandle, i->second, pinfo);
+
+ QString oldName = snd_seq_port_info_get_name(pinfo);
+ int sep = oldName.find(" - ");
+
+ QString newName;
+
+ if (name.startsWith("Anonymous MIDI device ")) {
+ if (sep < 0)
+ sep = 0;
+ newName = oldName.left(sep);
+ } else if (sep < 0) {
+ newName = oldName + " - " + name;
+ } else {
+ newName = oldName.left(sep + 3) + name;
+ }
+
+ snd_seq_port_info_set_name(pinfo, newName.data());
+ checkAlsaError(snd_seq_set_port_info(m_midiHandle, i->second, pinfo),
+ "renameDevice");
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+ if (m_devices[i]->getId() == id) {
+ m_devices[i]->setName(newName.data());
+ break;
+ }
+ }
+
+ std::cerr << "Renamed " << m_client << ":" << i->second << " to " << name << std::endl;
+}
+
+ClientPortPair
+AlsaDriver::getPortByName(std::string name)
+{
+ for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
+ if (m_alsaPorts[i]->m_name == name) {
+ return ClientPortPair(m_alsaPorts[i]->m_client,
+ m_alsaPorts[i]->m_port);
+ }
+ }
+ return ClientPortPair( -1, -1);
+}
+
+std::string
+AlsaDriver::getPortName(ClientPortPair port)
+{
+ for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
+ if (m_alsaPorts[i]->m_client == port.first &&
+ m_alsaPorts[i]->m_port == port.second) {
+ return m_alsaPorts[i]->m_name;
+ }
+ }
+ return "";
+}
+
+
+unsigned int
+AlsaDriver::getConnections(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction)
+{
+ if (type != Device::Midi)
+ return 0;
+
+ int count = 0;
+ for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
+ if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
+ (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+QString
+AlsaDriver::getConnection(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction,
+ unsigned int connectionNo)
+{
+ if (type != Device::Midi)
+ return "";
+
+ AlsaPortList tempList;
+ for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
+ if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
+ (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
+ tempList.push_back(m_alsaPorts[j]);
+ }
+ }
+
+ if (connectionNo < tempList.size()) {
+ return tempList[connectionNo]->m_name.c_str();
+ }
+
+ return "";
+}
+
+void
+AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection)
+{
+ ClientPortPair pair( -1, -1);
+ if (connection && connection != "") {
+ pair = getPortByName(connection.data());
+ }
+ setConnectionToDevice(device, connection, pair);
+}
+
+void
+AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection,
+ const ClientPortPair &pair)
+{
+ QString prevConnection = device.getConnection().c_str();
+ device.setConnection(connection.data());
+
+ if (device.getDirection() == MidiDevice::Play) {
+
+ DeviceIntMap::iterator j = m_outputPorts.find(device.getId());
+
+ if (j != m_outputPorts.end()) {
+
+ if (prevConnection != "") {
+ ClientPortPair prevPair = getPortByName(prevConnection.data());
+ if (prevPair.first >= 0 && prevPair.second >= 0) {
+
+ std::cerr << "Disconnecting my port " << j->second << " from " << prevPair.first << ":" << prevPair.second << " on reconnection" << std::endl;
+ snd_seq_disconnect_to(m_midiHandle,
+ j->second,
+ prevPair.first,
+ prevPair.second);
+
+ if (m_midiSyncAutoConnect) {
+ bool foundElsewhere = false;
+ for (MappedDeviceList::iterator k = m_devices.begin();
+ k != m_devices.end(); ++k) {
+ if ((*k)->getId() != device.getId()) {
+ if ((*k)->getConnection() == prevConnection.data()) {
+ foundElsewhere = true;
+ break;
+ }
+ }
+ }
+ if (!foundElsewhere) {
+ snd_seq_disconnect_to(m_midiHandle,
+ m_syncOutputPort,
+ pair.first,
+ pair.second);
+ }
+ }
+ }
+ }
+
+ if (pair.first >= 0 && pair.second >= 0) {
+ std::cerr << "Connecting my port " << j->second << " to " << pair.first << ":" << pair.second << " on reconnection" << std::endl;
+ snd_seq_connect_to(m_midiHandle,
+ j->second,
+ pair.first,
+ pair.second);
+ if (m_midiSyncAutoConnect) {
+ snd_seq_connect_to(m_midiHandle,
+ m_syncOutputPort,
+ pair.first,
+ pair.second);
+ }
+ }
+ }
+ }
+}
+
+void
+AlsaDriver::setConnection(DeviceId id, QString connection)
+{
+ Audit audit;
+ ClientPortPair port(getPortByName(connection.data()));
+
+ if (port.first != -1 && port.second != -1) {
+
+ m_devicePortMap[id] = port;
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+
+ if (m_devices[i]->getId() == id) {
+ setConnectionToDevice(*m_devices[i], connection, port);
+
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ insertMappedEventForReturn(mE);
+
+ break;
+ }
+ }
+ }
+}
+
+void
+AlsaDriver::setPlausibleConnection(DeviceId id, QString idealConnection)
+{
+ Audit audit;
+ ClientPortPair port(getPortByName(idealConnection.data()));
+
+ audit << "AlsaDriver::setPlausibleConnection: connection like "
+ << idealConnection << " requested for device " << id << std::endl;
+
+ if (port.first != -1 && port.second != -1) {
+
+ m_devicePortMap[id] = port;
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+
+ if (m_devices[i]->getId() == id) {
+ setConnectionToDevice(*m_devices[i], idealConnection, port);
+ break;
+ }
+ }
+
+ audit << "AlsaDriver::setPlausibleConnection: exact match available"
+ << std::endl;
+ return ;
+ }
+
+ // What we want is a connection that:
+ //
+ // * is in the right "class" (the 0-63/64-127/128+ range of client id)
+ // * has at least some text in common
+ // * is not yet in use for any device.
+ //
+ // To do this, we exploit our privileged position as part of AlsaDriver
+ // and use our knowledge of how connection strings are made (see
+ // AlsaDriver::generatePortList above) to pick out the relevant parts
+ // of the requested string.
+
+ int client = -1;
+ int colon = idealConnection.find(":");
+ if (colon >= 0)
+ client = idealConnection.left(colon).toInt();
+
+ int portNo = -1;
+ if (client > 0) {
+ QString remainder = idealConnection.mid(colon + 1);
+ int space = remainder.find(" ");
+ if (space >= 0)
+ portNo = remainder.left(space).toInt();
+ }
+
+ int firstSpace = idealConnection.find(" ");
+ int endOfText = idealConnection.find(QRegExp("[^\\w ]"), firstSpace);
+
+ QString text;
+ if (endOfText < 2) {
+ text = idealConnection.mid(firstSpace + 1);
+ } else {
+ text = idealConnection.mid(firstSpace + 1, endOfText - firstSpace - 2);
+ }
+
+ for (int testUsed = 1; testUsed >= 0; --testUsed) {
+
+ for (int testNumbers = 1; testNumbers >= 0; --testNumbers) {
+
+ for (int testName = 1; testName >= 0; --testName) {
+
+ int fitness =
+ (testName << 3) +
+ (testNumbers << 2) +
+ (testUsed << 1) + 1;
+
+ for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
+
+ AlsaPortDescription *port = m_alsaPorts[i];
+
+ if (client > 0) {
+
+ if (port->m_client / 64 != client / 64)
+ continue;
+
+ if (testNumbers) {
+ // We always check the client class (above).
+ // But we also prefer to have something in
+ // common with client or port number, at least
+ // for ports that aren't used elsewhere
+ // already. We don't check both because the
+ // chances are the entire string would already
+ // have matched if both figures did; instead
+ // we check the port if it's > 0 (handy for
+ // e.g. matching the MIDI synth port on a
+ // multi-port soundcard) and the client
+ // otherwise.
+ if (portNo > 0) {
+ if (port->m_port != portNo)
+ continue;
+ } else {
+ if (port->m_client != client)
+ continue;
+ }
+ }
+ }
+
+ if (testName && text != "" &&
+ !QString(port->m_name.c_str()).contains(text))
+ continue;
+
+ if (testUsed) {
+ bool used = false;
+ for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
+ dpmi != m_devicePortMap.end(); ++dpmi) {
+ if (dpmi->second.first == port->m_client &&
+ dpmi->second.second == port->m_port) {
+ used = true;
+ break;
+ }
+ }
+ if (used)
+ continue;
+ }
+
+ // OK, this one will do
+
+ audit << "AlsaDriver::setPlausibleConnection: fuzzy match "
+ << port->m_name << " available with fitness "
+ << fitness << std::endl;
+
+ m_devicePortMap[id] = ClientPortPair(port->m_client, port->m_port);
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+
+ if (m_devices[i]->getId() == id) {
+ setConnectionToDevice(*m_devices[i],
+ port->m_name.c_str(),
+ m_devicePortMap[id]);
+
+ // in this case we don't request a device resync,
+ // because this is only invoked at times such as
+ // file load when the GUI is well aware that the
+ // whole situation is in upheaval anyway
+
+ return ;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ audit << "AlsaDriver::setPlausibleConnection: nothing suitable available"
+ << std::endl;
+}
+
+
+void
+AlsaDriver::checkTimerSync(size_t frames)
+{
+ if (!m_doTimerChecks)
+ return ;
+
+#ifdef HAVE_LIBJACK
+
+ if (!m_jackDriver || !m_queueRunning || frames == 0 ||
+ (getMTCStatus() == TRANSPORT_SLAVE)) {
+ m_firstTimerCheck = true;
+ return ;
+ }
+
+ static RealTime startAlsaTime;
+ static size_t startJackFrames = 0;
+ static size_t lastJackFrames = 0;
+
+ size_t nowJackFrames = m_jackDriver->getFramesProcessed();
+ RealTime nowAlsaTime = getAlsaTime();
+
+ if (m_firstTimerCheck ||
+ (nowJackFrames <= lastJackFrames) ||
+ (nowAlsaTime <= startAlsaTime)) {
+
+ startAlsaTime = nowAlsaTime;
+ startJackFrames = nowJackFrames;
+ lastJackFrames = nowJackFrames;
+
+ m_firstTimerCheck = false;
+ return ;
+ }
+
+ RealTime jackDiff = RealTime::frame2RealTime
+ (nowJackFrames - startJackFrames,
+ m_jackDriver->getSampleRate());
+
+ RealTime alsaDiff = nowAlsaTime - startAlsaTime;
+
+ if (alsaDiff > RealTime(10, 0)) {
+
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cout << "\nALSA:" << startAlsaTime << "\t->" << nowAlsaTime << "\nJACK: " << startJackFrames << "\t\t-> " << nowJackFrames << std::endl;
+ std::cout << "ALSA diff: " << alsaDiff << "\nJACK diff: " << jackDiff << std::endl;
+ }
+#endif
+
+ double ratio = (jackDiff - alsaDiff) / alsaDiff;
+
+ if (fabs(ratio) > 0.1) {
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cout << "Ignoring excessive ratio " << ratio
+ << ", hoping for a more likely result next time"
+ << std::endl;
+ }
+#endif
+
+ } else if (fabs(ratio) > 0.000001) {
+
+#ifdef DEBUG_ALSA
+ if (alsaDiff > RealTime::zeroTime && jackDiff > RealTime::zeroTime) {
+ if (!m_playing) {
+ if (jackDiff < alsaDiff) {
+ std::cout << "<<<< ALSA timer is faster by " << 100.0 * ((alsaDiff - jackDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
+ } else {
+ std::cout << ">>>> JACK timer is faster by " << 100.0 * ((jackDiff - alsaDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
+ }
+ }
+ }
+#endif
+
+ m_timerRatio = ratio;
+ m_timerRatioCalculated = true;
+ }
+
+ m_firstTimerCheck = true;
+ }
+#endif
+}
+
+
+unsigned int
+AlsaDriver::getTimers()
+{
+ return m_timers.size() + 1; // one extra for auto
+}
+
+QString
+AlsaDriver::getTimer(unsigned int n)
+{
+ if (n == 0)
+ return AUTO_TIMER_NAME;
+ else
+ return m_timers[n -1].name.c_str();
+}
+
+QString
+AlsaDriver::getCurrentTimer()
+{
+ return m_currentTimer.c_str();
+}
+
+void
+AlsaDriver::setCurrentTimer(QString timer)
+{
+ Audit audit;
+
+ if (timer == getCurrentTimer())
+ return ;
+
+ std::cerr << "AlsaDriver::setCurrentTimer(" << timer << ")" << std::endl;
+
+ std::string name(timer.data());
+
+ if (name == AUTO_TIMER_NAME) {
+ name = getAutoTimer(m_doTimerChecks);
+ } else {
+ m_doTimerChecks = false;
+ }
+ m_timerRatioCalculated = false;
+
+ // Stop and restart the queue around the timer change. We don't
+ // call stopClocks/startClocks here because they do the wrong
+ // thing if we're currently playing and on the JACK transport.
+
+ m_queueRunning = false;
+ checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "setCurrentTimer(): stopping queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to stop queue");
+
+ snd_seq_event_t event;
+ snd_seq_ev_clear(&event);
+ snd_seq_real_time_t z = { 0, 0 };
+ snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
+ snd_seq_ev_set_direct(&event);
+ checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
+ 0, &event), "setCurrentTimer(): control queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to control queue");
+ m_alsaPlayStartTime = RealTime::zeroTime;
+
+ for (unsigned int i = 0; i < m_timers.size(); ++i) {
+ if (m_timers[i].name == name) {
+
+ snd_seq_queue_timer_t *timer;
+ snd_timer_id_t *timerid;
+
+ snd_seq_queue_timer_alloca(&timer);
+ snd_seq_get_queue_timer(m_midiHandle, m_queue, timer);
+
+ snd_timer_id_alloca(&timerid);
+ snd_timer_id_set_class(timerid, m_timers[i].clas);
+ snd_timer_id_set_sclass(timerid, m_timers[i].sclas);
+ snd_timer_id_set_card(timerid, m_timers[i].card);
+ snd_timer_id_set_device(timerid, m_timers[i].device);
+ snd_timer_id_set_subdevice(timerid, m_timers[i].subdevice);
+
+ snd_seq_queue_timer_set_id(timer, timerid);
+ snd_seq_set_queue_timer(m_midiHandle, m_queue, timer);
+
+ if (m_doTimerChecks) {
+ audit << " Current timer set to \"" << name << "\" with timer checks"
+ << std::endl;
+ } else {
+ audit << " Current timer set to \"" << name << "\""
+ << std::endl;
+ }
+
+ if (m_timers[i].clas == SND_TIMER_CLASS_GLOBAL &&
+ m_timers[i].device == SND_TIMER_GLOBAL_SYSTEM) {
+ long hz = 1000000000 / m_timers[i].resolution;
+ if (hz < 900) {
+ audit << " WARNING: using system timer with only "
+ << hz << "Hz resolution!" << std::endl;
+ }
+ }
+
+ break;
+ }
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver)
+ m_jackDriver->prebufferAudio();
+#endif
+
+ checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "checkAlsaError(): continue queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to continue queue");
+ m_queueRunning = true;
+
+ m_firstTimerCheck = true;
+}
+
+bool
+AlsaDriver::initialise()
+{
+ bool result = true;
+
+ initialiseAudio();
+ result = initialiseMidi();
+
+ return result;
+}
+
+
+
+// Set up queue, client and port
+//
+bool
+AlsaDriver::initialiseMidi()
+{
+ Audit audit;
+
+ // Create a non-blocking handle.
+ //
+ if (snd_seq_open(&m_midiHandle,
+ "default",
+ SND_SEQ_OPEN_DUPLEX,
+ SND_SEQ_NONBLOCK) < 0) {
+ audit << "AlsaDriver::initialiseMidi - "
+ << "couldn't open sequencer - " << snd_strerror(errno)
+ << " - perhaps you need to modprobe snd-seq-midi."
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ return false;
+ }
+
+ snd_seq_set_client_name(m_midiHandle, "rosegarden");
+
+ if ((m_client = snd_seq_client_id(m_midiHandle)) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::initialiseMidi - can't create client"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Create a queue
+ //
+ if ((m_queue = snd_seq_alloc_named_queue(m_midiHandle,
+ "Rosegarden queue")) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::initialiseMidi - can't allocate queue"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Create the input port
+ //
+ snd_seq_port_info_t *pinfo;
+
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_port_info_set_capability(pinfo,
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_WRITE );
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_midi_channels(pinfo, 16);
+ /* we want to know when the events got delivered to us */
+ snd_seq_port_info_set_timestamping(pinfo, 1);
+ snd_seq_port_info_set_timestamp_real(pinfo, 1);
+ snd_seq_port_info_set_timestamp_queue(pinfo, m_queue);
+ snd_seq_port_info_set_name(pinfo, "record in");
+
+ if (checkAlsaError(snd_seq_create_port(m_midiHandle, pinfo),
+ "initialiseMidi - can't create input port") < 0)
+ return false;
+ m_inputPort = snd_seq_port_info_get_port(pinfo);
+
+ // Subscribe the input port to the ALSA Announce port
+ // to receive notifications when clients, ports and subscriptions change
+ snd_seq_connect_from( m_midiHandle, m_inputPort,
+ SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE );
+
+ m_midiInputPortConnected = true;
+
+ // Set the input queue size
+ //
+ if (snd_seq_set_client_pool_output(m_midiHandle, 2000) < 0 ||
+ snd_seq_set_client_pool_input(m_midiHandle, 2000) < 0 ||
+ snd_seq_set_client_pool_output_room(m_midiHandle, 2000) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::initialiseMidi - "
+ << "can't modify pool parameters"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Create sync output now as well
+ m_syncOutputPort = checkAlsaError(snd_seq_create_simple_port
+ (m_midiHandle,
+ "sync out",
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_APPLICATION),
+ "initialiseMidi - can't create sync output port");
+
+ // and port for hardware controller
+ m_controllerPort = checkAlsaError(snd_seq_create_simple_port
+ (m_midiHandle,
+ "external controller",
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_READ |
+ SND_SEQ_PORT_CAP_SUBS_WRITE,
+ SND_SEQ_PORT_TYPE_APPLICATION),
+ "initialiseMidi - can't create controller port");
+
+ getSystemInfo();
+
+ generatePortList();
+ generateInstruments();
+
+ // Modify status with MIDI success
+ //
+ m_driverStatus |= MIDI_OK;
+
+ generateTimerList();
+ setCurrentTimer(AUTO_TIMER_NAME);
+
+ // Start the timer
+ if (checkAlsaError(snd_seq_start_queue(m_midiHandle, m_queue, NULL),
+ "initialiseMidi(): couldn't start queue") < 0) {
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ return false;
+ }
+
+ m_queueRunning = true;
+
+ // process anything pending
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "initialiseMidi(): couldn't drain output");
+
+ audit << "AlsaDriver::initialiseMidi - initialised MIDI subsystem"
+ << std::endl << std::endl;
+
+ return true;
+}
+
+// We don't even attempt to use ALSA audio. We just use JACK instead.
+// See comment at the top of this file and jackProcess() for further
+// information on how we use this.
+//
+void
+AlsaDriver::initialiseAudio()
+{
+#ifdef HAVE_LIBJACK
+ m_jackDriver = new JackDriver(this);
+
+ if (m_jackDriver->isOK()) {
+ m_driverStatus |= AUDIO_OK;
+ } else {
+ delete m_jackDriver;
+ m_jackDriver = 0;
+ }
+#endif
+}
+
+void
+AlsaDriver::initialisePlayback(const RealTime &position)
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "\n\nAlsaDriver - initialisePlayback" << std::endl;
+#endif
+
+ // now that we restart the queue at each play, the origin is always zero
+ m_alsaPlayStartTime = RealTime::zeroTime;
+ m_playStartPosition = position;
+
+ m_startPlayback = true;
+
+ m_mtcFirstTime = -1;
+ m_mtcSigmaE = 0;
+ m_mtcSigmaC = 0;
+
+ if (getMMCStatus() == TRANSPORT_MASTER) {
+ sendMMC(127, MIDI_MMC_PLAY, true, "");
+ m_eat_mtc = 0;
+ }
+
+ if (getMTCStatus() == TRANSPORT_MASTER) {
+ insertMTCFullFrame(position);
+ }
+
+ // If MIDI Sync is enabled then adjust for the MIDI Clock to
+ // synchronise the sequencer with the clock.
+ //
+ if (getMIDISyncStatus() == TRANSPORT_MASTER) {
+ // Send the Song Position Pointer for MIDI CLOCK positioning
+ //
+ // Get time from current alsa time to start of alsa timing -
+ // add the initial starting point and divide by the MIDI Beat
+ // length. The SPP is is the MIDI Beat upon which to start the song.
+ // Songs are always assumed to start on a MIDI Beat of 0. Each MIDI
+ // Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th
+ // note (since there are 24 MIDI Clocks in a quarter note).
+ //
+ long spp =
+ long(((getAlsaTime() - m_alsaPlayStartTime + m_playStartPosition) /
+ m_midiClockInterval) / 6.0 );
+
+ // Ok now we have the new SPP - stop the transport and restart with the
+ // new value.
+ //
+ sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
+
+ signed int args = spp;
+ sendSystemDirect(SND_SEQ_EVENT_SONGPOS, &args);
+
+ // Now send the START/CONTINUE
+ //
+ if (m_playStartPosition == RealTime::zeroTime)
+ sendSystemQueued(SND_SEQ_EVENT_START, "",
+ m_alsaPlayStartTime);
+ else
+ sendSystemQueued(SND_SEQ_EVENT_CONTINUE, "",
+ m_alsaPlayStartTime);
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_needJackStart = NeedJackStart;
+ }
+#endif
+}
+
+
+void
+AlsaDriver::stopPlayback()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "\n\nAlsaDriver - stopPlayback" << std::endl;
+#endif
+
+ if (getMIDISyncStatus() == TRANSPORT_MASTER) {
+ sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
+ }
+
+ if (getMMCStatus() == TRANSPORT_MASTER) {
+ sendMMC(127, MIDI_MMC_STOP, true, "");
+ //<VN> need to throw away the next MTC event
+ m_eat_mtc = 3;
+ }
+
+ allNotesOff();
+ m_playing = false;
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_jackDriver->stopTransport();
+ m_needJackStart = NeedNoJackStart;
+ }
+#endif
+
+ // Flush the output and input queues
+ //
+ snd_seq_remove_events_t *info;
+ snd_seq_remove_events_alloca(&info);
+ snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_INPUT |
+ SND_SEQ_REMOVE_OUTPUT);
+ snd_seq_remove_events(m_midiHandle, info);
+
+ // send sounds-off to all play devices
+ //
+ for (MappedDeviceList::iterator i = m_devices.begin(); i != m_devices.end(); ++i) {
+ if ((*i)->getDirection() == MidiDevice::Play) {
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_SUSTAIN, 0);
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+ }
+ }
+
+ punchOut();
+
+ stopClocks(); // Resets ALSA timer to zero
+
+ clearAudioQueue();
+
+ startClocksApproved(); // restarts ALSA timer without starting JACK transport
+}
+
+void
+AlsaDriver::punchOut()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::punchOut" << std::endl;
+#endif
+
+#ifdef HAVE_LIBJACK
+ // Close any recording file
+ if (m_recordStatus == RECORD_ON) {
+ for (InstrumentSet::const_iterator i = m_recordingInstruments.begin();
+ i != m_recordingInstruments.end(); ++i) {
+
+ InstrumentId id = *i;
+
+ if (id >= AudioInstrumentBase &&
+ id < MidiInstrumentBase) {
+
+ AudioFileId auid = 0;
+ if (m_jackDriver && m_jackDriver->closeRecordFile(id, auid)) {
+
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::stopPlayback: sending back to GUI for instrument " << id << std::endl;
+#endif
+
+ // Create event to return to gui to say that we've
+ // completed an audio file and we can generate a
+ // preview for it now.
+ //
+ // nasty hack -- don't have right audio id here, and
+ // the sequencer will wipe out the instrument id and
+ // replace it with currently-selected one in gui --
+ // so use audio id slot to pass back instrument id
+ // and handle accordingly in gui
+ try {
+ MappedEvent *mE =
+ new MappedEvent(id,
+ MappedEvent::AudioGeneratePreview,
+ id % 256,
+ id / 256);
+
+ // send completion event
+ insertMappedEventForReturn(mE);
+ } catch (...) {
+ ;
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ // Change recorded state if any set
+ //
+ if (m_recordStatus == RECORD_ON)
+ m_recordStatus = RECORD_OFF;
+
+ m_recordingInstruments.clear();
+}
+
+void
+AlsaDriver::resetPlayback(const RealTime &oldPosition, const RealTime &position)
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "\n\nAlsaDriver - resetPlayback(" << oldPosition << "," << position << ")" << std::endl;
+#endif
+
+ if (getMMCStatus() == TRANSPORT_MASTER) {
+ unsigned char t_sec = (unsigned char) position.sec % 60;
+ unsigned char t_min = (unsigned char) (position.sec / 60) % 60;
+ unsigned char t_hrs = (unsigned char) (position.sec / 3600);
+#define STUPID_BROKEN_EQUIPMENT
+#ifdef STUPID_BROKEN_EQUIPMENT
+ // Some recorders assume you are talking in 30fps...
+ unsigned char t_frm = (unsigned char) (position.nsec / 33333333U);
+ unsigned char t_sbf = (unsigned char) ((position.nsec / 333333U) % 100U);
+#else
+ // We always send at 25fps, it's the easiest to avoid rounding problems
+ unsigned char t_frm = (unsigned char) (position.nsec / 40000000U);
+ unsigned char t_sbf = (unsigned char) ((position.nsec / 400000U) % 100U);
+#endif
+
+ std::cerr << "\n Jump using MMC LOCATE to" << position << std::endl;
+ std::cerr << "\t which is " << int(t_hrs) << ":" << int(t_min) << ":" << int(t_sec) << "." << int(t_frm) << "." << int(t_sbf) << std::endl;
+ unsigned char locateDataArr[7] = {
+ 0x06,
+ 0x01,
+ 0x60 + t_hrs, // (30fps flag) + hh
+ t_min, // mm
+ t_sec, // ss
+ t_frm, // frames
+ t_sbf // subframes
+ };
+
+ sendMMC(127, MIDI_MMC_LOCATE, true, std::string((const char *) locateDataArr, 7));
+ }
+
+ RealTime formerStartPosition = m_playStartPosition;
+
+ m_playStartPosition = position;
+ m_alsaPlayStartTime = getAlsaTime();
+
+ // Reset note offs to correct positions
+ //
+ RealTime jump = position - oldPosition;
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Currently " << m_noteOffQueue.size() << " in note off queue" << std::endl;
+#endif
+
+ // modify the note offs that exist as they're relative to the
+ // playStartPosition terms.
+ //
+ for (NoteOffQueue::iterator i = m_noteOffQueue.begin();
+ i != m_noteOffQueue.end(); ++i) {
+
+ // if we're fast forwarding then we bring the note off closer
+ if (jump >= RealTime::zeroTime) {
+
+ RealTime endTime = formerStartPosition + (*i)->getRealTime();
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Forward jump of " << jump << ": adjusting note off from "
+ << (*i)->getRealTime() << " (absolute " << endTime
+ << ") to ";
+#endif
+ (*i)->setRealTime(endTime - position);
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << (*i)->getRealTime() << std::endl;
+#endif
+ } else // we're rewinding - kill the note immediately
+ {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Rewind by " << jump << ": setting note off to zero" << std::endl;
+#endif
+ (*i)->setRealTime(RealTime::zeroTime);
+ }
+ }
+
+ pushRecentNoteOffs();
+ processNotesOff(getAlsaTime(), true);
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "resetPlayback(): draining");
+
+ // Ensure we clear down output queue on reset - in the case of
+ // MIDI clock where we might have a long queue of events already
+ // posted.
+ //
+ snd_seq_remove_events_t *info;
+ snd_seq_remove_events_alloca(&info);
+ snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
+ snd_seq_remove_events(m_midiHandle, info);
+
+ if (getMTCStatus() == TRANSPORT_MASTER) {
+ m_mtcFirstTime = -1;
+ m_mtcSigmaE = 0;
+ m_mtcSigmaC = 0;
+ insertMTCFullFrame(position);
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_jackDriver->clearSynthPluginEvents();
+ m_needJackStart = NeedJackReposition;
+ }
+#endif
+}
+
+void
+AlsaDriver::setMIDIClockInterval(RealTime interval)
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::setMIDIClockInterval(" << interval << ")" << endl;
+#endif
+
+ // Reset the value
+ //
+ SoundDriver::setMIDIClockInterval(interval);
+
+ // Return if the clock isn't enabled
+ //
+ if (!m_midiClockEnabled)
+ return ;
+
+ if (false) // don't remove any events quite yet
+ {
+
+ // Remove all queued events (although we should filter this
+ // down to just the clock events.
+ //
+ snd_seq_remove_events_t *info;
+ snd_seq_remove_events_alloca(&info);
+
+ //if (snd_seq_type_check(SND_SEQ_EVENT_CLOCK, SND_SEQ_EVFLG_CONTROL))
+ //snd_seq_remove_events_set_event_type(info,
+ snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
+ snd_seq_remove_events_set_event_type(info, SND_SEQ_EVFLG_CONTROL);
+ std::cout << "AlsaDriver::setMIDIClockInterval - "
+ << "MIDI CLOCK TYPE IS CONTROL" << std::endl;
+ snd_seq_remove_events(m_midiHandle, info);
+ }
+
+}
+
+
+void
+AlsaDriver::pushRecentNoteOffs()
+{
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::pushRecentNoteOffs: have " << m_recentNoteOffs.size() << " in queue" << std::endl;
+#endif
+
+ for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
+ i != m_recentNoteOffs.end(); ++i) {
+ (*i)->setRealTime(RealTime::zeroTime);
+ m_noteOffQueue.insert(*i);
+ }
+
+ m_recentNoteOffs.clear();
+}
+
+void
+AlsaDriver::cropRecentNoteOffs(const RealTime &t)
+{
+ while (!m_recentNoteOffs.empty()) {
+ NoteOffEvent *ev = *m_recentNoteOffs.begin();
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::cropRecentNoteOffs: " << ev->getRealTime() << " vs " << t << std::endl;
+#endif
+ if (ev->getRealTime() >= t) break;
+ delete ev;
+ m_recentNoteOffs.erase(m_recentNoteOffs.begin());
+ }
+}
+
+void
+AlsaDriver::weedRecentNoteOffs(unsigned int pitch, MidiByte channel,
+ InstrumentId instrument)
+{
+ for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
+ i != m_recentNoteOffs.end(); ++i) {
+ if ((*i)->getPitch() == pitch &&
+ (*i)->getChannel() == channel &&
+ (*i)->getInstrument() == instrument) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::weedRecentNoteOffs: deleting one" << std::endl;
+#endif
+ delete *i;
+ m_recentNoteOffs.erase(i);
+ break;
+ }
+ }
+}
+
+void
+AlsaDriver::allNotesOff()
+{
+ snd_seq_event_t event;
+ ClientPortPair outputDevice;
+ RealTime offTime;
+
+ // drop any pending notes
+ snd_seq_drop_output_buffer(m_midiHandle);
+ snd_seq_drop_output(m_midiHandle);
+
+ // prepare the event
+ snd_seq_ev_clear(&event);
+ offTime = getAlsaTime();
+
+ for (NoteOffQueue::iterator it = m_noteOffQueue.begin();
+ it != m_noteOffQueue.end(); ++it) {
+ // Set destination according to connection for instrument
+ //
+ outputDevice = getPairForMappedInstrument((*it)->getInstrument());
+ if (outputDevice.first < 0 || outputDevice.second < 0)
+ continue;
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to port for device
+ //
+ int src = getOutputPortForMappedInstrument((*it)->getInstrument());
+ if (src < 0)
+ continue;
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_set_noteoff(&event,
+ (*it)->getChannel(),
+ (*it)->getPitch(),
+ 127);
+
+ //snd_seq_event_output(m_midiHandle, &event);
+ int error = snd_seq_event_output_direct(m_midiHandle, &event);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::allNotesOff - "
+ << "can't send event" << std::endl;
+#endif
+
+ }
+
+ delete(*it);
+ }
+
+ m_noteOffQueue.erase(m_noteOffQueue.begin(), m_noteOffQueue.end());
+
+ /*
+ std::cerr << "AlsaDriver::allNotesOff - "
+ << " queue size = " << m_noteOffQueue.size() << std::endl;
+ */
+
+ // flush
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "allNotesOff(): draining");
+}
+
+void
+AlsaDriver::processNotesOff(const RealTime &time, bool now, bool everything)
+{
+ if (m_noteOffQueue.empty()) {
+ return;
+ }
+
+ snd_seq_event_t event;
+
+ ClientPortPair outputDevice;
+ RealTime offTime;
+
+ // prepare the event
+ snd_seq_ev_clear(&event);
+
+ RealTime alsaTime = getAlsaTime();
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff(" << time << "): alsaTime = " << alsaTime << ", now = " << now << std::endl;
+#endif
+
+ while (m_noteOffQueue.begin() != m_noteOffQueue.end()) {
+
+ NoteOffEvent *ev = *m_noteOffQueue.begin();
+
+ if (ev->getRealTime() > time) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Note off time " << ev->getRealTime() << " is beyond current time " << time << std::endl;
+#endif
+ if (!everything) break;
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff(" << time << "): found event at " << ev->getRealTime() << ", instr " << ev->getInstrument() << ", channel " << int(ev->getChannel()) << ", pitch " << int(ev->getPitch()) << std::endl;
+#endif
+
+ bool isSoftSynth = (ev->getInstrument() >= SoftSynthInstrumentBase);
+
+ offTime = ev->getRealTime();
+ if (offTime < RealTime::zeroTime) offTime = RealTime::zeroTime;
+ bool scheduled = (offTime > alsaTime) && !now;
+ if (!scheduled) offTime = RealTime::zeroTime;
+
+ snd_seq_real_time_t alsaOffTime = { offTime.sec,
+ offTime.nsec };
+
+ snd_seq_ev_set_noteoff(&event,
+ ev->getChannel(),
+ ev->getPitch(),
+ 127);
+
+ if (!isSoftSynth) {
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to instrument
+ //
+ int src = getOutputPortForMappedInstrument(ev->getInstrument());
+ if (src < 0) {
+ std::cerr << "note off has no output port (instr = " << ev->getInstrument() << ")" << std::endl;
+ delete ev;
+ m_noteOffQueue.erase(m_noteOffQueue.begin());
+ continue;
+ }
+
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_set_subs(&event);
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &alsaOffTime);
+
+ if (scheduled) {
+ snd_seq_event_output(m_midiHandle, &event);
+ } else {
+ snd_seq_event_output_direct(m_midiHandle, &event);
+ }
+
+ } else {
+
+ event.time.time = alsaOffTime;
+
+ processSoftSynthEventOut(ev->getInstrument(), &event, now);
+ }
+
+ if (!now) {
+ m_recentNoteOffs.insert(ev);
+ } else {
+ delete ev;
+ }
+ m_noteOffQueue.erase(m_noteOffQueue.begin());
+ }
+
+ // We don't flush the queue here, as this is called nested from
+ // processMidiOut, which does the flushing
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff - "
+ << " queue size now: " << m_noteOffQueue.size() << std::endl;
+#endif
+}
+
+// Get the queue time and convert it to RealTime for the gui
+// to use.
+//
+RealTime
+AlsaDriver::getSequencerTime()
+{
+ RealTime t(0, 0);
+
+ t = getAlsaTime() + m_playStartPosition - m_alsaPlayStartTime;
+
+ // std::cerr << "AlsaDriver::getSequencerTime: alsa time is "
+ // << getAlsaTime() << ", start time is " << m_alsaPlayStartTime << ", play start position is " << m_playStartPosition << endl;
+
+ return t;
+}
+
+// Gets the time of the ALSA queue
+//
+RealTime
+AlsaDriver::getAlsaTime()
+{
+ RealTime sequencerTime(0, 0);
+
+ snd_seq_queue_status_t *status;
+ snd_seq_queue_status_alloca(&status);
+
+ if (snd_seq_get_queue_status(m_midiHandle, m_queue, status) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getAlsaTime - can't get queue status"
+ << std::endl;
+#endif
+
+ return sequencerTime;
+ }
+
+ sequencerTime.sec = snd_seq_queue_status_get_real_time(status)->tv_sec;
+ sequencerTime.nsec = snd_seq_queue_status_get_real_time(status)->tv_nsec;
+
+ // std::cerr << "AlsaDriver::getAlsaTime: alsa time is " << sequencerTime << std::endl;
+
+ return sequencerTime;
+}
+
+
+// Get all pending input events and turn them into a MappedComposition.
+//
+//
+MappedComposition*
+AlsaDriver::getMappedComposition()
+{
+ m_recordComposition.clear();
+
+ while (_failureReportReadIndex != _failureReportWriteIndex) {
+ MappedEvent::FailureCode code = _failureReports[_failureReportReadIndex];
+ // std::cerr << "AlsaDriver::reportFailure(" << code << ")" << std::endl;
+ MappedEvent *mE = new MappedEvent
+ (0, MappedEvent::SystemFailure, code, 0);
+ m_returnComposition.insert(mE);
+ _failureReportReadIndex =
+ (_failureReportReadIndex + 1) % FAILURE_REPORT_COUNT;
+ }
+
+ if (!m_returnComposition.empty()) {
+ for (MappedComposition::iterator i = m_returnComposition.begin();
+ i != m_returnComposition.end(); ++i) {
+ m_recordComposition.insert(new MappedEvent(**i));
+ }
+ m_returnComposition.clear();
+ }
+
+ // If the input port hasn't connected we shouldn't poll it
+ //
+ if (m_midiInputPortConnected == false) {
+ return &m_recordComposition;
+ }
+
+ RealTime eventTime(0, 0);
+
+ snd_seq_event_t *event;
+
+ while (snd_seq_event_input(m_midiHandle, &event) > 0) {
+
+ unsigned int channel = (unsigned int)event->data.note.channel;
+ unsigned int chanNoteKey = ( channel << 8 ) +
+ (unsigned int) event->data.note.note;
+
+ bool fromController = false;
+
+ if (event->dest.client == m_client &&
+ event->dest.port == m_controllerPort) {
+#ifdef DEBUG_ALSA
+ std::cerr << "Received an external controller event" << std::endl;
+#endif
+
+ fromController = true;
+ }
+
+ unsigned int deviceId = Device::NO_DEVICE;
+
+ if (fromController) {
+ deviceId = Device::CONTROL_DEVICE;
+ } else {
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
+ if (((*i)->getDirection() == MidiDevice::Record) &&
+ ( pair.first == event->source.client ) &&
+ ( pair.second == event->source.port )) {
+ deviceId = (*i)->getId();
+ break;
+ }
+ }
+ }
+
+ eventTime.sec = event->time.time.tv_sec;
+ eventTime.nsec = event->time.time.tv_nsec;
+ eventTime = eventTime - m_alsaRecordStartTime + m_playStartPosition;
+
+#ifdef DEBUG_ALSA
+ if (!fromController) {
+ std::cerr << "Received normal event: type " << int(event->type) << ", chan " << channel << ", note " << int(event->data.note.note) << ", time " << eventTime << std::endl;
+ }
+#endif
+
+ switch (event->type) {
+ case SND_SEQ_EVENT_NOTE:
+ case SND_SEQ_EVENT_NOTEON:
+ if (fromController)
+ continue;
+ if (event->data.note.velocity > 0) {
+ MappedEvent *mE = new MappedEvent();
+ mE->setPitch(event->data.note.note);
+ mE->setVelocity(event->data.note.velocity);
+ mE->setEventTime(eventTime);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+
+ // Negative duration - we need to hear the NOTE ON
+ // so we must insert it now with a negative duration
+ // and pick and mix against the following NOTE OFF
+ // when we create the recorded segment.
+ //
+ mE->setDuration(RealTime( -1, 0));
+
+ // Create a copy of this when we insert the NOTE ON -
+ // keeping a copy alive on the m_noteOnMap.
+ //
+ // We shake out the two NOTE Ons after we've recorded
+ // them.
+ //
+ m_recordComposition.insert(new MappedEvent(mE));
+ m_noteOnMap[deviceId][chanNoteKey] = mE;
+
+ break;
+ }
+
+ case SND_SEQ_EVENT_NOTEOFF:
+ if (fromController)
+ continue;
+
+ if (m_noteOnMap[deviceId][chanNoteKey] != 0) {
+
+ // Set duration correctly on the NOTE OFF
+ //
+ MappedEvent *mE = m_noteOnMap[deviceId][chanNoteKey];
+ RealTime duration = eventTime - mE->getEventTime();
+
+#ifdef DEBUG_ALSA
+ std::cerr << "NOTE OFF: found NOTE ON at " << mE->getEventTime() << std::endl;
+#endif
+
+ if (duration < RealTime::zeroTime) {
+ duration = RealTime::zeroTime;
+ mE->setEventTime(eventTime);
+ }
+
+ // Velocity 0 - NOTE OFF. Set duration correctly
+ // for recovery later.
+ //
+ mE->setVelocity(0);
+ mE->setDuration(duration);
+
+ // force shut off of note
+ m_recordComposition.insert(mE);
+
+ // reset the reference
+ //
+ m_noteOnMap[deviceId][chanNoteKey] = 0;
+
+ }
+ break;
+
+ case SND_SEQ_EVENT_KEYPRESS: {
+ if (fromController)
+ continue;
+
+ // Fix for 632964 by Pedro Lopez-Cabanillas (20030523)
+ //
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiKeyPressure);
+ mE->setEventTime(eventTime);
+ mE->setData1(event->data.note.note);
+ mE->setData2(event->data.note.velocity);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_CONTROLLER: {
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiController);
+ mE->setEventTime(eventTime);
+ mE->setData1(event->data.control.param);
+ mE->setData2(event->data.control.value);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_PGMCHANGE: {
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiProgramChange);
+ mE->setEventTime(eventTime);
+ mE->setData1(event->data.control.value);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+
+ }
+ break;
+
+ case SND_SEQ_EVENT_PITCHBEND: {
+ if (fromController)
+ continue;
+
+ // Fix for 711889 by Pedro Lopez-Cabanillas (20030523)
+ //
+ int s = event->data.control.value + 8192;
+ int d1 = (s >> 7) & 0x7f; // data1 = MSB
+ int d2 = s & 0x7f; // data2 = LSB
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiPitchBend);
+ mE->setEventTime(eventTime);
+ mE->setData1(d1);
+ mE->setData2(d2);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_CHANPRESS: {
+ if (fromController)
+ continue;
+
+ // Fixed by Pedro Lopez-Cabanillas (20030523)
+ //
+ int s = event->data.control.value & 0x7f;
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiChannelPressure);
+ mE->setEventTime(eventTime);
+ mE->setData1(s);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_SYSEX:
+
+ if (fromController)
+ continue;
+
+ if (!testForMTCSysex(event) &&
+ !testForMMCSysex(event)) {
+
+ // Bundle up the data into a block on the MappedEvent
+ //
+ std::string data;
+ char *ptr = (char*)(event->data.ext.ptr);
+ for (unsigned int i = 0; i < event->data.ext.len; ++i)
+ data += *(ptr++);
+
+#ifdef DEBUG_ALSA
+
+ if ((MidiByte)(data[1]) == MIDI_SYSEX_RT) {
+ std::cerr << "REALTIME SYSEX" << endl;
+ for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
+ printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
+ }
+ } else {
+ std::cerr << "NON-REALTIME SYSEX" << endl;
+ for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
+ printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
+ }
+ }
+#endif
+
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiSystemMessage);
+ mE->setData1(MIDI_SYSTEM_EXCLUSIVE);
+ mE->setRecordedDevice(deviceId);
+ // chop off SYX and EOX bytes from data block
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030601)
+ DataBlockRepository::setDataBlockForEvent(mE, data.substr(1, data.length() - 2));
+ mE->setEventTime(eventTime);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+
+ case SND_SEQ_EVENT_SENSING: // MIDI device is still there
+ break;
+
+ case SND_SEQ_EVENT_QFRAME:
+ if (fromController)
+ continue;
+ if (getMTCStatus() == TRANSPORT_SLAVE) {
+ handleMTCQFrame(event->data.control.value, eventTime);
+ }
+ break;
+
+ case SND_SEQ_EVENT_CLOCK:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "got realtime MIDI clock" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_START:
+ if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump(ExternalTransport::TransportStopAtTime,
+ RealTime::zeroTime);
+ transport->transportChange(ExternalTransport::TransportStart);
+ }
+ }
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "START" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_CONTINUE:
+ if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportPlay);
+ }
+ }
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "CONTINUE" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_STOP:
+ if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && isPlaying()) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportStop);
+ }
+ }
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "STOP" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_SONGPOS:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "SONG POSITION" << std::endl;
+#endif
+
+ break;
+
+ // these cases are handled by checkForNewClients
+ //
+ case SND_SEQ_EVENT_CLIENT_START:
+ case SND_SEQ_EVENT_CLIENT_EXIT:
+ case SND_SEQ_EVENT_CLIENT_CHANGE:
+ case SND_SEQ_EVENT_PORT_START:
+ case SND_SEQ_EVENT_PORT_EXIT:
+ case SND_SEQ_EVENT_PORT_CHANGE:
+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+ m_portCheckNeeded = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "got announce event ("
+ << int(event->type) << ")" << std::endl;
+#endif
+
+ break;
+ case SND_SEQ_EVENT_TICK:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "got unhandled MIDI event type from ALSA sequencer"
+ << "(" << int(event->type) << ")" << std::endl;
+#endif
+
+ break;
+
+
+ }
+ }
+
+ if (getMTCStatus() == TRANSPORT_SLAVE && isPlaying()) {
+#ifdef MTC_DEBUG
+ std::cerr << "seq time is " << getSequencerTime() << ", last MTC receive "
+ << m_mtcLastReceive << ", first time " << m_mtcFirstTime << std::endl;
+#endif
+
+ if (m_mtcFirstTime == 0) { // have received _some_ MTC quarter-frame info
+ RealTime seqTime = getSequencerTime();
+ if (m_mtcLastReceive < seqTime &&
+ seqTime - m_mtcLastReceive > RealTime(0, 500000000L)) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump(ExternalTransport::TransportStopAtTime,
+ m_mtcLastEncoded);
+ }
+ }
+ }
+ }
+
+ return &m_recordComposition;
+}
+
+static int lock_count = 0;
+
+void
+AlsaDriver::handleMTCQFrame(unsigned int data_byte, RealTime the_time)
+{
+ if (getMTCStatus() != TRANSPORT_SLAVE)
+ return ;
+
+ switch (data_byte & 0xF0) {
+ /* Frame */
+ case 0x00:
+ /*
+ * Reset everything
+ */
+ m_mtcReceiveTime = the_time;
+ m_mtcFrames = data_byte & 0x0f;
+ m_mtcSeconds = 0;
+ m_mtcMinutes = 0;
+ m_mtcHours = 0;
+ m_mtcSMPTEType = 0;
+
+ break;
+
+ case 0x10:
+ m_mtcFrames |= (data_byte & 0x0f) << 4;
+ break;
+
+ /* Seconds */
+ case 0x20:
+ m_mtcSeconds = data_byte & 0x0f;
+ break;
+ case 0x30:
+ m_mtcSeconds |= (data_byte & 0x0f) << 4;
+ break;
+
+ /* Minutes */
+ case 0x40:
+ m_mtcMinutes = data_byte & 0x0f;
+ break;
+ case 0x50:
+ m_mtcMinutes |= (data_byte & 0x0f) << 4;
+ break;
+
+ /* Hours and SMPTE type */
+ case 0x60:
+ m_mtcHours = data_byte & 0x0f;
+ break;
+
+ case 0x70: {
+ m_mtcHours |= (data_byte & 0x01) << 4;
+ m_mtcSMPTEType = (data_byte & 0x06) >> 1;
+
+ int fps = 30;
+ if (m_mtcSMPTEType == 0)
+ fps = 24;
+ else if (m_mtcSMPTEType == 1)
+ fps = 25;
+
+ /*
+ * Ok, got all the bits now
+ * (Assuming time is rolling forward)
+ */
+
+ /* correct for 2-frame lag */
+ m_mtcFrames += 2;
+ if (m_mtcFrames >= fps) {
+ m_mtcFrames -= fps;
+ if (++m_mtcSeconds == 60) {
+ m_mtcSeconds = 0;
+ if (++m_mtcMinutes == 60) {
+ m_mtcMinutes = 0;
+ ++m_mtcHours;
+ }
+ }
+ }
+
+#ifdef MTC_DEBUG
+ printf("RG MTC: Got a complete sequence: %02d:%02d:%02d.%02d (type %d)\n",
+ m_mtcHours,
+ m_mtcMinutes,
+ m_mtcSeconds,
+ m_mtcFrames,
+ m_mtcSMPTEType);
+#endif
+
+ /* compute encoded time */
+ m_mtcEncodedTime.sec = m_mtcSeconds +
+ m_mtcMinutes * 60 +
+ m_mtcHours * 60 * 60;
+
+ switch (fps) {
+ case 24:
+ m_mtcEncodedTime.nsec = (int)
+ ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ case 25:
+ m_mtcEncodedTime.nsec = (int)
+ (40000000UL * (unsigned)m_mtcFrames);
+ break;
+ case 30:
+ default:
+ m_mtcEncodedTime.nsec = (int)
+ ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ }
+
+ /*
+ * We only mess with the clock if we are playing
+ */
+ if (m_playing) {
+#ifdef MTC_DEBUG
+ std::cerr << "RG MTC: Tstamp " << m_mtcEncodedTime;
+ std::cerr << " Received @ " << m_mtcReceiveTime << endl;
+#endif
+
+ calibrateMTC();
+
+ RealTime t_diff = m_mtcEncodedTime - m_mtcReceiveTime;
+#ifdef MTC_DEBUG
+
+ std::cerr << "Diff: " << t_diff << endl;
+#endif
+
+ /* -ve diff means ALSA time ahead of MTC time */
+
+ if (t_diff.sec > 0) {
+ tweakSkewForMTC(60000);
+ } else if (t_diff.sec < 0) {
+ tweakSkewForMTC( -60000);
+ } else {
+ /* "small" diff - use adaptive technique */
+ tweakSkewForMTC(t_diff.nsec / 1400);
+ if ((t_diff.nsec / 1000000) == 0) {
+ if (++lock_count == 3) {
+ printf("Got a lock @ %02d:%02d:%02d.%02d (type %d)\n",
+ m_mtcHours,
+ m_mtcMinutes,
+ m_mtcSeconds,
+ m_mtcFrames,
+ m_mtcSMPTEType);
+ }
+ } else {
+ lock_count = 0;
+ }
+ }
+
+ } else if (m_eat_mtc > 0) {
+#ifdef MTC_DEBUG
+ std::cerr << "MTC: Received quarter frame just after issuing MMC stop - ignore it" << std::endl;
+#endif
+
+ --m_eat_mtc;
+ } else {
+ /* If we're not playing, we should be. */
+#ifdef MTC_DEBUG
+ std::cerr << "MTC: Received quarter frame while not playing - starting now" << std::endl;
+#endif
+
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump
+ (ExternalTransport::TransportStartAtTime,
+ m_mtcEncodedTime);
+ }
+ }
+
+ break;
+ }
+
+ /* Oh dear, demented device! */
+ default:
+ break;
+ }
+}
+
+void
+AlsaDriver::insertMTCFullFrame(RealTime time)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ m_mtcEncodedTime = time;
+ m_mtcSeconds = m_mtcEncodedTime.sec % 60;
+ m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
+ m_mtcHours = (m_mtcEncodedTime.sec / 3600);
+
+ // We always send at 25fps, it's the easiest to avoid rounding problems
+ m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U;
+
+ time = time + m_alsaPlayStartTime - m_playStartPosition;
+ snd_seq_real_time_t atime = { time.sec, time.nsec };
+
+ unsigned char data[10] =
+ { MIDI_SYSTEM_EXCLUSIVE,
+ MIDI_SYSEX_RT, 127, 1, 1,
+ 0, 0, 0, 0,
+ MIDI_END_OF_EXCLUSIVE };
+
+ data[5] = ((unsigned char)m_mtcHours & 0x1f) + (1 << 5); // 1 indicates 25fps
+ data[6] = (unsigned char)m_mtcMinutes;
+ data[7] = (unsigned char)m_mtcSeconds;
+ data[8] = (unsigned char)m_mtcFrames;
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
+ snd_seq_ev_set_sysex(&event, 10, data);
+
+ checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
+ "insertMTCFullFrame event send");
+
+ if (m_queueRunning) {
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "insertMTCFullFrame drain");
+ }
+}
+
+void
+AlsaDriver::insertMTCQFrames(RealTime sliceStart, RealTime sliceEnd)
+{
+ if (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime) {
+ // not a real slice
+ return ;
+ }
+
+ // We send at 25fps, it's the easiest to avoid rounding problems
+ RealTime twoFrames(0, 80000000U);
+ RealTime quarterFrame(0, 10000000U);
+ int fps = 25;
+
+#ifdef MTC_DEBUG
+
+ std::cout << "AlsaDriver::insertMTCQFrames(" << sliceStart << ","
+ << sliceEnd << "): first time " << m_mtcFirstTime << std::endl;
+#endif
+
+ RealTime t;
+
+ if (m_mtcFirstTime != 0) { // first time through, reset location
+ m_mtcEncodedTime = sliceStart;
+ t = sliceStart;
+ m_mtcFirstTime = 0;
+ } else {
+ t = m_mtcEncodedTime + quarterFrame;
+ }
+
+ m_mtcSeconds = m_mtcEncodedTime.sec % 60;
+ m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
+ m_mtcHours = (m_mtcEncodedTime.sec / 3600);
+ m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U; // 25fps
+
+ std::string bytes = " ";
+
+ int type = 0;
+
+ while (m_mtcEncodedTime < sliceEnd) {
+
+ snd_seq_event_t event;
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+#ifdef MTC_DEBUG
+
+ std::cout << "Sending MTC quarter frame at " << t << std::endl;
+#endif
+
+ unsigned char c = (type << 4);
+
+ switch (type) {
+ case 0:
+ c += ((unsigned char)m_mtcFrames & 0x0f);
+ break;
+ case 1:
+ c += (((unsigned char)m_mtcFrames & 0xf0) >> 4);
+ break;
+ case 2:
+ c += ((unsigned char)m_mtcSeconds & 0x0f);
+ break;
+ case 3:
+ c += (((unsigned char)m_mtcSeconds & 0xf0) >> 4);
+ break;
+ case 4:
+ c += ((unsigned char)m_mtcMinutes & 0x0f);
+ break;
+ case 5:
+ c += (((unsigned char)m_mtcMinutes & 0xf0) >> 4);
+ break;
+ case 6:
+ c += ((unsigned char)m_mtcHours & 0x0f);
+ break;
+ case 7: // hours high nibble + smpte type
+ c += (m_mtcHours >> 4) & 0x01;
+ c += (1 << 1); // type 1 indicates 25fps
+ break;
+ }
+
+ RealTime scheduleTime = t + m_alsaPlayStartTime - m_playStartPosition;
+ snd_seq_real_time_t atime = { scheduleTime.sec, scheduleTime.nsec };
+
+ event.type = SND_SEQ_EVENT_QFRAME;
+ event.data.control.value = c;
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
+
+ checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
+ "insertMTCQFrames sending qframe event");
+
+ if (++type == 8) {
+ m_mtcFrames += 2;
+ if (m_mtcFrames >= fps) {
+ m_mtcFrames -= fps;
+ if (++m_mtcSeconds == 60) {
+ m_mtcSeconds = 0;
+ if (++m_mtcMinutes == 60) {
+ m_mtcMinutes = 0;
+ ++m_mtcHours;
+ }
+ }
+ }
+ m_mtcEncodedTime = t;
+ type = 0;
+ }
+
+ t = t + quarterFrame;
+ }
+}
+
+bool
+AlsaDriver::testForMTCSysex(const snd_seq_event_t *event)
+{
+ if (getMTCStatus() != TRANSPORT_SLAVE)
+ return false;
+
+ // At this point, and possibly for the foreseeable future, the only
+ // sysex we're interested in is full-frame transport location
+
+#ifdef MTC_DEBUG
+
+ std::cerr << "MTC: testing sysex of length " << event->data.ext.len << ":" << std::endl;
+ for (int i = 0; i < event->data.ext.len; ++i) {
+ std::cerr << (int)*((unsigned char *)event->data.ext.ptr + i) << " ";
+ }
+ std::cerr << endl;
+#endif
+
+ if (event->data.ext.len != 10)
+ return false;
+
+ unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);
+
+ if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
+ return false;
+ if (*ptr++ != MIDI_SYSEX_RT)
+ return false;
+ if (*ptr++ > 127)
+ return false;
+
+ // 01 01 for MTC full frame
+
+ if (*ptr++ != 1)
+ return false;
+ if (*ptr++ != 1)
+ return false;
+
+ int htype = *ptr++;
+ int min = *ptr++;
+ int sec = *ptr++;
+ int frame = *ptr++;
+
+ if (*ptr != MIDI_END_OF_EXCLUSIVE)
+ return false;
+
+ int hour = (htype & 0x1f);
+ int type = (htype & 0xe0) >> 5;
+
+ m_mtcFrames = frame;
+ m_mtcSeconds = sec;
+ m_mtcMinutes = min;
+ m_mtcHours = hour;
+ m_mtcSMPTEType = type;
+
+ int fps = 30;
+ if (m_mtcSMPTEType == 0)
+ fps = 24;
+ else if (m_mtcSMPTEType == 1)
+ fps = 25;
+
+ m_mtcEncodedTime.sec = sec + min * 60 + hour * 60 * 60;
+
+ switch (fps) {
+ case 24:
+ m_mtcEncodedTime.nsec = (int)
+ ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ case 25:
+ m_mtcEncodedTime.nsec = (int)
+ (40000000UL * (unsigned)m_mtcFrames);
+ break;
+ case 30:
+ default:
+ m_mtcEncodedTime.nsec = (int)
+ ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ }
+
+#ifdef MTC_DEBUG
+ std::cerr << "MTC: MTC sysex found (frame type " << type
+ << "), jumping to " << m_mtcEncodedTime << std::endl;
+#endif
+
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump
+ (ExternalTransport::TransportJumpToTime,
+ m_mtcEncodedTime);
+ }
+
+ return true;
+}
+
+static int last_factor = 0;
+static int bias_factor = 0;
+
+void
+AlsaDriver::calibrateMTC()
+{
+ if (m_mtcFirstTime < 0)
+ return ;
+ else if (m_mtcFirstTime > 0) {
+ --m_mtcFirstTime;
+ m_mtcSigmaC = 0;
+ m_mtcSigmaE = 0;
+ } else {
+ RealTime diff_e = m_mtcEncodedTime - m_mtcLastEncoded;
+ RealTime diff_c = m_mtcReceiveTime - m_mtcLastReceive;
+
+#ifdef MTC_DEBUG
+
+ printf("RG MTC: diffs %d %d %d\n", diff_c.nsec, diff_e.nsec, m_mtcSkew);
+#endif
+
+ m_mtcSigmaE += ((long long int) diff_e.nsec) * m_mtcSkew;
+ m_mtcSigmaC += diff_c.nsec;
+
+
+ int t_bias = (m_mtcSigmaE / m_mtcSigmaC) - 0x10000;
+
+#ifdef MTC_DEBUG
+
+ printf("RG MTC: sigmas %lld %lld %d\n", m_mtcSigmaE, m_mtcSigmaC, t_bias);
+#endif
+
+ bias_factor = t_bias;
+ }
+
+ m_mtcLastReceive = m_mtcReceiveTime;
+ m_mtcLastEncoded = m_mtcEncodedTime;
+
+}
+
+void
+AlsaDriver::tweakSkewForMTC(int factor)
+{
+ if (factor > 50000) {
+ factor = 50000;
+ } else if (factor < -50000) {
+ factor = -50000;
+ } else if (factor == last_factor) {
+ return ;
+ } else {
+ if (m_mtcFirstTime == -1)
+ m_mtcFirstTime = 5;
+ }
+ last_factor = factor;
+
+ snd_seq_queue_tempo_t *q_ptr;
+ snd_seq_queue_tempo_alloca(&q_ptr);
+
+ snd_seq_get_queue_tempo( m_midiHandle, m_queue, q_ptr);
+
+ unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
+#ifdef MTC_DEBUG
+
+ std::cerr << "RG MTC: skew: " << t_skew;
+#endif
+
+ t_skew = 0x10000 + factor + bias_factor;
+
+#ifdef MTC_DEBUG
+
+ std::cerr << " changed to " << factor << "+" << bias_factor << endl;
+#endif
+
+ snd_seq_queue_tempo_set_skew(q_ptr, t_skew);
+ snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
+
+ m_mtcSkew = t_skew;
+}
+
+bool
+AlsaDriver::testForMMCSysex(const snd_seq_event_t *event)
+{
+ if (getMMCStatus() != TRANSPORT_SLAVE)
+ return false;
+
+ if (event->data.ext.len != 6)
+ return false;
+
+ unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);
+
+ if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
+ return false;
+ if (*ptr++ != MIDI_SYSEX_RT)
+ return false;
+ if (*ptr++ > 127)
+ return false;
+ if (*ptr++ != MIDI_SYSEX_RT_COMMAND)
+ return false;
+
+ int instruction = *ptr++;
+
+ if (*ptr != MIDI_END_OF_EXCLUSIVE)
+ return false;
+
+ if (instruction == MIDI_MMC_PLAY ||
+ instruction == MIDI_MMC_DEFERRED_PLAY) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportPlay);
+ }
+ } else if (instruction == MIDI_MMC_STOP) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportStop);
+ }
+ }
+
+ return true;
+}
+
+void
+AlsaDriver::processMidiOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd)
+{
+ RealTime outputTime;
+ RealTime outputStopTime;
+ MappedInstrument *instrument;
+ ClientPortPair outputDevice;
+ MidiByte channel;
+ snd_seq_event_t event;
+
+ // special case for unqueued events
+ bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);
+
+ if (!now) {
+ // This 0.5 sec is arbitrary, but it must be larger than the
+ // sequencer's read-ahead
+ RealTime diff = RealTime::fromSeconds(0.5);
+ RealTime cutoff = sliceStart - diff;
+ cropRecentNoteOffs(cutoff - m_playStartPosition + m_alsaPlayStartTime);
+ }
+
+ // These won't change in this slice
+ //
+ snd_seq_ev_clear(&event);
+
+ if ((mC.begin() != mC.end()) && getSequencerDataBlock()) {
+ getSequencerDataBlock()->setVisual(*mC.begin());
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processMidiOut(" << sliceStart << "," << sliceEnd
+ << "), " << mC.size() << " events, now is " << now << std::endl;
+#endif
+
+ // NB the MappedComposition is implicitly ordered by time (std::multiset)
+
+ for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
+ if ((*i)->getType() >= MappedEvent::Audio)
+ continue;
+
+ bool isControllerOut = ((*i)->getRecordedDevice() ==
+ Device::CONTROL_DEVICE);
+
+ bool isSoftSynth = (!isControllerOut &&
+ ((*i)->getInstrument() >= SoftSynthInstrumentBase));
+
+ outputTime = (*i)->getEventTime() - m_playStartPosition +
+ m_alsaPlayStartTime;
+
+ if (now && !m_playing && m_queueRunning) {
+ // stop queue to ensure exact timing and make sure the
+ // event gets through right now
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut: stopping queue for now-event" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): stop queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
+ }
+
+ RealTime alsaTimeNow = getAlsaTime();
+
+ if (now) {
+ if (!m_playing) {
+ outputTime = alsaTimeNow;
+ } else if (outputTime < alsaTimeNow) {
+ outputTime = alsaTimeNow + RealTime(0, 10000000);
+ }
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: event is at " << outputTime << " (" << outputTime - alsaTimeNow << " ahead of queue time), type " << int((*i)->getType()) << ", duration " << (*i)->getDuration() << std::endl;
+#endif
+
+ if (!m_queueRunning && outputTime < alsaTimeNow) {
+ RealTime adjust = alsaTimeNow - outputTime;
+ if ((*i)->getDuration() > RealTime::zeroTime) {
+ if ((*i)->getDuration() <= adjust) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: too late for this event, abandoning it" << std::endl;
+#endif
+
+ continue;
+ } else {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: pushing event forward and reducing duration by " << adjust << std::endl;
+#endif
+
+ (*i)->setDuration((*i)->getDuration() - adjust);
+ }
+ } else {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: pushing zero-duration event forward by " << adjust << std::endl;
+#endif
+
+ }
+ outputTime = alsaTimeNow;
+ }
+
+ processNotesOff(outputTime, now);
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver) {
+ size_t frameCount = m_jackDriver->getFramesProcessed();
+ size_t elapsed = frameCount - _debug_jack_frame_count;
+ RealTime rt = RealTime::frame2RealTime(elapsed, m_jackDriver->getSampleRate());
+ rt = rt - getAlsaTime();
+#ifdef DEBUG_PROCESS_MIDI_OUT
+
+ std::cerr << "processMidiOut[" << now << "]: JACK time is " << rt << " ahead of ALSA time" << std::endl;
+#endif
+
+ }
+#endif
+
+ // Second and nanoseconds for ALSA
+ //
+ snd_seq_real_time_t time = { outputTime.sec, outputTime.nsec };
+
+ if (!isSoftSynth) {
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cout << "processMidiOut[" << now << "]: instrument " << (*i)->getInstrument() << std::endl;
+ std::cout << "pitch: " << (int)(*i)->getPitch() << ", velocity " << (int)(*i)->getVelocity() << ", duration " << (*i)->getDuration() << std::endl;
+#endif
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to port for device
+ //
+ int src;
+
+ if (isControllerOut) {
+ src = m_controllerPort;
+ } else {
+ src = getOutputPortForMappedInstrument((*i)->getInstrument());
+ }
+
+ if (src < 0) continue;
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &time);
+
+ } else {
+ event.time.time = time;
+ }
+
+ instrument = getMappedInstrument((*i)->getInstrument());
+
+ // set the stop time for Note Off
+ //
+ outputStopTime = outputTime + (*i)->getDuration()
+ - RealTime(0, 1); // notch it back 1nsec just to ensure
+ // correct ordering against any other
+ // note-ons at the same nominal time
+ bool needNoteOff = false;
+
+ if (isControllerOut) {
+ channel = (*i)->getRecordedChannel();
+#ifdef DEBUG_ALSA
+
+ std::cerr << "processMidiOut() - Event of type " << (int)((*i)->getType()) << " (data1 " << (int)(*i)->getData1() << ", data2 " << (int)(*i)->getData2() << ") for external controller channel " << (int)channel << std::endl;
+#endif
+
+ } else if (instrument != 0) {
+ channel = instrument->getChannel();
+ } else {
+#ifdef DEBUG_ALSA
+ std::cerr << "processMidiOut() - No instrument for event of type "
+ << (int)(*i)->getType() << " at " << (*i)->getEventTime()
+ << std::endl;
+#endif
+
+ channel = 0;
+ }
+
+ switch ((*i)->getType()) {
+
+ case MappedEvent::MidiNoteOneShot:
+ {
+ snd_seq_ev_set_noteon(&event,
+ channel,
+ (*i)->getPitch(),
+ (*i)->getVelocity());
+ needNoteOff = true;
+
+ if (!isSoftSynth && getSequencerDataBlock()) {
+ LevelInfo info;
+ info.level = (*i)->getVelocity();
+ info.levelRight = 0;
+ getSequencerDataBlock()->setInstrumentLevel
+ ((*i)->getInstrument(), info);
+ }
+
+ weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
+ }
+ break;
+
+ case MappedEvent::MidiNote:
+ // We always use plain NOTE ON here, not ALSA
+ // time+duration notes, because we have our own NOTE
+ // OFF stack (which will be augmented at the bottom of
+ // this function) and we want to ensure it gets used
+ // for the purposes of e.g. soft synths
+ //
+ if ((*i)->getVelocity() > 0) {
+ snd_seq_ev_set_noteon(&event,
+ channel,
+ (*i)->getPitch(),
+ (*i)->getVelocity());
+
+ if (!isSoftSynth && getSequencerDataBlock()) {
+ LevelInfo info;
+ info.level = (*i)->getVelocity();
+ info.levelRight = 0;
+ getSequencerDataBlock()->setInstrumentLevel
+ ((*i)->getInstrument(), info);
+ }
+
+ weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
+ } else {
+ snd_seq_ev_set_noteoff(&event,
+ channel,
+ (*i)->getPitch(),
+ (*i)->getVelocity());
+ }
+
+ break;
+
+ case MappedEvent::MidiProgramChange:
+ snd_seq_ev_set_pgmchange(&event,
+ channel,
+ (*i)->getData1());
+ break;
+
+ case MappedEvent::MidiKeyPressure:
+ snd_seq_ev_set_keypress(&event,
+ channel,
+ (*i)->getData1(),
+ (*i)->getData2());
+ break;
+
+ case MappedEvent::MidiChannelPressure:
+ snd_seq_ev_set_chanpress(&event,
+ channel,
+ (*i)->getData1());
+ break;
+
+ case MappedEvent::MidiPitchBend: {
+ int d1 = (int)((*i)->getData1());
+ int d2 = (int)((*i)->getData2());
+ int value = ((d1 << 7) | d2) - 8192;
+
+ // keep within -8192 to +8192
+ //
+ // if (value & 0x4000)
+ // value -= 0x8000;
+
+ snd_seq_ev_set_pitchbend(&event,
+ channel,
+ value);
+ }
+ break;
+
+ case MappedEvent::MidiSystemMessage: {
+ switch ((*i)->getData1()) {
+ case MIDI_SYSTEM_EXCLUSIVE: {
+ char out[2];
+ sprintf(out, "%c", MIDI_SYSTEM_EXCLUSIVE);
+ std::string data = out;
+
+ data += DataBlockRepository::getDataBlockForEvent((*i));
+
+ sprintf(out, "%c", MIDI_END_OF_EXCLUSIVE);
+ data += out;
+
+ snd_seq_ev_set_sysex(&event,
+ data.length(),
+ (char*)(data.c_str()));
+ }
+ break;
+
+ case MIDI_TIMING_CLOCK: {
+ RealTime rt =
+ RealTime(time.tv_sec, time.tv_nsec);
+
+ /*
+ std::cerr << "AlsaDriver::processMidiOut - "
+ << "send clock @ " << rt << std::endl;
+ */
+
+ sendSystemQueued(SND_SEQ_EVENT_CLOCK, "", rt);
+
+ continue;
+
+ }
+ break;
+
+ default:
+ std::cerr << "AlsaDriver::processMidiOut - "
+ << "unrecognised system message"
+ << std::endl;
+ break;
+ }
+ }
+ break;
+
+ case MappedEvent::MidiController:
+ snd_seq_ev_set_controller(&event,
+ channel,
+ (*i)->getData1(),
+ (*i)->getData2());
+ break;
+
+ case MappedEvent::Audio:
+ case MappedEvent::AudioCancel:
+ case MappedEvent::AudioLevel:
+ case MappedEvent::AudioStopped:
+ case MappedEvent::SystemUpdateInstruments:
+ case MappedEvent::SystemJackTransport: //???
+ case MappedEvent::SystemMMCTransport:
+ case MappedEvent::SystemMIDIClock:
+ case MappedEvent::SystemMIDISyncAuto:
+ break;
+
+ default:
+ case MappedEvent::InvalidMappedEvent:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processMidiOut - "
+ << "skipping unrecognised or invalid MappedEvent type"
+ << std::endl;
+#endif
+
+ continue;
+ }
+
+ if (isSoftSynth) {
+
+ processSoftSynthEventOut((*i)->getInstrument(), &event, now);
+
+ } else {
+ checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
+ "processMidiOut(): output queued");
+
+ if (now) {
+ if (m_queueRunning && !m_playing) {
+ // restart queue
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut: restarting queue after now-event" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
+ }
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
+ }
+ }
+
+ // Add note to note off stack
+ //
+ if (needNoteOff) {
+ NoteOffEvent *noteOffEvent =
+ new NoteOffEvent(outputStopTime, // already calculated
+ (*i)->getPitch(),
+ channel,
+ (*i)->getInstrument());
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "Adding NOTE OFF at " << outputStopTime
+ << std::endl;
+#endif
+
+ m_noteOffQueue.insert(noteOffEvent);
+ }
+ }
+
+ processNotesOff(sliceEnd - m_playStartPosition + m_alsaPlayStartTime, now);
+
+ if (getMTCStatus() == TRANSPORT_MASTER) {
+ insertMTCQFrames(sliceStart, sliceEnd);
+ }
+
+ if (m_queueRunning) {
+
+ if (now && !m_playing) {
+ // just to be sure
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut: restarting queue after all now-events" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ // std::cerr << "processMidiOut: m_queueRunning " << m_queueRunning
+ // << ", now " << now << std::endl;
+#endif
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
+ }
+}
+
+void
+AlsaDriver::processSoftSynthEventOut(InstrumentId id, const snd_seq_event_t *ev, bool now)
+{
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: instrument " << id << ", now " << now << std::endl;
+#endif
+
+#ifdef HAVE_LIBJACK
+
+ if (!m_jackDriver)
+ return ;
+ RunnablePluginInstance *synthPlugin = m_jackDriver->getSynthPlugin(id);
+
+ if (synthPlugin) {
+
+ RealTime t(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+
+ if (now)
+ t = RealTime::zeroTime;
+ else
+ t = t + m_playStartPosition - m_alsaPlayStartTime;
+
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: event time " << t << std::endl;
+#endif
+
+ synthPlugin->sendEvent(t, ev);
+
+ if (now) {
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: setting haveAsyncAudioEvent" << std::endl;
+#endif
+
+ m_jackDriver->setHaveAsyncAudioEvent();
+ }
+ }
+#endif
+}
+
+void
+AlsaDriver::startClocks()
+{
+ int result;
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::startClocks" << std::endl;
+#endif
+
+ if (m_needJackStart) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: Need JACK start (m_playing = " << m_playing << ")" << std::endl;
+#endif
+
+ }
+
+#ifdef HAVE_LIBJACK
+
+ // New JACK transport scheme: The initialisePlayback,
+ // resetPlayback and stopPlayback methods set m_needJackStart, and
+ // then this method checks it and calls the appropriate JACK
+ // transport start or relocate method, which calls back on
+ // startClocksApproved when ready. (Previously this method always
+ // called the JACK transport start method, so we couldn't handle
+ // moving the pointer when not playing, and we had to stop the
+ // transport explicitly from resetPlayback when repositioning
+ // during playback.)
+
+ if (m_jackDriver) {
+
+ // Don't need any locks on this, except for those that the
+ // driver methods take and hold for themselves
+
+ if (m_needJackStart != NeedNoJackStart) {
+ if (m_needJackStart == NeedJackStart ||
+ m_playing) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: playing, prebuffer audio" << std::endl;
+#endif
+
+ m_jackDriver->prebufferAudio();
+ } else {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: prepare audio only" << std::endl;
+#endif
+
+ m_jackDriver->prepareAudio();
+ }
+ bool rv;
+ if (m_needJackStart == NeedJackReposition) {
+ rv = m_jackDriver->relocateTransport();
+ } else {
+ rv = m_jackDriver->startTransport();
+ if (!rv) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: Waiting for startClocksApproved" << std::endl;
+#endif
+ // need to wait for transport sync
+ _debug_jack_frame_count = m_jackDriver->getFramesProcessed();
+ return ;
+ }
+ }
+ }
+ }
+#endif
+
+ // Restart the timer
+ if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
+ std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
+ << snd_strerror(result)
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ }
+
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: started clocks" << std::endl;
+#endif
+
+ m_queueRunning = true;
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver) {
+ _debug_jack_frame_count = m_jackDriver->getFramesProcessed();
+ }
+#endif
+
+ // process pending MIDI events
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocks(): draining");
+}
+
+void
+AlsaDriver::startClocksApproved()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: startClocksApproved" << std::endl;
+#endif
+
+ //!!!
+ m_needJackStart = NeedNoJackStart;
+ startClocks();
+ return ;
+
+ int result;
+
+ // Restart the timer
+ if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
+ std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
+ << snd_strerror(result)
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ }
+
+ m_queueRunning = true;
+
+ // process pending MIDI events
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocksApproved(): draining");
+}
+
+void
+AlsaDriver::stopClocks()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::stopClocks" << std::endl;
+#endif
+
+ if (checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "stopClocks(): stopping queue") < 0) {
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ }
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to stop queue");
+
+ m_queueRunning = false;
+
+ // We used to call m_jackDriver->stop() from here, but we no
+ // longer do -- it's now called from stopPlayback() so as to
+ // handle repositioning during playback (when stopClocks is
+ // necessary but stopPlayback and m_jackDriver->stop() are not).
+
+ snd_seq_event_t event;
+ snd_seq_ev_clear(&event);
+ snd_seq_real_time_t z = { 0, 0 };
+ snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
+ snd_seq_ev_set_direct(&event);
+ checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
+ 0, &event), "stopClocks(): setting zpos to queue");
+ // process that
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to zpos queue");
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::stopClocks: ALSA time now is " << getAlsaTime() << std::endl;
+#endif
+
+ m_alsaPlayStartTime = RealTime::zeroTime;
+}
+
+
+void
+AlsaDriver::processEventsOut(const MappedComposition &mC)
+{
+ processEventsOut(mC, RealTime::zeroTime, RealTime::zeroTime);
+}
+
+void
+AlsaDriver::processEventsOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd)
+{
+ // special case for unqueued events
+ bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);
+
+ if (m_startPlayback) {
+ m_startPlayback = false;
+ // This only records whether we're playing in principle,
+ // not whether the clocks are actually ticking. Contrariwise,
+ // areClocksRunning tells us whether the clocks are ticking
+ // but not whether we're actually playing (the clocks go even
+ // when we're not). Check both if you want to know whether
+ // we're really rolling.
+ m_playing = true;
+
+ if (getMTCStatus() == TRANSPORT_SLAVE) {
+ tweakSkewForMTC(0);
+ }
+ }
+
+ AudioFile *audioFile = 0;
+ bool haveNewAudio = false;
+
+ // insert audio events if we find them
+ for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
+#ifdef HAVE_LIBJACK
+
+ // Play an audio file
+ //
+ if ((*i)->getType() == MappedEvent::Audio) {
+ if (!m_jackDriver)
+ continue;
+
+ // This is used for handling asynchronous
+ // (i.e. unexpected) audio events only
+
+ if ((*i)->getEventTime() > RealTime( -120, 0)) {
+ // Not an asynchronous event
+ continue;
+ }
+
+ // Check for existence of file - if the sequencer has died
+ // and been restarted then we're not always loaded up with
+ // the audio file references we should have. In the future
+ // we could make this just get the gui to reload our files
+ // when (or before) this fails.
+ //
+ audioFile = getAudioFile((*i)->getAudioID());
+
+ if (audioFile) {
+ MappedAudioFader *fader =
+ dynamic_cast<MappedAudioFader*>
+ (getMappedStudio()->getAudioFader((*i)->getInstrument()));
+
+ if (!fader) {
+ std::cerr << "WARNING: AlsaDriver::processEventsOut: no fader for audio instrument " << (*i)->getInstrument() << std::endl;
+ continue;
+ }
+
+ unsigned int channels = fader->getPropertyList(
+ MappedAudioFader::Channels)[0].toInt();
+
+ RealTime bufferLength = getAudioReadBufferLength();
+ int bufferFrames = RealTime::realTime2Frame
+ (bufferLength, m_jackDriver->getSampleRate());
+ if (bufferFrames % m_jackDriver->getBufferSize()) {
+ bufferFrames /= m_jackDriver->getBufferSize();
+ bufferFrames ++;
+ bufferFrames *= m_jackDriver->getBufferSize();
+ }
+
+ //#define DEBUG_PLAYING_AUDIO
+#ifdef DEBUG_PLAYING_AUDIO
+ std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << (*i)->getEventTime() << ", time now " << getAlsaTime() << ", start marker " << (*i)->getAudioStartMarker() << ", duration " << (*i)->getDuration() << ", instrument " << (*i)->getInstrument() << " channels " << channels << std::endl;
+
+ std::cout << "Read buffer length is " << bufferLength << " (" << bufferFrames << " frames)" << std::endl;
+#endif
+
+ PlayableAudioFile *paf = 0;
+
+ try {
+ paf = new PlayableAudioFile((*i)->getInstrument(),
+ audioFile,
+ getSequencerTime() +
+ (RealTime(1, 0) / 4),
+ (*i)->getAudioStartMarker(),
+ (*i)->getDuration(),
+ bufferFrames,
+ getSmallFileSize() * 1024,
+ channels,
+ m_jackDriver->getSampleRate());
+ } catch (...) {
+ continue;
+ }
+
+ if ((*i)->isAutoFading()) {
+ paf->setAutoFade(true);
+ paf->setFadeInTime((*i)->getFadeInTime());
+ paf->setFadeOutTime((*i)->getFadeInTime());
+
+ //#define DEBUG_AUTOFADING
+#ifdef DEBUG_AUTOFADING
+
+ std::cout << "PlayableAudioFile is AUTOFADING - "
+ << "in = " << (*i)->getFadeInTime()
+ << ", out = " << (*i)->getFadeOutTime()
+ << std::endl;
+#endif
+
+ }
+#ifdef DEBUG_AUTOFADING
+ else {
+ std::cout << "PlayableAudioFile has no AUTOFADE"
+ << std::endl;
+ }
+#endif
+
+
+ // segment runtime id
+ paf->setRuntimeSegmentId((*i)->getRuntimeSegmentId());
+
+ m_audioQueue->addUnscheduled(paf);
+
+ haveNewAudio = true;
+ } else {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "can't find audio file reference"
+ << std::endl;
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "try reloading the current Rosegarden file"
+ << std::endl;
+#else
+
+ ;
+#endif
+
+ }
+ }
+
+ // Cancel a playing audio file preview (this is predicated on
+ // runtime segment ID and optionally start time)
+ //
+ if ((*i)->getType() == MappedEvent::AudioCancel) {
+ cancelAudioFile(*i);
+ }
+
+#endif // HAVE_LIBJACK
+
+ if ((*i)->getType() == MappedEvent::SystemMIDIClock) {
+ switch ((int)(*i)->getData1()) {
+ case 0:
+ m_midiClockEnabled = false;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MIDI CLOCK, START and STOP DISABLED"
+ << std::endl;
+#endif
+
+ setMIDISyncStatus(TRANSPORT_OFF);
+ break;
+
+ case 1:
+ m_midiClockEnabled = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden send MIDI CLOCK, START and STOP ENABLED"
+ << std::endl;
+#endif
+
+ setMIDISyncStatus(TRANSPORT_MASTER);
+ break;
+
+ case 2:
+ m_midiClockEnabled = false;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden accept START and STOP ENABLED"
+ << std::endl;
+#endif
+
+ setMIDISyncStatus(TRANSPORT_SLAVE);
+ break;
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemMIDISyncAuto) {
+ if ((*i)->getData1()) {
+ m_midiSyncAutoConnect = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MIDI SYNC AUTO ENABLED"
+ << std::endl;
+#endif
+
+ for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
+ dpmi != m_devicePortMap.end(); ++dpmi) {
+ snd_seq_connect_to(m_midiHandle,
+ m_syncOutputPort,
+ dpmi->second.first,
+ dpmi->second.second);
+ }
+ } else {
+ m_midiSyncAutoConnect = false;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MIDI SYNC AUTO DISABLED"
+ << std::endl;
+#endif
+
+ }
+ }
+
+#ifdef HAVE_LIBJACK
+
+ // Set the JACK transport
+ if ((*i)->getType() == MappedEvent::SystemJackTransport) {
+ bool enabled = false;
+ bool master = false;
+
+ switch ((int)(*i)->getData1()) {
+ case 2:
+ master = true;
+ enabled = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden to follow JACK transport and request JACK timebase master role (not yet implemented)"
+ << std::endl;
+#endif
+
+ break;
+
+ case 1:
+ enabled = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden to follow JACK transport"
+ << std::endl;
+#endif
+
+ break;
+
+ case 0:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden to ignore JACK transport"
+ << std::endl;
+#endif
+
+ break;
+ }
+
+ if (m_jackDriver) {
+ m_jackDriver->setTransportEnabled(enabled);
+ m_jackDriver->setTransportMaster(master);
+ }
+ }
+#endif // HAVE_LIBJACK
+
+
+ if ((*i)->getType() == MappedEvent::SystemMMCTransport) {
+ switch ((int)(*i)->getData1()) {
+ case 1:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MMC MASTER"
+ << std::endl;
+#endif
+
+ setMMCStatus(TRANSPORT_MASTER);
+ break;
+
+ case 2:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MMC SLAVE"
+ << std::endl;
+#endif
+
+ setMMCStatus(TRANSPORT_SLAVE);
+ break;
+
+ case 0:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MMC Transport DISABLED"
+ << std::endl;
+#endif
+
+ setMMCStatus(TRANSPORT_OFF);
+ break;
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemMTCTransport) {
+ switch ((int)(*i)->getData1()) {
+ case 1:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MTC MASTER"
+ << std::endl;
+#endif
+
+ setMTCStatus(TRANSPORT_MASTER);
+ tweakSkewForMTC(0);
+ m_mtcFirstTime = -1;
+ break;
+
+ case 2:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MTC SLAVE"
+ << std::endl;
+#endif
+
+ setMTCStatus(TRANSPORT_SLAVE);
+ m_mtcFirstTime = -1;
+ break;
+
+ case 0:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MTC Transport DISABLED"
+ << std::endl;
+#endif
+
+ setMTCStatus(TRANSPORT_OFF);
+ m_mtcFirstTime = -1;
+ break;
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemRecordDevice) {
+ DeviceId recordDevice =
+ (DeviceId)((*i)->getData1());
+ bool conn = (bool) ((*i)->getData2());
+
+ // Unset connections
+ //
+ // unsetRecordDevices();
+
+ // Special case to set for all record ports
+ //
+ if (recordDevice == Device::ALL_DEVICES) {
+ /* set all record devices */
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "set all record devices - not implemented"
+ << std::endl;
+#endif
+
+ /*
+ MappedDeviceList::iterator it = m_devices.begin();
+ std::vector<int> ports;
+ std::vector<int>::iterator pIt;
+
+ for (; it != m_devices.end(); ++it)
+ {
+ std::cout << "DEVICE = " << (*it)->getName() << " - DIR = "
+ << (*it)->getDirection() << endl;
+ // ignore ports we can't connect to
+ if ((*it)->getDirection() == MidiDevice::WriteOnly) continue;
+
+ std::cout << "PORTS = " << ports.size() << endl;
+ ports = (*it)->getPorts();
+ for (pIt = ports.begin(); pIt != ports.end(); ++pIt)
+ {
+ setRecordDevice((*it)->getClient(), *pIt);
+ }
+ }
+ */
+ } else {
+ // Otherwise just for the one device and port
+ //
+ setRecordDevice(recordDevice, conn);
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemAudioPortCounts) {
+ // never actually used, I think?
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemAudioPorts) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ int data = (*i)->getData1();
+ m_jackDriver->setAudioPorts(data & MappedEvent::FaderOuts,
+ data & MappedEvent::SubmasterOuts);
+ }
+#else
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "MappedEvent::SystemAudioPorts - no audio subsystem"
+ << std::endl;
+#endif
+#endif
+
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemAudioFileFormat) {
+#ifdef HAVE_LIBJACK
+ int format = (*i)->getData1();
+ switch (format) {
+ case 0:
+ m_audioRecFileFormat = RIFFAudioFile::PCM;
+ break;
+ case 1:
+ m_audioRecFileFormat = RIFFAudioFile::FLOAT;
+ break;
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "MappedEvent::SystemAudioFileFormat - unexpected format number " << format
+ << std::endl;
+#endif
+
+ break;
+ }
+#else
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "MappedEvent::SystemAudioFileFormat - no audio subsystem"
+ << std::endl;
+#endif
+#endif
+
+ }
+
+ if ((*i)->getType() == MappedEvent::Panic) {
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ if ((*i)->getDirection() == MidiDevice::Play) {
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_SUSTAIN, 0);
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_RESET, 0);
+ }
+ }
+ }
+ }
+
+ // Process Midi and Audio
+ //
+ processMidiOut(mC, sliceStart, sliceEnd);
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver) {
+ if (haveNewAudio) {
+ if (now) {
+ m_jackDriver->prebufferAudio();
+ m_jackDriver->setHaveAsyncAudioEvent();
+ }
+ if (m_queueRunning) {
+ m_jackDriver->kickAudio();
+ }
+ }
+ }
+#endif
+}
+
+bool
+AlsaDriver::record(RecordStatus recordStatus,
+ const std::vector<InstrumentId> *armedInstruments,
+ const std::vector<QString> *audioFileNames)
+{
+ m_recordingInstruments.clear();
+
+ if (recordStatus == RECORD_ON) {
+ // start recording
+ m_recordStatus = RECORD_ON;
+ m_alsaRecordStartTime = RealTime::zeroTime;
+
+ unsigned int audioCount = 0;
+
+ if (armedInstruments) {
+
+ for (unsigned int i = 0; i < armedInstruments->size(); ++i) {
+
+ InstrumentId id = (*armedInstruments)[i];
+
+ m_recordingInstruments.insert(id);
+ if (!audioFileNames || (audioCount >= audioFileNames->size())) {
+ continue;
+ }
+
+ QString fileName = (*audioFileNames)[audioCount];
+
+ if (id >= AudioInstrumentBase &&
+ id < MidiInstrumentBase) {
+
+ bool good = false;
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::record: Requesting new record file \"" << fileName << "\" for instrument " << id << std::endl;
+#endif
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver &&
+ m_jackDriver->openRecordFile(id, fileName.data())) {
+ good = true;
+ }
+#endif
+
+ if (!good) {
+ m_recordStatus = RECORD_OFF;
+ std::cerr << "AlsaDriver::record: No JACK driver, or JACK driver failed to prepare for recording audio" << std::endl;
+ return false;
+ }
+
+ ++audioCount;
+ }
+ }
+ }
+ } else
+ if (recordStatus == RECORD_OFF) {
+ m_recordStatus = RECORD_OFF;
+ }
+#ifdef DEBUG_ALSA
+ else {
+ std::cerr << "AlsaDriver::record - unsupported recording mode"
+ << std::endl;
+ }
+#endif
+
+ return true;
+}
+
+ClientPortPair
+AlsaDriver::getFirstDestination(bool duplex)
+{
+ ClientPortPair destPair( -1, -1);
+ AlsaPortList::iterator it;
+
+ for (it = m_alsaPorts.begin(); it != m_alsaPorts.end(); ++it) {
+ destPair.first = (*it)->m_client;
+ destPair.second = (*it)->m_port;
+
+ // If duplex port is required then choose first one
+ //
+ if (duplex) {
+ if ((*it)->m_direction == Duplex)
+ return destPair;
+ } else {
+ // If duplex port isn't required then choose first
+ // specifically non-duplex port (should be a synth)
+ //
+ if ((*it)->m_direction != Duplex)
+ return destPair;
+ }
+ }
+
+ return destPair;
+}
+
+
+// Sort through the ALSA client/port pairs for the range that
+// matches the one we're querying. If none matches then send
+// back -1 for each.
+//
+ClientPortPair
+AlsaDriver::getPairForMappedInstrument(InstrumentId id)
+{
+ MappedInstrument *instrument = getMappedInstrument(id);
+ if (instrument) {
+ DeviceId device = instrument->getDevice();
+ DevicePortMap::iterator i = m_devicePortMap.find(device);
+ if (i != m_devicePortMap.end()) {
+ return i->second;
+ }
+ }
+#ifdef DEBUG_ALSA
+ /*
+ else
+ {
+ cerr << "WARNING: AlsaDriver::getPairForMappedInstrument: couldn't find instrument for id " << id << ", falling through" << endl;
+ }
+ */
+#endif
+
+ return ClientPortPair( -1, -1);
+}
+
+int
+AlsaDriver::getOutputPortForMappedInstrument(InstrumentId id)
+{
+ MappedInstrument *instrument = getMappedInstrument(id);
+ if (instrument) {
+ DeviceId device = instrument->getDevice();
+ DeviceIntMap::iterator i = m_outputPorts.find(device);
+ if (i != m_outputPorts.end()) {
+ return i->second;
+ }
+#ifdef DEBUG_ALSA
+ else {
+ cerr << "WARNING: AlsaDriver::getOutputPortForMappedInstrument: couldn't find output port for device for instrument " << id << ", falling through" << endl;
+ }
+#endif
+
+ }
+
+ return -1;
+}
+
+// Send a direct controller to the specified port/client
+//
+void
+AlsaDriver::sendDeviceController(DeviceId device,
+ MidiByte controller,
+ MidiByte value)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+
+ snd_seq_ev_set_subs(&event);
+
+ DeviceIntMap::iterator dimi = m_outputPorts.find(device);
+ if (dimi == m_outputPorts.end())
+ return ;
+
+ snd_seq_ev_set_source(&event, dimi->second);
+ snd_seq_ev_set_direct(&event);
+
+ for (int i = 0; i < 16; i++) {
+ snd_seq_ev_set_controller(&event,
+ i,
+ controller,
+ value);
+ snd_seq_event_output_direct(m_midiHandle, &event);
+ }
+
+ // we probably don't need this:
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendDeviceController(): draining");
+}
+
+void
+AlsaDriver::processPending()
+{
+ if (!m_playing) {
+ processNotesOff(getAlsaTime(), true);
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processPending(): draining");
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_jackDriver->updateAudioData();
+ }
+#endif
+
+ scavengePlugins();
+ m_audioQueueScavenger.scavenge();
+}
+
+void
+AlsaDriver::insertMappedEventForReturn(MappedEvent *mE)
+{
+ // Insert the event ready for return at the next opportunity.
+ //
+ m_returnComposition.insert(mE);
+}
+
+// check for recording status on any ALSA Port
+//
+bool
+AlsaDriver::isRecording(AlsaPortDescription *port)
+{
+ if (port->isReadable()) {
+
+ snd_seq_query_subscribe_t *qSubs;
+ snd_seq_addr_t rg_addr, sender_addr;
+ snd_seq_query_subscribe_alloca(&qSubs);
+
+ rg_addr.client = m_client;
+ rg_addr.port = m_inputPort;
+
+ snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
+ snd_seq_query_subscribe_set_index(qSubs, 0);
+ snd_seq_query_subscribe_set_root(qSubs, &rg_addr);
+
+ while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
+ sender_addr = *snd_seq_query_subscribe_get_addr(qSubs);
+ if (sender_addr.client == port->m_client &&
+ sender_addr.port == port->m_port)
+ return true;
+
+ snd_seq_query_subscribe_set_index(qSubs,
+ snd_seq_query_subscribe_get_index(qSubs) + 1);
+ }
+ }
+ return false;
+}
+
+bool
+AlsaDriver::checkForNewClients()
+{
+ Audit audit;
+ bool madeChange = false;
+
+ if (!m_portCheckNeeded)
+ return false;
+
+ AlsaPortList newPorts;
+ generatePortList(&newPorts); // updates m_alsaPorts, returns new ports as well
+
+ // If any devices have connections that no longer exist,
+ // clear those connections and stick them in the suspended
+ // port map in case they come back online later.
+
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+
+ ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
+
+ bool found = false;
+ for (AlsaPortList::iterator j = m_alsaPorts.begin();
+ j != m_alsaPorts.end(); ++j) {
+ if ((*j)->m_client == pair.first &&
+ (*j)->m_port == pair.second) {
+ if ((*i)->getDirection() == MidiDevice::Record) {
+ bool recState = isRecording(*j);
+ if (recState != (*i)->isRecording()) {
+ madeChange = true;
+ (*i)->setRecording(recState);
+ }
+ } else {
+ (*i)->setRecording(false);
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ m_suspendedPortMap[pair] = (*i)->getId();
+ m_devicePortMap[(*i)->getId()] = ClientPortPair( -1, -1);
+ setConnectionToDevice(**i, "");
+ (*i)->setRecording(false);
+ madeChange = true;
+ }
+ }
+
+ // If we've increased the number of connections, we need
+ // to assign the new connections to existing devices that
+ // have none, where possible, and create new devices for
+ // any left over.
+
+ if (newPorts.size() > 0) {
+
+ audit << "New ports:" << std::endl;
+
+ for (AlsaPortList::iterator i = newPorts.begin();
+ i != newPorts.end(); ++i) {
+
+ if ((*i)->m_client == m_client) {
+ audit << "(Ignoring own port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
+ continue;
+ } else if ((*i)->m_client == 0) {
+ audit << "(Ignoring system port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
+ continue;
+ }
+
+ audit << (*i)->m_name << std::endl;
+
+ QString portName = (*i)->m_name.c_str();
+ ClientPortPair portPair = ClientPortPair((*i)->m_client,
+ (*i)->m_port);
+
+ if (m_suspendedPortMap.find(portPair) != m_suspendedPortMap.end()) {
+
+ DeviceId id = m_suspendedPortMap[portPair];
+
+ audit << "(Reusing suspended device " << id << ")" << std::endl;
+
+ for (MappedDeviceList::iterator j = m_devices.begin();
+ j != m_devices.end(); ++j) {
+ if ((*j)->getId() == id) {
+ setConnectionToDevice(**j, portName);
+ }
+ }
+
+ m_suspendedPortMap.erase(m_suspendedPortMap.find(portPair));
+ m_devicePortMap[id] = portPair;
+ madeChange = true;
+ continue;
+ }
+
+ bool needPlayDevice = true, needRecordDevice = true;
+
+ if ((*i)->isReadable()) {
+ for (MappedDeviceList::iterator j = m_devices.begin();
+ j != m_devices.end(); ++j) {
+ if ((*j)->getType() == Device::Midi &&
+ (*j)->getConnection() == "" &&
+ (*j)->getDirection() == MidiDevice::Record) {
+ audit << "(Reusing record device " << (*j)->getId()
+ << ")" << std::endl;
+ m_devicePortMap[(*j)->getId()] = portPair;
+ setConnectionToDevice(**j, portName);
+ needRecordDevice = false;
+ madeChange = true;
+ break;
+ }
+ }
+ } else {
+ needRecordDevice = false;
+ }
+
+ if ((*i)->isWriteable()) {
+ for (MappedDeviceList::iterator j = m_devices.begin();
+ j != m_devices.end(); ++j) {
+ if ((*j)->getType() == Device::Midi &&
+ (*j)->getConnection() == "" &&
+ (*j)->getDirection() == MidiDevice::Play) {
+ audit << "(Reusing play device " << (*j)->getId()
+ << ")" << std::endl;
+ m_devicePortMap[(*j)->getId()] = portPair;
+ setConnectionToDevice(**j, portName);
+ needPlayDevice = false;
+ madeChange = true;
+ break;
+ }
+ }
+ } else {
+ needPlayDevice = false;
+ }
+
+ if (needRecordDevice) {
+ MappedDevice *device = createMidiDevice(*i, MidiDevice::Record);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create record device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ audit << "(Created new record device " << device->getId() << ")" << std::endl;
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+ madeChange = true;
+ }
+ }
+
+ if (needPlayDevice) {
+ MappedDevice *device = createMidiDevice(*i, MidiDevice::Play);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create play device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ audit << "(Created new play device " << device->getId() << ")" << std::endl;
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+ madeChange = true;
+ }
+ }
+ }
+ }
+
+ // If one of our ports is connected to a single other port and
+ // it isn't the one we thought, we should update our connection
+
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+
+ DevicePortMap::iterator j = m_devicePortMap.find((*i)->getId());
+
+ snd_seq_addr_t addr;
+ addr.client = m_client;
+
+ DeviceIntMap::iterator ii = m_outputPorts.find((*i)->getId());
+ if (ii == m_outputPorts.end())
+ continue;
+ addr.port = ii->second;
+
+ snd_seq_query_subscribe_t *subs;
+ snd_seq_query_subscribe_alloca(&subs);
+ snd_seq_query_subscribe_set_root(subs, &addr);
+ snd_seq_query_subscribe_set_index(subs, 0);
+
+ bool haveOurs = false;
+ int others = 0;
+ ClientPortPair firstOther;
+
+ while (!snd_seq_query_port_subscribers(m_midiHandle, subs)) {
+
+ const snd_seq_addr_t *otherEnd =
+ snd_seq_query_subscribe_get_addr(subs);
+
+ if (!otherEnd)
+ continue;
+
+ if (j != m_devicePortMap.end() &&
+ otherEnd->client == j->second.first &&
+ otherEnd->port == j->second.second) {
+ haveOurs = true;
+ } else {
+ ++others;
+ firstOther = ClientPortPair(otherEnd->client, otherEnd->port);
+ }
+
+ snd_seq_query_subscribe_set_index
+ (subs, snd_seq_query_subscribe_get_index(subs) + 1);
+ }
+
+ if (haveOurs) { // leave our own connection alone, and stop worrying
+ continue;
+
+ } else {
+ if (others == 0) {
+ if (j != m_devicePortMap.end()) {
+ j->second = ClientPortPair( -1, -1);
+ setConnectionToDevice(**i, "");
+ madeChange = true;
+ }
+ } else {
+ for (AlsaPortList::iterator k = m_alsaPorts.begin();
+ k != m_alsaPorts.end(); ++k) {
+ if ((*k)->m_client == firstOther.first &&
+ (*k)->m_port == firstOther.second) {
+ m_devicePortMap[(*i)->getId()] = firstOther;
+ setConnectionToDevice(**i, (*k)->m_name.c_str(),
+ firstOther);
+ madeChange = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (madeChange) {
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ // send completion event
+ insertMappedEventForReturn(mE);
+ }
+
+ m_portCheckNeeded = false;
+
+ return true;
+}
+
+
+// From a DeviceId get a client/port pair for connecting as the
+// MIDI record device.
+//
+void
+AlsaDriver::setRecordDevice(DeviceId id, bool connectAction)
+{
+ Audit audit;
+
+ // Locate a suitable port
+ //
+ if (m_devicePortMap.find(id) == m_devicePortMap.end()) {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "couldn't match device id (" << id << ") to ALSA port"
+ << std::endl;
+#endif
+
+ return ;
+ }
+
+ ClientPortPair pair = m_devicePortMap[id];
+
+ snd_seq_addr_t sender, dest;
+ sender.client = pair.first;
+ sender.port = pair.second;
+
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ if ((*i)->getId() == id) {
+ if ((*i)->getDirection() == MidiDevice::Record) {
+ if ((*i)->isRecording() && connectAction) {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "attempting to subscribe (" << id
+ << ") already subscribed" << std::endl;
+#endif
+
+ return ;
+ }
+ if (!(*i)->isRecording() && !connectAction) {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "attempting to unsubscribe (" << id
+ << ") already unsubscribed" << std::endl;
+#endif
+
+ return ;
+ }
+ } else {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "attempting to set play device (" << id
+ << ") to record device" << std::endl;
+#endif
+
+ return ;
+ }
+ break;
+ }
+ }
+
+ snd_seq_port_subscribe_t *subs;
+ snd_seq_port_subscribe_alloca(&subs);
+
+ dest.client = m_client;
+ dest.port = m_inputPort;
+
+ // Set destinations and senders
+ //
+ snd_seq_port_subscribe_set_sender(subs, &sender);
+ snd_seq_port_subscribe_set_dest(subs, &dest);
+
+ // subscribe or unsubscribe the port
+ //
+ if (connectAction) {
+ if (checkAlsaError(snd_seq_subscribe_port(m_midiHandle, subs),
+ "setRecordDevice - failed subscription of input port") < 0) {
+ // Not the end of the world if this fails but we
+ // have to flag it internally.
+ //
+ audit << "AlsaDriver::setRecordDevice - "
+ << int(sender.client) << ":" << int(sender.port)
+ << " failed to subscribe device "
+ << id << " as record port" << std::endl;
+ } else {
+ m_midiInputPortConnected = true;
+ audit << "AlsaDriver::setRecordDevice - "
+ << "successfully subscribed device "
+ << id << " as record port" << std::endl;
+ }
+ } else {
+ if (checkAlsaError(snd_seq_unsubscribe_port(m_midiHandle, subs),
+ "setRecordDevice - failed to unsubscribe a device") == 0)
+ audit << "AlsaDriver::setRecordDevice - "
+ << "successfully unsubscribed device "
+ << id << " as record port" << std::endl;
+
+ }
+}
+
+// Clear any record device connections
+//
+void
+AlsaDriver::unsetRecordDevices()
+{
+ snd_seq_addr_t dest;
+ dest.client = m_client;
+ dest.port = m_inputPort;
+
+ snd_seq_query_subscribe_t *qSubs;
+ snd_seq_addr_t tmp_addr;
+ snd_seq_query_subscribe_alloca(&qSubs);
+
+ tmp_addr.client = m_client;
+ tmp_addr.port = m_inputPort;
+
+ // Unsubsribe any existing connections
+ //
+ snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
+ snd_seq_query_subscribe_set_index(qSubs, 0);
+ snd_seq_query_subscribe_set_root(qSubs, &tmp_addr);
+
+ while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
+ tmp_addr = *snd_seq_query_subscribe_get_addr(qSubs);
+
+ snd_seq_port_subscribe_t *dSubs;
+ snd_seq_port_subscribe_alloca(&dSubs);
+
+ snd_seq_addr_t dSender;
+ dSender.client = tmp_addr.client;
+ dSender.port = tmp_addr.port;
+
+ snd_seq_port_subscribe_set_sender(dSubs, &dSender);
+ snd_seq_port_subscribe_set_dest(dSubs, &dest);
+
+ int error = snd_seq_unsubscribe_port(m_midiHandle, dSubs);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::unsetRecordDevices - "
+ << "can't unsubscribe record port" << std::endl;
+#endif
+
+ }
+
+ snd_seq_query_subscribe_set_index(qSubs,
+ snd_seq_query_subscribe_get_index(qSubs) + 1);
+ }
+}
+
+void
+AlsaDriver::sendMMC(MidiByte deviceArg,
+ MidiByte instruction,
+ bool isCommand,
+ const std::string &data)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ unsigned char dataArr[10] =
+ { MIDI_SYSTEM_EXCLUSIVE,
+ MIDI_SYSEX_RT, deviceArg,
+ (isCommand ? MIDI_SYSEX_RT_COMMAND : MIDI_SYSEX_RT_RESPONSE),
+ instruction };
+
+ std::string dataString = std::string((const char *)dataArr) +
+ data + (char)MIDI_END_OF_EXCLUSIVE;
+
+ snd_seq_ev_set_sysex(&event, dataString.length(),
+ (char *)dataString.c_str());
+
+ event.queue = SND_SEQ_QUEUE_DIRECT;
+
+ checkAlsaError(snd_seq_event_output_direct(m_midiHandle, &event),
+ "sendMMC event send");
+
+ if (m_queueRunning) {
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendMMC drain");
+ }
+}
+
+// Send a system real-time message from the sync output port
+//
+void
+AlsaDriver::sendSystemDirect(MidiByte command, int *args)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ event.queue = SND_SEQ_QUEUE_DIRECT;
+
+ // set the command
+ event.type = command;
+
+ // set args if we have them
+ if (args) {
+ event.data.control.value = *args;
+ }
+
+ int error = snd_seq_event_output_direct(m_midiHandle, &event);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::sendSystemDirect - "
+ << "can't send event (" << int(command) << ")"
+ << std::endl;
+#endif
+
+ }
+
+ // checkAlsaError(snd_seq_drain_output(m_midiHandle),
+ // "sendSystemDirect(): draining");
+}
+
+
+void
+AlsaDriver::sendSystemQueued(MidiByte command,
+ const std::string &args,
+ const RealTime &time)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ snd_seq_real_time_t sendTime = { time.sec, time.nsec };
+
+ // Schedule the command
+ //
+ event.type = command;
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &sendTime);
+
+ // set args if we have them
+ switch (args.length()) {
+ case 1:
+ event.data.control.value = args[0];
+ break;
+
+ case 2:
+ event.data.control.value = int(args[0]) | (int(args[1]) << 7);
+ break;
+
+ default: // do nothing
+ break;
+ }
+
+ int error = snd_seq_event_output(m_midiHandle, &event);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::sendSystemQueued - "
+ << "can't send event (" << int(command) << ")"
+ << " - error = (" << error << ")"
+ << std::endl;
+#endif
+
+ }
+
+ // if (m_queueRunning) {
+ // checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendSystemQueued(): draining");
+ // }
+}
+
+
+void
+AlsaDriver::claimUnwantedPlugin(void *plugin)
+{
+ m_pluginScavenger.claim((RunnablePluginInstance *)plugin);
+}
+
+
+void
+AlsaDriver::scavengePlugins()
+{
+ m_pluginScavenger.scavenge();
+}
+
+
+QString
+AlsaDriver::getStatusLog()
+{
+ return QString::fromUtf8(Audit::getAudit().c_str());
+}
+
+
+void
+AlsaDriver::sleep(const RealTime &rt)
+{
+ int npfd = snd_seq_poll_descriptors_count(m_midiHandle, POLLIN);
+ struct pollfd *pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
+ snd_seq_poll_descriptors(m_midiHandle, pfd, npfd, POLLIN);
+ poll(pfd, npfd, rt.sec * 1000 + rt.msec());
+}
+
+void
+AlsaDriver::runTasks()
+{
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ if (!m_jackDriver->isOK()) {
+ m_jackDriver->restoreIfRestorable();
+ }
+ }
+
+ if (m_doTimerChecks && m_timerRatioCalculated) {
+
+ double ratio = m_timerRatio;
+ m_timerRatioCalculated = false;
+
+ snd_seq_queue_tempo_t *q_ptr;
+ snd_seq_queue_tempo_alloca(&q_ptr);
+
+ snd_seq_get_queue_tempo(m_midiHandle, m_queue, q_ptr);
+
+ unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
+#ifdef DEBUG_ALSA
+
+ unsigned int t_base = snd_seq_queue_tempo_get_skew_base(q_ptr);
+ if (!m_playing) {
+ std::cerr << "Skew: " << t_skew << "/" << t_base;
+ }
+#endif
+
+ unsigned int newSkew = t_skew + (unsigned int)(t_skew * ratio);
+
+ if (newSkew != t_skew) {
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cerr << " changed to " << newSkew << endl;
+ }
+#endif
+ snd_seq_queue_tempo_set_skew(q_ptr, newSkew);
+ snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
+ } else {
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cerr << endl;
+ }
+#endif
+
+ }
+
+ m_firstTimerCheck = true;
+ }
+
+#endif
+}
+
+void
+AlsaDriver::reportFailure(MappedEvent::FailureCode code)
+{
+ //#define REPORT_XRUNS 1
+#ifndef REPORT_XRUNS
+ if (code == MappedEvent::FailureXRuns ||
+ code == MappedEvent::FailureDiscUnderrun ||
+ code == MappedEvent::FailureBussMixUnderrun ||
+ code == MappedEvent::FailureMixUnderrun) {
+ return ;
+ }
+#endif
+
+ // Ignore consecutive duplicates
+ if (_failureReportWriteIndex > 0 &&
+ _failureReportWriteIndex != _failureReportReadIndex) {
+ if (code == _failureReports[_failureReportWriteIndex - 1])
+ return ;
+ }
+
+ _failureReports[_failureReportWriteIndex] = code;
+ _failureReportWriteIndex =
+ (_failureReportWriteIndex + 1) % FAILURE_REPORT_COUNT;
+}
+
+std::string
+AlsaDriver::getAlsaModuleVersionString()
+{
+ FILE *v = fopen("/proc/asound/version", "r");
+
+ // Examples:
+ // Advanced Linux Sound Architecture Driver Version 1.0.14rc3.
+ // Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2008 UTC).
+
+ if (v) {
+ char buf[256];
+ fgets(buf, 256, v);
+ fclose(v);
+
+ std::string vs(buf);
+ std::string::size_type sp = vs.find_first_of('.');
+ if (sp > 0 && sp != std::string::npos) {
+ while (sp > 0 && isdigit(vs[sp-1])) --sp;
+ vs = vs.substr(sp);
+ if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ if (vs.length() > 0 && vs[vs.length()-1] == '.') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ return vs;
+ }
+ }
+
+ return "(unknown)";
+}
+
+std::string
+AlsaDriver::getKernelVersionString()
+{
+ FILE *v = fopen("/proc/version", "r");
+
+ if (v) {
+ char buf[256];
+ fgets(buf, 256, v);
+ fclose(v);
+
+ std::string vs(buf);
+ std::string key(" version ");
+ std::string::size_type sp = vs.find(key);
+ if (sp != std::string::npos) {
+ vs = vs.substr(sp + key.length());
+ sp = vs.find(' ');
+ if (sp != std::string::npos) {
+ vs = vs.substr(0, sp);
+ }
+ if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ return vs;
+ }
+ }
+
+ return "(unknown)";
+}
+
+void
+AlsaDriver::extractVersion(std::string v, int &major, int &minor, int &subminor, std::string &suffix)
+{
+ major = minor = subminor = 0;
+ suffix = "";
+ if (v == "(unknown)") return;
+
+ std::string::size_type sp, pp;
+
+ sp = v.find('.');
+ if (sp == std::string::npos) goto done;
+ major = atoi(v.substr(0, sp).c_str());
+ pp = sp + 1;
+
+ sp = v.find('.', pp);
+ if (sp == std::string::npos) goto done;
+ minor = atoi(v.substr(pp, sp - pp).c_str());
+ pp = sp + 1;
+
+ while (++sp < v.length() && (::isdigit(v[sp]) || v[sp] == '-'));
+ subminor = atoi(v.substr(pp, sp - pp).c_str());
+
+ if (sp >= v.length()) goto done;
+ suffix = v.substr(sp);
+
+done:
+ std::cerr << "extractVersion: major = " << major << ", minor = " << minor << ", subminor = " << subminor << ", suffix = \"" << suffix << "\"" << std::endl;
+}
+
+bool
+AlsaDriver::versionIsAtLeast(std::string v, int major, int minor, int subminor)
+{
+ int actualMajor, actualMinor, actualSubminor;
+ std::string actualSuffix;
+
+ extractVersion(v, actualMajor, actualMinor, actualSubminor, actualSuffix);
+
+ bool ok = false;
+
+ if (actualMajor > major) {
+ ok = true;
+ } else if (actualMajor == major) {
+ if (actualMinor > minor) {
+ ok = true;
+ } else if (actualMinor == minor) {
+ if (actualSubminor > subminor) {
+ ok = true;
+ } else if (actualSubminor == subminor) {
+ if (strncmp(actualSuffix.c_str(), "rc", 2) &&
+ strncmp(actualSuffix.c_str(), "pre", 3)) {
+ ok = true;
+ }
+ }
+ }
+ }
+
+ std::cerr << "AlsaDriver::versionIsAtLeast: is version " << v << " at least " << major << "." << minor << "." << subminor << "? " << (ok ? "yes" : "no") << std::endl;
+ return ok;
+}
+
+}
+
+
+#endif // HAVE_ALSA
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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+// Specialisation of SoundDriver to support ALSA (http://www.alsa-project.org)
+//
+//
+#ifndef _ALSADRIVER_H_
+#define _ALSADRIVER_H_
+
+#include <vector>
+#include <set>
+#include <map>
+
+#ifdef HAVE_ALSA
+
+#include <alsa/asoundlib.h> // 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<InstrumentId> *armedInstruments = 0,
+ const std::vector<QString> *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<AlsaPortDescription *> AlsaPortList;
+
+ ClientPortPair getFirstDestination(bool duplex);
+ ClientPortPair getPairForMappedInstrument(InstrumentId id);
+ int getOutputPortForMappedInstrument(InstrumentId id);
+ std::map<unsigned int, std::map<unsigned int, MappedEvent*> > 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<DeviceId, int> 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<RunnablePluginInstance> m_pluginScavenger;
+
+ //!!! -- hoist to SoundDriver w/setter?
+ typedef std::set<InstrumentId> InstrumentSet;
+ InstrumentSet m_recordingInstruments;
+
+ typedef std::map<DeviceId, ClientPortPair> DevicePortMap;
+ DevicePortMap m_devicePortMap;
+
+ typedef std::map<ClientPortPair, DeviceId> 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<AlsaTimerInfo> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AlsaPort.h"
+
+#include <iostream>
+#include <cstdlib>
+#include <cstdio>
+
+#ifdef HAVE_ALSA
+
+// ALSA
+#include <alsa/asoundlib.h>
+#include <alsa/seq_event.h>
+#include <alsa/version.h>
+
+#include "MappedInstrument.h"
+#include "Midi.h"
+#include "WAVAudioFile.h"
+#include "MappedStudio.h"
+#include "misc/Strings.h"
+
+#ifdef HAVE_LIBJACK
+#include <jack/types.h>
+#include <unistd.h> // for usleep
+#include <cmath>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <vector>
+#include <set>
+#include "Instrument.h"
+#include "MappedCommon.h"
+
+#ifndef _ALSAPORT_H_
+#define _ALSAPORT_H_
+
+#ifdef HAVE_ALSA
+#include <alsa/asoundlib.h> // ALSA
+
+namespace Rosegarden
+{
+
+typedef std::pair<int, int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AudioCache.h"
+#include <iostream>
+
+//#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<void *, CacheRec *>::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<void *, CacheRec *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIO_CACHE_H_
+#define _AUDIO_CACHE_H_
+
+#include <map>
+
+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<void *, CacheRec *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _AUDIOFILE_H_
+#define _AUDIOFILE_H_
+
+#include <string>
+#include <vector>
+#include <cmath>
+
+#include <qfileinfo.h>
+
+#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<float *> &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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include <iostream>
+#include <kapplication.h>
+#include <fstream>
+#include <string>
+#include <dirent.h> // for new recording file
+#include <cstdio> // sprintf
+#include <cstdlib>
+#include <pthread.h>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include <kapp.h>
+#include <klocale.h>
+#include <kprocess.h>
+#include <kio/netaccess.h>
+#include <kmessagebox.h>
+
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qdatetime.h>
+#include <qfile.h>
+
+#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<AudioFile*>::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<AudioFile*>::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<AudioFile*>::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<AudioFile*>::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<AudioFile*>::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<std::string>
+AudioFileManager::createRecordingAudioFiles(unsigned int n)
+{
+ std::vector<std::string> 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<AudioFile*>::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 << "<audiofiles";
+ if (m_expectedSampleRate != 0) {
+ audioFiles << " expectedRate=\"" << m_expectedSampleRate << "\"";
+ }
+ audioFiles << ">" << std::endl;
+ audioFiles << " <audioPath value=\""
+ << audioPath << "\"/>" << std::endl;
+
+ std::string fileName;
+ std::vector<AudioFile*>::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 << " <audio id=\""
+ << (*it)->getId()
+ << "\" file=\""
+ << fileName
+ << "\" label=\""
+ << encode((*it)->getName())
+ << "\"/>" << std::endl;
+ }
+
+ audioFiles << "</audiofiles>" << std::endl;
+
+#if (__GNUC__ < 3)
+
+ audioFiles << std::ends;
+#else
+
+ audioFiles << std::endl;
+#endif
+
+ return audioFiles.str();
+}
+
+// Generate preview peak files or peak chunks according
+// to file type.
+//
+void
+AudioFileManager::generatePreviews()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+#ifdef DEBUG_AUDIOFILEMANAGER
+
+ std::cout << "AudioFileManager::generatePreviews - "
+ << "for " << m_audioFiles.size() << " files"
+ << std::endl;
+#endif
+
+
+ // Generate peaks if we need to
+ //
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
+ if (!m_peakManager.hasValidPeaks(*it))
+ m_peakManager.generatePeaks(*it, 1);
+ }
+}
+
+// Attempt to stop a preview
+//
+void
+AudioFileManager::slotStopPreview()
+{
+ MutexLock lock (&_audioFileManagerLock);
+ m_peakManager.stopPreview();
+}
+
+
+// Generate a preview for a specific audio file - say if
+// one has just been added to the AudioFileManager.
+// Also used for generating previews if the file has been
+// modified.
+//
+bool
+AudioFileManager::generatePreview(AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+
+ if (audioFile == 0)
+ return false;
+
+ if (!m_peakManager.hasValidPeaks(audioFile))
+ m_peakManager.generatePeaks(audioFile, 1);
+
+ return true;
+}
+
+AudioFile*
+AudioFileManager::getAudioFile(AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ it++) {
+ if ((*it)->getId() == id)
+ return (*it);
+ }
+ return 0;
+}
+
+std::vector<float>
+AudioFileManager::getPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool withMinima)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+
+ if (audioFile == 0) {
+ return std::vector<float>();
+ }
+
+ if (!m_peakManager.hasValidPeaks(audioFile)) {
+ std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
+ throw PeakFileManager::BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ return m_peakManager.getPreview(audioFile,
+ startTime,
+ endTime,
+ width,
+ withMinima);
+}
+
+void
+AudioFileManager::drawPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ QPixmap *pixmap)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+ if (!audioFile)
+ return ;
+
+ if (!m_peakManager.hasValidPeaks(audioFile)) {
+ std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
+ throw PeakFileManager::BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ std::vector<float> values = m_peakManager.getPreview
+ (audioFile,
+ startTime,
+ endTime,
+ pixmap->width(),
+ false);
+
+ QPainter painter(pixmap);
+ pixmap->fill(kapp->palette().color(QPalette::Active,
+ QColorGroup::Base));
+ painter.setPen(kapp->palette().color(QPalette::Active,
+ QColorGroup::Dark));
+
+ if (values.size() == 0) {
+#ifdef DEBUG_AUDIOFILEMANAGER
+ std::cerr << "AudioFileManager::drawPreview - "
+ << "no preview values returned!" << std::endl;
+#endif
+
+ return ;
+ }
+
+ float yStep = pixmap->height() / 2;
+ int channels = audioFile->getChannels();
+ float ch1Value, ch2Value;
+
+ if (channels == 0) {
+#ifdef DEBUG_AUDIOFILEMANAGER
+ std::cerr << "AudioFileManager::drawPreview - "
+ << "no channels in audio file!" << std::endl;
+#endif
+
+ return ;
+ }
+
+
+ // Render pixmap
+ //
+ for (int i = 0; i < pixmap->width(); i++) {
+ // Always get two values for our pixmap no matter how many
+ // channels in AudioFile as that's all we can display.
+ //
+ if (channels == 1) {
+ ch1Value = values[i];
+ ch2Value = values[i];
+ } else {
+ ch1Value = values[i * channels];
+ ch2Value = values[i * channels + 1];
+ }
+
+ painter.drawLine(i, static_cast<int>(yStep - ch1Value * yStep),
+ i, static_cast<int>(yStep + ch2Value * yStep));
+ }
+}
+
+void
+AudioFileManager::drawHighlightedPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ const RealTime &highlightStart,
+ const RealTime &highlightEnd,
+ QPixmap *pixmap)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+ if (!audioFile)
+ return ;
+
+ if (!m_peakManager.hasValidPeaks(audioFile)) {
+ std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
+ throw PeakFileManager::BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ std::vector<float> values = m_peakManager.getPreview
+ (audioFile,
+ startTime,
+ endTime,
+ pixmap->width(),
+ false);
+
+ int startWidth = (int)(double(pixmap->width()) * (highlightStart /
+ (endTime - startTime)));
+ int endWidth = (int)(double(pixmap->width()) * (highlightEnd /
+ (endTime - startTime)));
+
+ QPainter painter(pixmap);
+ pixmap->fill(kapp->palette().color(QPalette::Active,
+ QColorGroup::Base));
+
+ float yStep = pixmap->height() / 2;
+ int channels = audioFile->getChannels();
+ float ch1Value, ch2Value;
+
+ // Render pixmap
+ //
+ for (int i = 0; i < pixmap->width(); ++i) {
+ if ((i * channels + (channels - 1)) >= int(values.size()))
+ break;
+
+ // Always get two values for our pixmap no matter how many
+ // channels in AudioFile as that's all we can display.
+ //
+ if (channels == 1) {
+ ch1Value = values[i];
+ ch2Value = values[i];
+ } else {
+ ch1Value = values[i * channels];
+ ch2Value = values[i * channels + 1];
+ }
+
+ if (i < startWidth || i > endWidth)
+ painter.setPen(kapp->palette().color(QPalette::Active,
+ QColorGroup::Mid));
+ else
+ painter.setPen(kapp->palette().color(QPalette::Active,
+ QColorGroup::Dark));
+
+ painter.drawLine(i, static_cast<int>(yStep - ch1Value * yStep),
+ i, static_cast<int>(yStep + ch2Value * yStep));
+ }
+}
+
+
+void
+AudioFileManager::print()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+#ifdef DEBUG_AUDIOFILEMANAGER
+
+ std::cout << "AudioFileManager - " << m_audioFiles.size() << " entr";
+
+ if (m_audioFiles.size() == 1)
+ std::cout << "y";
+ else
+ std::cout << "ies";
+
+ std::cout << std::endl << std::endl;
+
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
+ std::cout << (*it)->getId() << " : " << (*it)->getName()
+ << " : \"" << (*it)->getFilename() << "\"" << std::endl;
+ }
+#endif
+}
+
+std::vector<SplitPointPair>
+AudioFileManager::getSplitPoints(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+
+ if (audioFile == 0)
+ return std::vector<SplitPointPair>();
+
+ return m_peakManager.getSplitPoints(audioFile,
+ startTime,
+ endTime,
+ threshold,
+ minTime);
+}
+
+std::set<int>
+AudioFileManager::getActualSampleRates() const
+{
+ std::set<int> rates;
+
+ for (std::vector<AudioFile *>::const_iterator i = m_audioFiles.begin();
+ i != m_audioFiles.end(); ++i) {
+
+ unsigned int sr = (*i)->getSampleRate();
+ if (sr != 0) rates.insert(int(sr));
+ }
+
+ return rates;
+}
+
+}
+
+
+#include "AudioFileManager.moc"
diff --git a/src/sound/AudioFileManager.h b/src/sound/AudioFileManager.h
new file mode 100644
index 0000000..9721669
--- /dev/null
+++ b/src/sound/AudioFileManager.h
@@ -0,0 +1,327 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIOFILEMANAGER_H_
+#define _AUDIOFILEMANAGER_H_
+
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+
+#include <qpixmap.h>
+#include <qobject.h>
+
+#include "AudioFile.h"
+#include "XmlExportable.h"
+#include "PeakFileManager.h"
+#include "PeakFile.h"
+#include "Exception.h"
+
+#include <kurl.h>
+
+// AudioFileManager loads and maps audio files to their
+// internal references (ids). A point of contact for
+// AudioFile information - loading a Composition should
+// use this class to pick up the AudioFile references,
+// editing the AudioFiles in a Composition will be
+// made through this manager.
+
+// This is in the sound library because it's so closely
+// connected to other sound classes like the AudioFile
+// ones. However, the audio file manager itself within
+// Rosegarden is stored in the GUI process. This class
+// is not (and should not be) used elsewhere within the
+// sound or sequencer libraries.
+
+class KProcess;
+
+namespace Rosegarden
+{
+
+typedef std::vector<AudioFile*>::const_iterator AudioFileManagerIterator;
+
+class AudioFileManager : public QObject, public XmlExportable
+{
+ Q_OBJECT
+public:
+ AudioFileManager();
+ virtual ~AudioFileManager();
+
+ class BadAudioPathException : public Exception
+ {
+ public:
+ BadAudioPathException(std::string path) :
+ Exception("Bad audio file path " + path), m_path(path) { }
+ BadAudioPathException(std::string path, std::string file, int line) :
+ Exception("Bad audio file path " + path, file, line), m_path(path) { }
+ BadAudioPathException(const SoundFile::BadSoundFileException &e) :
+ Exception("Bad audio file path (malformed file?) " + e.getPath()), m_path(e.getPath()) { }
+
+ ~BadAudioPathException() throw() { }
+
+ std::string getPath() const { return m_path; }
+
+ private:
+ std::string m_path;
+ };
+
+private:
+ AudioFileManager(const AudioFileManager &aFM);
+ AudioFileManager& operator=(const AudioFileManager &);
+
+public:
+
+ // Create an audio file from an absolute path - we use this interface
+ // to add an actual file.
+ //
+ AudioFileId addFile(const std::string &filePath);
+ // throw BadAudioPathException
+
+ // Return true if a file would require importFile to import it, rather
+ // than a simple addFile. You can use importFile even when a file
+ // doesn't need conversion, but this tells you whether it's necessary
+ //
+ bool fileNeedsConversion(const std::string &filePath,
+ int targetSampleRate = 0);
+
+ // Create an audio file by importing (i.e. converting and/or
+ // resampling) an existing file using the external conversion
+ // utility
+ //
+ AudioFileId importFile(const std::string &filePath,
+ int targetSampleRate = 0);
+ // throw BadAudioPathException, BadSoundFileException
+
+ // Create an audio file by importing from a URL
+ //
+ AudioFileId importURL(const KURL &filePath,
+ int targetSampleRate = 0);
+ // throw BadAudioPathException, BadSoundFileException
+
+ // Insert an audio file into the AudioFileManager and get the
+ // first allocated id for it. Used from the RG file as we already
+ // have both name and filename/path.
+ //
+ AudioFileId insertFile(const std::string &name,
+ const std::string &fileName);
+ // throw BadAudioPathException
+
+ // And insert an AudioFile and specify an id
+ //
+ bool insertFile(const std::string &name, const std::string &fileName,
+ AudioFileId id);
+ // throw BadAudioPathException
+
+ // Remove a file from the AudioManager by id
+ //
+ bool removeFile(AudioFileId id);
+
+ // Does a specific file id exist?
+ //
+ bool fileExists(AudioFileId id);
+
+ // Does a specific file path exist? Return ID or -1.
+ //
+ int fileExists(const std::string &path);
+
+ // get audio file by id
+ //
+ AudioFile* getAudioFile(AudioFileId id);
+
+ // Get the list of files
+ //
+ std::vector<AudioFile*>::const_iterator begin() const
+ { return m_audioFiles.begin(); }
+
+ std::vector<AudioFile*>::const_iterator end() const
+ { return m_audioFiles.end(); }
+
+ // Clear down all audio file references
+ //
+ void clear();
+
+ // Get and set the record path
+ //
+ std::string getAudioPath() const { return m_audioPath; }
+ void setAudioPath(const std::string &path);
+
+ // Throw if the current audio path does not exist or is not writable
+ //
+ void testAudioPath() throw(BadAudioPathException);
+
+ // Get a new audio filename at the audio record path
+ //
+ AudioFile *createRecordingAudioFile();
+ // throw BadAudioPathException
+
+ // Get a set of new audio filenames at the audio record path
+ //
+ std::vector<std::string> createRecordingAudioFiles(unsigned int number);
+ // throw BadAudioPathException
+
+ // Return whether a file was created by recording within this "session"
+ //
+ bool wasAudioFileRecentlyRecorded(AudioFileId id);
+
+ // Return whether a file was created by derivation within this "session"
+ //
+ bool wasAudioFileRecentlyDerived(AudioFileId id);
+
+ // Indicate that a new "session" has started from the point of
+ // view of recorded and derived audio files (e.g. that the
+ // document has been saved)
+ //
+ void resetRecentlyCreatedFiles();
+
+ // Create an empty file "derived from" the source (used by e.g. stretcher)
+ //
+ AudioFile *createDerivedAudioFile(AudioFileId source,
+ const char *prefix);
+
+ // return the last file in the vector - the last created
+ //
+ AudioFile* getLastAudioFile();
+
+ // Export to XML
+ //
+ virtual std::string toXmlString();
+
+ // Convenience function generate all previews on the audio file.
+ //
+ void generatePreviews();
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Generate for a single audio file
+ //
+ bool generatePreview(AudioFileId id);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Get a preview for an AudioFile adjusted to Segment start and
+ // end parameters (assuming they fall within boundaries).
+ //
+ // We can get back a set of values (floats) or a Pixmap if we
+ // supply the details.
+ //
+ std::vector<float> getPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool withMinima);
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Draw a fixed size (fixed by QPixmap) preview of an audio file
+ //
+ void drawPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ QPixmap *pixmap);
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Usually used to show how an audio Segment makes up part of
+ // an audio file.
+ //
+ void drawHighlightedPreview(AudioFileId it,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ const RealTime &highlightStart,
+ const RealTime &highlightEnd,
+ QPixmap *pixmap);
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Get a short file name from a long one (with '/'s)
+ //
+ std::string getShortFilename(const std::string &fileName);
+
+ // Get a directory from a full file path
+ //
+ std::string getDirectory(const std::string &path);
+
+ // Attempt to subsititute a tilde '~' for a home directory
+ // to make paths a little more generic when saving. Also
+ // provide the inverse function as convenience here.
+ //
+ std::string substituteHomeForTilde(const std::string &path);
+ std::string substituteTildeForHome(const std::string &path);
+
+ // Show entries for debug purposes
+ //
+ void print();
+
+ // Get a split point vector from a peak file
+ //
+ std::vector<SplitPointPair>
+ getSplitPoints(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime = RealTime(0, 100000000));
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Get the peak file manager
+ //
+ const PeakFileManager& getPeakFileManager() const { return m_peakManager; }
+
+ // Get the peak file manager
+ //
+ PeakFileManager& getPeakFileManager() { return m_peakManager; }
+
+ int getExpectedSampleRate() const { return m_expectedSampleRate; }
+ void setExpectedSampleRate(int rate) { m_expectedSampleRate = rate; }
+
+ std::set<int> getActualSampleRates() const;
+
+signals:
+ void setProgress(int);
+ void setOperationName(QString);
+
+public slots:
+ // Cancel a running preview
+ //
+ void slotStopPreview();
+
+ void slotStopImport();
+
+private:
+ std::string getFileInPath(const std::string &file);
+
+ AudioFileId getFirstUnusedID();
+
+ std::vector<AudioFile*> m_audioFiles;
+ std::string m_audioPath;
+
+ PeakFileManager m_peakManager;
+
+ // All audio files are stored in m_audioFiles. These additional
+ // sets of pointers just refer to those that have been created by
+ // recording or derivations within the current session, and thus
+ // that the user may wish to remove at the end of the session if
+ // the document is not saved.
+ std::set<AudioFile *> m_recordedAudioFiles;
+ std::set<AudioFile *> m_derivedAudioFiles;
+
+ KProcess *m_importProcess;
+
+ int m_expectedSampleRate;
+};
+
+}
+
+#endif // _AUDIOFILEMANAGER_H_
diff --git a/src/sound/AudioFileTimeStretcher.cpp b/src/sound/AudioFileTimeStretcher.cpp
new file mode 100644
index 0000000..d5b2321
--- /dev/null
+++ b/src/sound/AudioFileTimeStretcher.cpp
@@ -0,0 +1,268 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ 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 "AudioFileTimeStretcher.h"
+#include "AudioTimeStretcher.h"
+#include "AudioFileManager.h"
+#include "WAVAudioFile.h"
+#include "base/RealTime.h"
+
+#include <kapplication.h>
+
+#include <iostream>
+#include <fstream>
+
+namespace Rosegarden {
+
+
+AudioFileTimeStretcher::AudioFileTimeStretcher(AudioFileManager *manager) :
+ m_manager(manager),
+ m_timestretchCancelled(false)
+{
+}
+
+AudioFileTimeStretcher::~AudioFileTimeStretcher()
+{
+}
+
+AudioFileId
+AudioFileTimeStretcher::getStretchedAudioFile(AudioFileId source,
+ float ratio)
+{
+ AudioFile *sourceFile = m_manager->getAudioFile(source);
+ if (!sourceFile) {
+ throw SoundFile::BadSoundFileException
+ ("<unknown source>",
+ "Source file not found in AudioFileTimeStretcher::getStretchedAudioFile");
+ }
+
+ std::cerr << "AudioFileTimeStretcher: got source file id " << source
+ << ", name " << sourceFile->getFilename() << std::endl;
+
+ AudioFile *file = m_manager->createDerivedAudioFile(source, "stretch");
+ if (!file) {
+ throw AudioFileManager::BadAudioPathException(m_manager->getAudioPath());
+ }
+
+ std::cerr << "AudioFileTimeStretcher: got derived file id " << file->getId()
+ << ", name " << file->getFilename() << std::endl;
+
+ std::ifstream streamIn(sourceFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+ if (!streamIn) {
+ throw SoundFile::BadSoundFileException
+ (file->getFilename().c_str(),
+ "Failed to open source stream for time stretcher");
+ }
+
+ //!!!
+ //...
+ // Need to make SoundDriver::getAudioRecFileFormat available?
+ // -- the sound file classes should just have a float interface
+ // (like libsndfile, or hey!, we could use libsndfile...)
+
+ WAVAudioFile writeFile
+ (file->getFilename(),
+ sourceFile->getChannels(),
+ sourceFile->getSampleRate(),
+ sourceFile->getSampleRate() * 4 * sourceFile->getChannels(),
+ 4 * sourceFile->getChannels(),
+ 32);
+
+ if (!writeFile.write()) {
+ throw AudioFileManager::BadAudioPathException
+ (file->getFilename());
+ }
+
+ int obs = 1024;
+ int ibs = obs / ratio;
+ int ch = sourceFile->getChannels();
+ int sr = sourceFile->getSampleRate();
+
+ AudioTimeStretcher stretcher(sr, ch, ratio, true, obs);
+
+ // We'll first prime the timestretcher with half its window size
+ // of silence, an amount which we then discard at the start of the
+ // output (as well as its own processing latency). Really the
+ // timestretcher should handle this itself and report it in its
+ // own latency calculation
+
+ size_t padding = stretcher.getWindowSize()/2;
+
+ char *ebf = (char *)alloca
+ (ch * ibs * sourceFile->getBytesPerFrame());
+
+ std::vector<float *> dbfs;
+ for (int c = 0; c < ch; ++c) {
+ dbfs.push_back((float *)alloca((ibs > padding ? ibs : padding)
+ * sizeof(float)));
+ }
+
+ float **ibfs = (float **)alloca(ch * sizeof(float *));
+ float **obfs = (float **)alloca(ch * sizeof(float *));
+
+ for (int c = 0; c < ch; ++c) {
+ ibfs[c] = dbfs[c];
+ }
+
+ for (int c = 0; c < ch; ++c) {
+ obfs[c] = (float *)alloca(obs * sizeof(float));
+ }
+
+ char *oebf = (char *)alloca(ch * obs * sizeof(float));
+
+ int totalIn = 0, totalOut = 0;
+
+ for (int c = 0; c < ch; ++c) {
+ for (size_t i = 0; i < padding; ++i) {
+ ibfs[c][i] = 0.f;
+ }
+ }
+ stretcher.putInput(ibfs, padding);
+
+ RealTime totalTime = sourceFile->getLength();
+ long fileTotalIn = RealTime::realTime2Frame
+ (totalTime, sourceFile->getSampleRate());
+ int progressCount = 0;
+
+ long expectedOut = ceil(fileTotalIn * ratio);
+
+ m_timestretchCancelled = false;
+ bool inputExhausted = false;
+
+ sourceFile->scanTo(&streamIn, RealTime::zeroTime);
+
+ while (1) {
+
+ if (m_timestretchCancelled) {
+ std::cerr << "AudioFileTimeStretcher::getStretchedAudioFile: cancelled" << std::endl;
+ throw CancelledException();
+ }
+
+ unsigned int thisRead = 0;
+
+ if (!inputExhausted) {
+ thisRead = sourceFile->getSampleFrames(&streamIn, ebf, ibs);
+ if (thisRead < ibs) inputExhausted = true;
+ }
+
+ if (thisRead == 0) {
+ if (totalOut >= expectedOut) break;
+ else {
+ // run out of input data, continue feeding zeroes until
+ // we have enough output data
+ for (int c = 0; c < ch; ++c) {
+ for (int i = 0; i < ibs; ++i) {
+ ibfs[c][i] = 0.f;
+ }
+ }
+ thisRead = ibs;
+ }
+ }
+
+ if (!sourceFile->decode((unsigned char *)ebf,
+ thisRead * sourceFile->getBytesPerFrame(),
+ sr, ch,
+ thisRead, dbfs, false)) {
+ std::cerr << "ERROR: Stupid audio file class failed to decode its own output" << std::endl;
+ break;
+ }
+
+ stretcher.putInput(ibfs, thisRead);
+ totalIn += thisRead;
+
+ unsigned int available = stretcher.getAvailableOutputSamples();
+
+ while (available > 0) {
+
+ unsigned int count = available;
+ if (count > obs) count = obs;
+
+ if (padding > 0) {
+ if (count <= padding) {
+ stretcher.getOutput(obfs, count);
+ padding -= count;
+ available -= count;
+ continue;
+ } else {
+ stretcher.getOutput(obfs, padding);
+ count -= padding;
+ available -= padding;
+ padding = 0;
+ }
+ }
+
+ stretcher.getOutput(obfs, count);
+
+ char *encodePointer = oebf;
+ for (int i = 0; i < count; ++i) {
+ for (int c = 0; c < ch; ++c) {
+ float sample = obfs[c][i];
+ *(float *)encodePointer = sample;
+ encodePointer += sizeof(float);
+ }
+ }
+
+ if (totalOut < expectedOut &&
+ totalOut + count > expectedOut) {
+ count = expectedOut - totalOut;
+ }
+
+ writeFile.appendSamples(oebf, count);
+ totalOut += count;
+ available -= count;
+
+ if (totalOut >= expectedOut) break;
+ }
+
+ if (++progressCount == 100) {
+ int progress = int
+ ((100.f * float(totalIn)) / float(fileTotalIn));
+ emit setProgress(progress);
+ kapp->processEvents();
+ progressCount = 0;
+ }
+ }
+
+ emit setProgress(100);
+ kapp->processEvents();
+ writeFile.close();
+
+ std::cerr << "AudioFileTimeStretcher::getStretchedAudioFile: success, id is "
+ << file->getId() << std::endl;
+
+ return file->getId();
+}
+
+void
+AudioFileTimeStretcher::slotStopTimestretch()
+{
+ m_timestretchCancelled = true;
+}
+
+
+}
+
+#include "AudioFileTimeStretcher.moc"
+
diff --git a/src/sound/AudioFileTimeStretcher.h b/src/sound/AudioFileTimeStretcher.h
new file mode 100644
index 0000000..c02e286
--- /dev/null
+++ b/src/sound/AudioFileTimeStretcher.h
@@ -0,0 +1,76 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ 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_FILE_TIME_STRETCHER_H_
+#define _AUDIO_FILE_TIME_STRETCHER_H_
+
+#include <qobject.h>
+#include "AudioFile.h"
+#include "base/Exception.h"
+
+namespace Rosegarden {
+
+class AudioFileManager;
+
+class AudioFileTimeStretcher : public QObject
+{
+ Q_OBJECT
+
+public:
+ AudioFileTimeStretcher(AudioFileManager *mgr);
+ virtual ~AudioFileTimeStretcher();
+
+ /**
+ * Stretch an audio file and return the ID of the stretched
+ * version. May throw SoundFile::BadSoundFileException,
+ * AudioFileManager::BadAudioPathException, CancelledException
+ */
+ AudioFileId getStretchedAudioFile(AudioFileId source,
+ float ratio);
+
+ class CancelledException : public Exception
+ {
+ public:
+ CancelledException() : Exception("Cancelled") { }
+ ~CancelledException() throw() { }
+ };
+
+signals:
+ void setProgress(int);
+
+public slots:
+ /**
+ * Cancel an ongoing getStretchedAudioFile
+ */
+ void slotStopTimestretch();
+
+protected:
+ AudioFileManager *m_manager;
+
+ bool m_timestretchCancelled;
+};
+
+}
+
+#endif
diff --git a/src/sound/AudioPlayQueue.cpp b/src/sound/AudioPlayQueue.cpp
new file mode 100644
index 0000000..2bd07c3
--- /dev/null
+++ b/src/sound/AudioPlayQueue.cpp
@@ -0,0 +1,501 @@
+
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AudioPlayQueue.h"
+#include "misc/Debug.h"
+#include "PlayableAudioFile.h"
+#include "Profiler.h"
+
+//#define DEBUG_AUDIO_PLAY_QUEUE 1
+//#define FINE_DEBUG_AUDIO_PLAY_QUEUE 1
+
+namespace Rosegarden
+{
+
+
+static inline unsigned int instrumentId2Index(InstrumentId id)
+{
+ if (id < AudioInstrumentBase)
+ return 0;
+ else
+ return (id - AudioInstrumentBase);
+}
+
+bool
+AudioPlayQueue::FileTimeCmp::operator()(const PlayableAudioFile &f1,
+ const PlayableAudioFile &f2) const
+{
+ return operator()(&f1, &f2);
+}
+
+bool
+AudioPlayQueue::FileTimeCmp::operator()(const PlayableAudioFile *f1,
+ const PlayableAudioFile *f2) const
+{
+ RealTime t1 = f1->getStartTime(), t2 = f2->getStartTime();
+ if (t1 < t2)
+ return true;
+ else if (t2 < t1)
+ return false;
+ else
+ return f1 < f2;
+}
+
+
+AudioPlayQueue::AudioPlayQueue() :
+ m_maxBuffers(0)
+{
+ // nothing to do
+}
+
+AudioPlayQueue::~AudioPlayQueue()
+{
+ std::cerr << "AudioPlayQueue::~AudioPlayQueue()" << std::endl;
+ clear();
+}
+
+void
+AudioPlayQueue::addScheduled(PlayableAudioFile *file)
+{
+ if (m_files.find(file) != m_files.end()) {
+ std::cerr << "WARNING: AudioPlayQueue::addScheduled("
+ << file << "): already in queue" << std::endl;
+ return ;
+ }
+
+ m_files.insert(file);
+
+ RealTime startTime = file->getStartTime();
+ RealTime endTime = file->getStartTime() + file->getDuration();
+
+ InstrumentId instrument = file->getInstrument();
+ unsigned int index = instrumentId2Index(instrument);
+
+ while (m_instrumentIndex.size() <= index) {
+ m_instrumentIndex.push_back(ReverseFileMap());
+ }
+
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::addScheduled(" << file << "): start " << file->getStartTime() << ", end " << file->getEndTime() << ", slots: " << std::endl;
+#endif
+
+ for (int i = startTime.sec; i <= endTime.sec; ++i) {
+ m_index[i].push_back(file);
+ m_instrumentIndex[index][i].push_back(file);
+ if (!file->isSmallFile()) {
+ m_counts[i] += file->getTargetChannels();
+ if (m_counts[i] > m_maxBuffers) {
+ m_maxBuffers = m_counts[i];
+ }
+ }
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << i << " ";
+#endif
+
+ }
+
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << std::endl << "(max buffers now "
+ << m_maxBuffers << ")" << std::endl;
+#endif
+}
+
+void
+AudioPlayQueue::addUnscheduled(PlayableAudioFile *file)
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::addUnscheduled(" << file << "): start " << file->getStartTime() << ", end " << file->getEndTime() << ", instrument " << file->getInstrument() << std::endl;
+#endif
+
+ m_unscheduled.push_back(file);
+
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+
+ std::cerr << "AudioPlayQueue[" << this << "]::addUnscheduled: now " << m_unscheduled.size() << " unscheduled files" << std::endl;
+#endif
+
+}
+
+void
+AudioPlayQueue::erase(PlayableAudioFile *file)
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue::erase(" << file << "): start " << file->getStartTime() << ", end " << file->getEndTime() << std::endl;
+#endif
+
+ FileSet::iterator fi = m_files.find(file);
+ if (fi == m_files.end()) {
+ for (FileList::iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+ if (*fli == file) {
+ m_unscheduled.erase(fli);
+ delete file;
+ return ;
+ }
+ }
+ return ;
+ }
+ m_files.erase(fi);
+
+ InstrumentId instrument = file->getInstrument();
+ unsigned int index = instrumentId2Index(instrument);
+
+ for (ReverseFileMap::iterator mi = m_instrumentIndex[index].begin();
+ mi != m_instrumentIndex[index].end(); ++mi) {
+
+ for (FileVector::iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ if (*fi == file) {
+ mi->second.erase(fi);
+ if (m_counts[mi->first] > 0)
+ --m_counts[mi->first];
+ break;
+ }
+ }
+ }
+
+ for (ReverseFileMap::iterator mi = m_index.begin();
+ mi != m_index.end(); ++mi) {
+
+ for (FileVector::iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ if (*fi == file) {
+ mi->second.erase(fi);
+ if (m_counts[mi->first] > 0)
+ --m_counts[mi->first];
+ break;
+ }
+ }
+ }
+
+ delete file;
+}
+
+void
+AudioPlayQueue::clear()
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue::clear()" << std::endl;
+#endif
+
+ while (m_files.begin() != m_files.end()) {
+ delete *m_files.begin();
+ m_files.erase(m_files.begin());
+ }
+
+ while (m_unscheduled.begin() != m_unscheduled.end()) {
+ delete *m_unscheduled.begin();
+ m_unscheduled.erase(m_unscheduled.begin());
+ }
+
+ m_instrumentIndex.clear();
+ m_index.clear();
+ m_counts.clear();
+ m_maxBuffers = 0;
+}
+
+bool
+AudioPlayQueue::empty() const
+{
+ return m_unscheduled.empty() && m_files.empty();
+}
+
+size_t
+AudioPlayQueue::size() const
+{
+ return m_unscheduled.size() + m_files.size();
+}
+
+void
+AudioPlayQueue::getPlayingFiles(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ FileSet &playing) const
+{
+ // Profiler profiler("AudioPlayQueue::getPlayingFiles");
+
+ // This one needs to be quick.
+
+ playing.clear();
+
+ RealTime sliceEnd = sliceStart + sliceDuration;
+
+ for (int i = sliceStart.sec; i <= sliceEnd.sec; ++i) {
+
+ ReverseFileMap::const_iterator mi(m_index.find(i));
+ if (mi == m_index.end())
+ continue;
+
+ for (FileVector::const_iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ PlayableAudioFile *f = *fi;
+
+ if (f->getStartTime() > sliceEnd ||
+ f->getStartTime() + f->getDuration() <= sliceStart)
+ continue;
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+
+ std::cerr << "... found " << f << " in slot " << i << std::endl;
+#endif
+
+ playing.insert(f);
+ }
+ }
+
+ for (FileList::const_iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+ PlayableAudioFile *file = *fli;
+ if (file->getStartTime() <= sliceEnd &&
+ file->getStartTime() + file->getDuration() > sliceStart) {
+ playing.insert(file);
+ }
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ if (playing.size() > 0) {
+ std::cerr << "AudioPlayQueue::getPlayingFiles(" << sliceStart << ","
+ << sliceDuration << "): total "
+ << playing.size() << " files" << std::endl;
+ }
+#endif
+}
+
+void
+AudioPlayQueue::getPlayingFilesForInstrument(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ InstrumentId instrumentId,
+ PlayableAudioFile **playing,
+ size_t &size) const
+{
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ bool printed = false;
+ Profiler profiler("AudioPlayQueue::getPlayingFilesForInstrument", true);
+#endif
+
+ // This one needs to be quick.
+
+ size_t written = 0;
+
+ RealTime sliceEnd = sliceStart + sliceDuration;
+
+ unsigned int index = instrumentId2Index(instrumentId);
+ if (index >= m_instrumentIndex.size()) {
+ goto unscheduled; // nothing scheduled here
+ }
+
+ for (int i = sliceStart.sec; i <= sliceEnd.sec; ++i) {
+
+ ReverseFileMap::const_iterator mi
+ (m_instrumentIndex[index].find(i));
+
+ if (mi == m_instrumentIndex[index].end())
+ continue;
+
+ for (FileVector::const_iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ PlayableAudioFile *f = *fi;
+
+ if (f->getInstrument() != instrumentId)
+ continue;
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+
+ if (!printed) {
+ std::cerr << "AudioPlayQueue::getPlayingFilesForInstrument(" << sliceStart
+ << ", " << sliceDuration << ", " << instrumentId << ")"
+ << std::endl;
+ printed = true;
+ }
+#endif
+
+ if (f->getStartTime() > sliceEnd ||
+ f->getEndTime() <= sliceStart) {
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "... rejected " << f << " in slot " << i << std::endl;
+ if (f->getStartTime() > sliceEnd) {
+ std::cerr << "(" << f->getStartTime() << " > " << sliceEnd
+ << ")" << std::endl;
+ } else {
+ std::cerr << "(" << f->getEndTime() << " <= " << sliceStart
+ << ")" << std::endl;
+ }
+#endif
+
+ continue;
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "... found " << f << " in slot " << i << " ("
+ << f->getStartTime() << " -> " << f->getEndTime()
+ << ")" << std::endl;
+#endif
+
+ size_t j = 0;
+ for (j = 0; j < written; ++j) {
+ if (playing[j] == f)
+ break;
+ }
+ if (j < written)
+ break; // already have it
+
+ if (written >= size) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "No room to write it!" << std::endl;
+#endif
+
+ break;
+ }
+
+ playing[written++] = f;
+ }
+ }
+
+unscheduled:
+
+ for (FileList::const_iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+
+ PlayableAudioFile *f = *fli;
+
+ if (f->getInstrument() != instrumentId) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "rejecting unscheduled " << f << " as wrong instrument ("
+ << f->getInstrument() << " != " << instrumentId << ")" << std::endl;
+#endif
+
+ continue;
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ if (!printed) {
+ std::cerr << "AudioPlayQueue::getPlayingFilesForInstrument(" << sliceStart
+ << ", " << sliceDuration << ", " << instrumentId << ")"
+ << std::endl;
+ printed = true;
+ }
+#endif
+
+ if (f->getStartTime() <= sliceEnd &&
+ f->getStartTime() + f->getDuration() > sliceStart) {
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "... found " << f << " in unscheduled list ("
+ << f->getStartTime() << " -> " << f->getEndTime()
+ << ")" << std::endl;
+#endif
+
+ if (written >= size)
+ break;
+ playing[written++] = f;
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+
+ } else {
+
+ std::cerr << "... rejected " << f << " in unscheduled list" << std::endl;
+ if (f->getStartTime() > sliceEnd) {
+ std::cerr << "(" << f->getStartTime() << " > " << sliceEnd
+ << ")" << std::endl;
+ } else {
+ std::cerr << "(" << f->getEndTime() << " <= " << sliceStart
+ << ")" << std::endl;
+ }
+#endif
+
+ }
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ if (written > 0) {
+ std::cerr << "AudioPlayQueue::getPlayingFilesForInstrument: total "
+ << written << " files" << std::endl;
+ }
+#endif
+
+ size = written;
+}
+
+bool
+AudioPlayQueue::haveFilesForInstrument(InstrumentId instrumentId) const
+{
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue::haveFilesForInstrument(" << instrumentId << ")...";
+#endif
+
+ unsigned int index = instrumentId2Index(instrumentId);
+
+ if (index < m_instrumentIndex.size() &&
+ !m_instrumentIndex[index].empty()) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << " yes (scheduled)" << std::endl;
+#endif
+
+ return true;
+ }
+
+ for (FileList::const_iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+ PlayableAudioFile *file = *fli;
+ if (file->getInstrument() == instrumentId) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << " yes (unscheduled)" << std::endl;
+#endif
+
+ return true;
+ }
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << " no" << std::endl;
+#endif
+
+ return false;
+}
+
+const AudioPlayQueue::FileSet &
+AudioPlayQueue::getAllScheduledFiles() const
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::getAllScheduledFiles: have " << m_files.size() << " files" << std::endl;
+#endif
+
+ return m_files;
+}
+
+const AudioPlayQueue::FileList &
+AudioPlayQueue::getAllUnscheduledFiles() const
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::getAllUnscheduledFiles: have " << m_unscheduled.size() << " files" << std::endl;
+#endif
+
+ return m_unscheduled;
+}
+
+
+}
+
diff --git a/src/sound/AudioPlayQueue.h b/src/sound/AudioPlayQueue.h
new file mode 100644
index 0000000..2a7067c
--- /dev/null
+++ b/src/sound/AudioPlayQueue.h
@@ -0,0 +1,168 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIO_PLAY_QUEUE_H_
+#define _AUDIO_PLAY_QUEUE_H_
+
+#include "RealTime.h"
+#include "Instrument.h"
+
+#include <set>
+#include <vector>
+#include <map>
+#include <list>
+
+namespace Rosegarden
+{
+
+class PlayableAudioFile;
+
+/**
+ * An ordered list of PlayableAudioFiles that does not aim to be quick
+ * to add to or remove files from, but that aims to quickly answer the
+ * question of which files are playing within a given time slice.
+ *
+ * Note that there is no locking between audio file add/remove and
+ * lookup. Add/remove should only be carried out when it is known
+ * that no threads will be performing lookup.
+ */
+
+class AudioPlayQueue
+{
+public:
+ AudioPlayQueue();
+ virtual ~AudioPlayQueue();
+
+ struct FileTimeCmp {
+ bool operator()(const PlayableAudioFile &, const PlayableAudioFile &) const;
+ bool operator()(const PlayableAudioFile *, const PlayableAudioFile *) const;
+ };
+ typedef std::set<PlayableAudioFile *, FileTimeCmp> FileSet;
+ typedef std::list<PlayableAudioFile *> FileList;
+
+ /**
+ * Add a file to the queue. AudioPlayQueue takes ownership of the
+ * file and will delete it when removed.
+ */
+ void addScheduled(PlayableAudioFile *file);
+
+ /**
+ * Add a file to the unscheduled list. AudioPlayQueue takes
+ * ownership of the file and will delete it when removed.
+ * Unscheduled files will be returned along with schuled ones for
+ * normal lookups, but everything will be less efficient when
+ * there are unscheduled files on the queue. This is intended
+ * for asynchronous (preview) playback.
+ */
+ void addUnscheduled(PlayableAudioFile *file);
+
+ /**
+ * Remove a scheduled or unscheduled file from the queue and
+ * delete it.
+ */
+ void erase(PlayableAudioFile *file);
+
+ /**
+ * Remove all files and delete them.
+ */
+ void clear();
+
+ /**
+ * Return true if the queue is empty.
+ */
+ bool empty() const;
+
+ /**
+ * Return the total number of files in the queue. (May be slow.)
+ */
+ size_t size() const;
+
+ /**
+ * Look up the files playing during a given slice and return them
+ * in the passed FileSet. The pointers returned are still owned
+ * by me and the caller should not delete them.
+ */
+ void getPlayingFiles(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ FileSet &) const;
+
+ /**
+ * Look up the files playing during a given slice on a given
+ * instrument and return them in the passed array. The size arg
+ * gives the available size of the array and is used to return the
+ * number of file pointers written. The pointers returned are
+ * still owned by me and the caller should not delete them.
+ */
+ void getPlayingFilesForInstrument(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ InstrumentId instrumentId,
+ PlayableAudioFile **files,
+ size_t &size) const;
+
+ /**
+ * Return true if at least one scheduled or unscheduled file is
+ * associated with the given instrument somewhere in the queue.
+ */
+ bool haveFilesForInstrument(InstrumentId instrumentId) const;
+
+ /**
+ * Return a (shared reference to an) ordered set of all files on
+ * the scheduled queue.
+ */
+ const FileSet &getAllScheduledFiles() const;
+
+ /**
+ * Return a (shared reference to an) ordered set of all files on
+ * the unscheduled queue.
+ */
+ const FileList &getAllUnscheduledFiles() const;
+
+ /**
+ * Get an approximate (but always pessimistic) estimate of the
+ * number of ring buffers required for the current queue -- that
+ * is, the maximum possible number of audio channels playing at
+ * once from non-small-file-cached-files.
+ */
+ size_t getMaxBuffersRequired() const { return m_maxBuffers; }
+
+private:
+ FileSet m_files;
+
+ typedef std::vector<PlayableAudioFile *> FileVector;
+ typedef std::map<int, FileVector> ReverseFileMap;
+ ReverseFileMap m_index;
+
+ typedef std::vector<ReverseFileMap> InstrumentReverseFileMap;
+ InstrumentReverseFileMap m_instrumentIndex;
+
+ FileList m_unscheduled;
+
+ typedef std::map<int, size_t> FileCountMap;
+ FileCountMap m_counts;
+
+ size_t m_maxBuffers;
+};
+
+
+}
+
+#endif
+
diff --git a/src/sound/AudioProcess.cpp b/src/sound/AudioProcess.cpp
new file mode 100644
index 0000000..9b44e13
--- /dev/null
+++ b/src/sound/AudioProcess.cpp
@@ -0,0 +1,2463 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AudioProcess.h"
+
+#include "RunnablePluginInstance.h"
+#include "PlayableAudioFile.h"
+#include "RecordableAudioFile.h"
+#include "WAVAudioFile.h"
+#include "MappedStudio.h"
+#include "Profiler.h"
+#include "AudioLevel.h"
+#include "AudioPlayQueue.h"
+#include "PluginFactory.h"
+
+#include <sys/time.h>
+#include <pthread.h>
+
+#include <cmath>
+
+//#define DEBUG_THREAD_CREATE_DESTROY 1
+//#define DEBUG_BUSS_MIXER 1
+//#define DEBUG_MIXER 1
+//#define DEBUG_MIXER_LIGHTWEIGHT 1
+//#define DEBUG_LOCKS 1
+//#define DEBUG_READER 1
+//#define DEBUG_WRITER 1
+
+namespace Rosegarden
+{
+
+/* Branch-free optimizer-resistant denormal killer courtesy of Simon
+ Jenkins on LAD: */
+
+static inline float flushToZero(volatile float f)
+{
+ f += 9.8607615E-32f;
+ return f - 9.8607615E-32f;
+}
+
+static inline void denormalKill(float *buffer, int size)
+{
+ for (int i = 0; i < size; ++i) {
+ buffer[i] = flushToZero(buffer[i]);
+ }
+}
+
+AudioThread::AudioThread(std::string name,
+ SoundDriver *driver,
+ unsigned int sampleRate) :
+ m_name(name),
+ m_driver(driver),
+ m_sampleRate(sampleRate),
+ m_thread(0),
+ m_running(false),
+ m_exiting(false)
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << "AudioThread::AudioThread() [" << m_name << "]" << std::endl;
+#endif
+
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_lock, &initialisingMutex, sizeof(pthread_mutex_t));
+
+ pthread_cond_t initialisingCondition = PTHREAD_COND_INITIALIZER;
+ memcpy(&m_condition, &initialisingCondition, sizeof(pthread_cond_t));
+}
+
+AudioThread::~AudioThread()
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << "AudioThread::~AudioThread() [" << m_name << "]" << std::endl;
+#endif
+
+ if (m_thread) {
+ pthread_mutex_destroy(&m_lock);
+ m_thread = 0;
+ }
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << "AudioThread::~AudioThread() exiting" << std::endl;
+#endif
+}
+
+void
+AudioThread::run()
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << m_name << "::run()" << std::endl;
+#endif
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+
+ int priority = getPriority();
+
+ if (priority > 0) {
+
+ if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
+
+ std::cerr << m_name << "::run: WARNING: couldn't set FIFO scheduling "
+ << "on new thread" << std::endl;
+ pthread_attr_init(&attr); // reset to safety
+
+ } else {
+
+ struct sched_param param;
+ memset(&param, 0, sizeof(struct sched_param));
+ param.sched_priority = priority;
+
+ if (pthread_attr_setschedparam(&attr, &param)) {
+ std::cerr << m_name << "::run: WARNING: couldn't set priority "
+ << priority << " on new thread" << std::endl;
+ pthread_attr_init(&attr); // reset to safety
+ }
+ }
+ }
+
+ pthread_attr_setstacksize(&attr, 1048576);
+ int rv = pthread_create(&m_thread, &attr, staticThreadRun, this);
+
+ if (rv != 0 && priority > 0) {
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << m_name << "::run: WARNING: unable to start RT thread;"
+ << "\ntrying again with normal scheduling" << std::endl;
+#endif
+
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, 1048576);
+ rv = pthread_create(&m_thread, &attr, staticThreadRun, this);
+ }
+
+ if (rv != 0) {
+ // This is quite fatal.
+ std::cerr << m_name << "::run: ERROR: failed to start thread!" << std::endl;
+ ::exit(1);
+ }
+
+ m_running = true;
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << m_name << "::run() done" << std::endl;
+#endif
+}
+
+void
+AudioThread::terminate()
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::string name = m_name;
+ std::cerr << name << "::terminate()" << std::endl;
+#endif
+
+ m_running = false;
+
+ if (m_thread) {
+
+ pthread_cancel(m_thread);
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << name << "::terminate(): cancel requested" << std::endl;
+#endif
+
+ int rv = pthread_join(m_thread, 0);
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << name << "::terminate(): thread exited with return value " << rv << std::endl;
+#endif
+
+ }
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << name << "::terminate(): done" << std::endl;
+#endif
+}
+
+void *
+AudioThread::staticThreadRun(void *arg)
+{
+ AudioThread *inst = static_cast<AudioThread *>(arg);
+ if (!inst)
+ return 0;
+
+ pthread_cleanup_push(staticThreadCleanup, arg);
+
+ inst->getLock();
+ inst->m_exiting = false;
+ inst->threadRun();
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << inst->m_name << "::staticThreadRun(): threadRun exited" << std::endl;
+#endif
+
+ inst->releaseLock();
+ pthread_cleanup_pop(0);
+
+ return 0;
+}
+
+void
+AudioThread::staticThreadCleanup(void *arg)
+{
+ AudioThread *inst = static_cast<AudioThread *>(arg);
+ if (!inst || inst->m_exiting)
+ return ;
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::string name = inst->m_name;
+ std::cerr << name << "::staticThreadCleanup()" << std::endl;
+#endif
+
+ inst->m_exiting = true;
+ inst->releaseLock();
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << name << "::staticThreadCleanup() done" << std::endl;
+#endif
+}
+
+int
+AudioThread::getLock()
+{
+ int rv;
+#ifdef DEBUG_LOCKS
+
+ std::cerr << m_name << "::getLock()" << std::endl;
+#endif
+
+ rv = pthread_mutex_lock(&m_lock);
+#ifdef DEBUG_LOCKS
+
+ std::cerr << "OK" << std::endl;
+#endif
+
+ return rv;
+}
+
+int
+AudioThread::tryLock()
+{
+ int rv;
+#ifdef DEBUG_LOCKS
+
+ std::cerr << m_name << "::tryLock()" << std::endl;
+#endif
+
+ rv = pthread_mutex_trylock(&m_lock);
+#ifdef DEBUG_LOCKS
+
+ std::cerr << "OK (rv is " << rv << ")" << std::endl;
+#endif
+
+ return rv;
+}
+
+int
+AudioThread::releaseLock()
+{
+ int rv;
+#ifdef DEBUG_LOCKS
+
+ std::cerr << m_name << "::releaseLock()" << std::endl;
+#endif
+
+ rv = pthread_mutex_unlock(&m_lock);
+#ifdef DEBUG_LOCKS
+
+ std::cerr << "OK" << std::endl;
+#endif
+
+ return rv;
+}
+
+void
+AudioThread::signal()
+{
+#ifdef DEBUG_LOCKS
+ std::cerr << m_name << "::signal()" << std::endl;
+#endif
+
+ pthread_cond_signal(&m_condition);
+}
+
+
+AudioBussMixer::AudioBussMixer(SoundDriver *driver,
+ AudioInstrumentMixer *instrumentMixer,
+ unsigned int sampleRate,
+ unsigned int blockSize) :
+ AudioThread("AudioBussMixer", driver, sampleRate),
+ m_instrumentMixer(instrumentMixer),
+ m_blockSize(blockSize),
+ m_bussCount(0)
+{
+ // nothing else here
+}
+
+AudioBussMixer::~AudioBussMixer()
+{
+ for (unsigned int i = 0; i < m_processBuffers.size(); ++i) {
+ delete[] m_processBuffers[i];
+ }
+}
+
+AudioBussMixer::BufferRec::~BufferRec()
+{
+ for (size_t i = 0; i < buffers.size(); ++i)
+ delete buffers[i];
+}
+
+void
+AudioBussMixer::generateBuffers()
+{
+ // Not RT safe
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::generateBuffers" << std::endl;
+#endif
+
+ // This returns one too many, as the master is counted as buss 0
+ m_bussCount =
+ m_driver->getMappedStudio()->getObjectCount(MappedStudio::AudioBuss) - 1;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::generateBuffers: have " << m_bussCount << " busses" << std::endl;
+#endif
+
+ int bufferSamples = m_blockSize;
+
+ if (!m_driver->getLowLatencyMode()) {
+ RealTime bufferLength = m_driver->getAudioMixBufferLength();
+ int bufferSamples = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+ bufferSamples = ((bufferSamples / m_blockSize) + 1) * m_blockSize;
+ }
+
+ for (int i = 0; i < m_bussCount; ++i) {
+
+ BufferRec &rec = m_bufferMap[i];
+
+ if (rec.buffers.size() == 2)
+ continue;
+
+ for (unsigned int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t> *rb = new RingBuffer<sample_t>(bufferSamples);
+ if (!rb->mlock()) {
+ // std::cerr << "WARNING: AudioBussMixer::generateBuffers: couldn't lock ring buffer into real memory, performance may be impaired" << std::endl;
+ }
+ rec.buffers.push_back(rb);
+ }
+
+ MappedAudioBuss *mbuss =
+ m_driver->getMappedStudio()->getAudioBuss(i + 1); // master is 0
+
+ if (mbuss) {
+
+ float level = 0.0;
+ (void)mbuss->getProperty(MappedAudioBuss::Level, level);
+
+ float pan = 0.0;
+ (void)mbuss->getProperty(MappedAudioBuss::Pan, pan);
+
+ setBussLevels(i + 1, level, pan);
+ }
+ }
+
+ if (m_processBuffers.size() == 0) {
+ m_processBuffers.push_back(new sample_t[m_blockSize]);
+ m_processBuffers.push_back(new sample_t[m_blockSize]);
+ }
+}
+
+void
+AudioBussMixer::fillBuffers(const RealTime &currentTime)
+{
+ // Not RT safe
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::fillBuffers" << std::endl;
+#endif
+
+ emptyBuffers();
+ m_instrumentMixer->fillBuffers(currentTime);
+ kick();
+}
+
+void
+AudioBussMixer::emptyBuffers()
+{
+ // Not RT safe
+
+ getLock();
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::emptyBuffers" << std::endl;
+#endif
+
+ // We can't generate buffers before this, because we don't know how
+ // many busses there are
+ generateBuffers();
+
+ for (int i = 0; i < m_bussCount; ++i) {
+ m_bufferMap[i].dormant = true;
+ for (int ch = 0; ch < 2; ++ch) {
+ if (int(m_bufferMap[i].buffers.size()) > ch) {
+ m_bufferMap[i].buffers[ch]->reset();
+ }
+ }
+ }
+
+ releaseLock();
+}
+
+void
+AudioBussMixer::kick(bool wantLock, bool signalInstrumentMixer)
+{
+ // Needs to be RT safe if wantLock is not specified
+
+ if (wantLock)
+ getLock();
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::kick" << std::endl;
+#endif
+
+ processBlocks();
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::kick: processed" << std::endl;
+#endif
+
+ if (wantLock)
+ releaseLock();
+
+ if (signalInstrumentMixer) {
+ m_instrumentMixer->signal();
+ }
+}
+
+void
+AudioBussMixer::setBussLevels(int bussId, float dB, float pan)
+{
+ // No requirement to be RT safe
+
+ if (bussId == 0)
+ return ; // master
+ int buss = bussId - 1;
+
+ BufferRec &rec = m_bufferMap[buss];
+
+ float volume = AudioLevel::dB_to_multiplier(dB);
+
+ rec.gainLeft = volume * ((pan > 0.0) ? (1.0 - (pan / 100.0)) : 1.0);
+ rec.gainRight = volume * ((pan < 0.0) ? ((pan + 100.0) / 100.0) : 1.0);
+}
+
+void
+AudioBussMixer::updateInstrumentConnections()
+{
+ // Not RT safe
+
+ if (m_bussCount <= 0)
+ generateBuffers();
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ for (int buss = 0; buss < m_bussCount; ++buss) {
+
+ MappedAudioBuss *mbuss =
+ m_driver->getMappedStudio()->getAudioBuss(buss + 1); // master is 0
+
+ if (!mbuss) {
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::updateInstrumentConnections: buss " << buss << " not found" << std::endl;
+#endif
+
+ continue;
+ }
+
+ BufferRec &rec = m_bufferMap[buss];
+
+ while (int(rec.instruments.size()) < audioInstruments + synthInstruments) {
+ rec.instruments.push_back(false);
+ }
+
+ std::vector<InstrumentId> instruments = mbuss->getInstruments();
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ size_t j = 0;
+ for (j = 0; j < instruments.size(); ++j) {
+ if (instruments[j] == id) {
+ rec.instruments[i] = true;
+ break;
+ }
+ }
+ if (j == instruments.size())
+ rec.instruments[i] = false;
+ }
+ }
+}
+
+void
+AudioBussMixer::processBlocks()
+{
+ // Needs to be RT safe
+
+ if (m_bussCount == 0)
+ return ;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ if (m_driver->isPlaying())
+ std::cerr << "AudioBussMixer::processBlocks" << std::endl;
+#endif
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ bool *processedInstruments = (bool *)alloca
+ ((audioInstruments + synthInstruments) * sizeof(bool));
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+ processedInstruments[i] = false;
+ }
+
+ int minBlocks = 0;
+ bool haveMinBlocks = false;
+
+ for (int buss = 0; buss < m_bussCount; ++buss) {
+
+ BufferRec &rec = m_bufferMap[buss];
+
+ float gain[2];
+ gain[0] = rec.gainLeft;
+ gain[1] = rec.gainRight;
+
+ // The dormant calculation here depends on the buffer length
+ // for this mixer being the same as that for the instrument mixer
+
+ size_t minSpace = 0;
+
+ for (int ch = 0; ch < 2; ++ch) {
+
+ size_t w = rec.buffers[ch]->getWriteSpace();
+ if (ch == 0 || w < minSpace)
+ minSpace = w;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::processBlocks: buss " << buss << ": write space " << w << " on channel " << ch << std::endl;
+#endif
+
+ if (minSpace == 0)
+ break;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ // is this instrument on this buss?
+ if (int(rec.instruments.size()) <= i ||
+ !rec.instruments[i])
+ continue;
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+ if (rb) {
+ size_t r = rb->getReadSpace(1);
+ if (r < minSpace)
+ minSpace = r;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ if (id == 1000) {
+ std::cerr << "AudioBussMixer::processBlocks: buss " << buss << ": read space " << r << " on instrument " << id << ", channel " << ch << std::endl;
+ }
+#endif
+
+ if (minSpace == 0)
+ break;
+ }
+ }
+
+ if (minSpace == 0)
+ break;
+ }
+
+ int blocks = minSpace / m_blockSize;
+ if (!haveMinBlocks || (blocks < minBlocks)) {
+ minBlocks = blocks;
+ haveMinBlocks = true;
+ }
+
+#ifdef DEBUG_BUSS_MIXER
+ if (m_driver->isPlaying())
+ std::cerr << "AudioBussMixer::processBlocks: doing " << blocks << " blocks at block size " << m_blockSize << std::endl;
+#endif
+
+ for (int block = 0; block < blocks; ++block) {
+
+ memset(m_processBuffers[0], 0, m_blockSize * sizeof(sample_t));
+ memset(m_processBuffers[1], 0, m_blockSize * sizeof(sample_t));
+
+ bool dormant = true;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ // is this instrument on this buss?
+ if (int(rec.instruments.size()) <= i ||
+ !rec.instruments[i])
+ continue;
+
+ if (processedInstruments[i]) {
+ // we aren't set up to process any instrument to
+ // more than one buss
+ continue;
+ } else {
+ processedInstruments[i] = true;
+ }
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ if (m_instrumentMixer->isInstrumentDormant(id)) {
+
+ for (int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (rb)
+ rb->skip(m_blockSize,
+ 1);
+ }
+ } else {
+ dormant = false;
+
+ for (int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (rb)
+ rb->readAdding(m_processBuffers[ch],
+ m_blockSize,
+ 1);
+ }
+ }
+ }
+
+ if (m_instrumentMixer) {
+ AudioInstrumentMixer::PluginList &plugins =
+ m_instrumentMixer->getBussPlugins(buss + 1);
+
+ // This will have to do for now!
+ if (!plugins.empty())
+ dormant = false;
+
+ for (AudioInstrumentMixer::PluginList::iterator pli =
+ plugins.begin(); pli != plugins.end(); ++pli) {
+
+ RunnablePluginInstance *plugin = *pli;
+ if (!plugin || plugin->isBypassed())
+ continue;
+
+ unsigned int ch = 0;
+
+ while (ch < plugin->getAudioInputCount()) {
+ if (ch < 2) {
+ memcpy(plugin->getAudioInputBuffers()[ch],
+ m_processBuffers[ch],
+ m_blockSize * sizeof(sample_t));
+ } else {
+ memset(plugin->getAudioInputBuffers()[ch], 0,
+ m_blockSize * sizeof(sample_t));
+ }
+ ++ch;
+ }
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "Running buss plugin with " << plugin->getAudioInputCount()
+ << " inputs, " << plugin->getAudioOutputCount() << " outputs" << std::endl;
+#endif
+
+ // We don't currently maintain a record of our
+ // frame time in the buss mixer. This will screw
+ // up any plugin that requires a good frame count:
+ // at the moment that only means DSSI effects
+ // plugins using run_multiple_synths, which would
+ // be an unusual although plausible combination
+ plugin->run(RealTime::zeroTime);
+
+ ch = 0;
+
+ while (ch < 2 && ch < plugin->getAudioOutputCount()) {
+
+ denormalKill(plugin->getAudioOutputBuffers()[ch],
+ m_blockSize);
+
+ memcpy(m_processBuffers[ch],
+ plugin->getAudioOutputBuffers()[ch],
+ m_blockSize * sizeof(sample_t));
+
+ ++ch;
+ }
+ }
+ }
+
+ for (int ch = 0; ch < 2; ++ch) {
+ if (dormant) {
+ rec.buffers[ch]->zero(m_blockSize);
+ } else {
+ for (size_t j = 0; j < m_blockSize; ++j) {
+ m_processBuffers[ch][j] *= gain[ch];
+ }
+ rec.buffers[ch]->write(m_processBuffers[ch], m_blockSize);
+ }
+ }
+
+ rec.dormant = dormant;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ if (m_driver->isPlaying())
+ std::cerr << "AudioBussMixer::processBlocks: buss " << buss << (dormant ? " dormant" : " not dormant") << std::endl;
+#endif
+
+ }
+ }
+
+ // any unprocessed instruments need to be skipped, or they'll block
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ if (processedInstruments[i])
+ continue;
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ for (int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (rb)
+ rb->skip(m_blockSize * minBlocks,
+ 1);
+ }
+ }
+
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::processBlocks: done" << std::endl;
+#endif
+}
+
+void
+AudioBussMixer::threadRun()
+{
+ while (!m_exiting) {
+
+ if (m_driver->areClocksRunning()) {
+ kick(false);
+ }
+
+ RealTime t = m_driver->getAudioMixBufferLength();
+ t = t / 2;
+ if (t < RealTime(0, 10000000))
+ t = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ t = t + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+}
+
+
+AudioInstrumentMixer::AudioInstrumentMixer(SoundDriver *driver,
+ AudioFileReader *fileReader,
+ unsigned int sampleRate,
+ unsigned int blockSize) :
+ AudioThread("AudioInstrumentMixer", driver, sampleRate),
+ m_fileReader(fileReader),
+ m_bussMixer(0),
+ m_blockSize(blockSize)
+{
+ // Pregenerate empty plugin slots
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ PluginList &list = m_plugins[id];
+ for (int j = 0; j < int(Instrument::PLUGIN_COUNT); ++j) {
+ list.push_back(0);
+ }
+
+ if (i >= audioInstruments) {
+ m_synths[id] = 0;
+ }
+ }
+
+ // Leave the buffer map and process buffer list empty for now.
+ // The buffer length can change between plays, so we always
+ // examine the buffers in fillBuffers and are prepared to
+ // regenerate from scratch if necessary. Don't like it though.
+}
+
+AudioInstrumentMixer::~AudioInstrumentMixer()
+{
+ std::cerr << "AudioInstrumentMixer::~AudioInstrumentMixer" << std::endl;
+ // BufferRec dtor will handle the BufferMap
+
+ removeAllPlugins();
+
+ for (std::vector<sample_t *>::iterator i = m_processBuffers.begin();
+ i != m_processBuffers.end(); ++i) {
+ delete[] *i;
+ }
+
+ std::cerr << "AudioInstrumentMixer::~AudioInstrumentMixer exiting" << std::endl;
+}
+
+AudioInstrumentMixer::BufferRec::~BufferRec()
+{
+ for (size_t i = 0; i < buffers.size(); ++i)
+ delete buffers[i];
+}
+
+
+void
+AudioInstrumentMixer::setPlugin(InstrumentId id, int position, QString identifier)
+{
+ // Not RT safe
+
+ std::cerr << "AudioInstrumentMixer::setPlugin(" << id << ", " << position << ", " << identifier << ")" << std::endl;
+
+ int channels = 2;
+ if (m_bufferMap.find(id) != m_bufferMap.end()) {
+ channels = m_bufferMap[id].channels;
+ }
+
+ RunnablePluginInstance *instance = 0;
+
+ PluginFactory *factory = PluginFactory::instanceFor(identifier);
+ if (factory) {
+ instance = factory->instantiatePlugin(identifier,
+ id,
+ position,
+ m_sampleRate,
+ m_blockSize,
+ channels);
+ if (instance && !instance->isOK()) {
+ std::cerr << "AudioInstrumentMixer::setPlugin(" << id << ", " << position
+ << ": instance is not OK" << std::endl;
+ delete instance;
+ instance = 0;
+ }
+ } else {
+ std::cerr << "AudioInstrumentMixer::setPlugin: No factory for identifier "
+ << identifier << std::endl;
+ }
+
+ RunnablePluginInstance *oldInstance = 0;
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+
+ oldInstance = m_synths[id];
+ m_synths[id] = instance;
+
+ } else {
+
+ PluginList &list = m_plugins[id];
+
+ if (position < Instrument::PLUGIN_COUNT) {
+ while (position >= (int)list.size()) {
+ list.push_back(0);
+ }
+ oldInstance = list[position];
+ list[position] = instance;
+ } else {
+ std::cerr << "AudioInstrumentMixer::setPlugin: No position "
+ << position << " for instrument " << id << std::endl;
+ delete instance;
+ }
+ }
+
+ if (oldInstance) {
+ m_driver->claimUnwantedPlugin(oldInstance);
+ }
+}
+
+void
+AudioInstrumentMixer::removePlugin(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ std::cerr << "AudioInstrumentMixer::removePlugin(" << id << ", " << position << ")" << std::endl;
+
+ RunnablePluginInstance *oldInstance = 0;
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+
+ if (m_synths[id]) {
+ oldInstance = m_synths[id];
+ m_synths[id] = 0;
+ }
+
+ } else {
+
+ PluginList &list = m_plugins[id];
+ if (position < (int)list.size()) {
+ oldInstance = list[position];
+ list[position] = 0;
+ }
+ }
+
+ if (oldInstance) {
+ m_driver->claimUnwantedPlugin(oldInstance);
+ }
+}
+
+void
+AudioInstrumentMixer::removeAllPlugins()
+{
+ // Not RT safe
+
+ std::cerr << "AudioInstrumentMixer::removeAllPlugins" << std::endl;
+
+ for (SynthPluginMap::iterator i = m_synths.begin();
+ i != m_synths.end(); ++i) {
+ if (i->second) {
+ RunnablePluginInstance *instance = i->second;
+ i->second = 0;
+ m_driver->claimUnwantedPlugin(instance);
+ }
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ PluginList &list = j->second;
+
+ for (PluginList::iterator i = list.begin(); i != list.end(); ++i) {
+ RunnablePluginInstance *instance = *i;
+ *i = 0;
+ m_driver->claimUnwantedPlugin(instance);
+ }
+ }
+}
+
+
+RunnablePluginInstance *
+AudioInstrumentMixer::getPluginInstance(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+ return m_synths[id];
+ } else {
+ PluginList &list = m_plugins[id];
+ if (position < int(list.size()))
+ return list[position];
+ }
+ return 0;
+}
+
+
+void
+AudioInstrumentMixer::setPluginPortValue(InstrumentId id, int position,
+ unsigned int port, float value)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+
+ if (instance) {
+ instance->setPortValue(port, value);
+ }
+}
+
+float
+AudioInstrumentMixer::getPluginPortValue(InstrumentId id, int position,
+ unsigned int port)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+
+ if (instance) {
+ return instance->getPortValue(port);
+ }
+
+ return 0;
+}
+
+void
+AudioInstrumentMixer::setPluginBypass(InstrumentId id, int position, bool bypass)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ instance->setBypassed(bypass);
+}
+
+QStringList
+AudioInstrumentMixer::getPluginPrograms(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ QStringList programs;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ programs = instance->getPrograms();
+ return programs;
+}
+
+QString
+AudioInstrumentMixer::getPluginProgram(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ QString program;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ program = instance->getCurrentProgram();
+ return program;
+}
+
+QString
+AudioInstrumentMixer::getPluginProgram(InstrumentId id, int position, int bank,
+ int program)
+{
+ // Not RT safe
+
+ QString programName;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ programName = instance->getProgram(bank, program);
+ return programName;
+}
+
+unsigned long
+AudioInstrumentMixer::getPluginProgram(InstrumentId id, int position, QString name)
+{
+ // Not RT safe
+
+ unsigned long program = 0;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ program = instance->getProgram(name);
+ return program;
+}
+
+void
+AudioInstrumentMixer::setPluginProgram(InstrumentId id, int position, QString program)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ instance->selectProgram(program);
+}
+
+QString
+AudioInstrumentMixer::configurePlugin(InstrumentId id, int position, QString key, QString value)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ return instance->configure(key, value);
+ return QString();
+}
+
+void
+AudioInstrumentMixer::discardPluginEvents()
+{
+ getLock();
+ if (m_bussMixer) m_bussMixer->getLock();
+
+ for (SynthPluginMap::iterator j = m_synths.begin();
+ j != m_synths.end(); ++j) {
+
+ RunnablePluginInstance *instance = j->second;
+ if (instance) instance->discardEvents();
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+
+ RunnablePluginInstance *instance = *i;
+ if (instance) instance->discardEvents();
+ }
+ }
+
+ if (m_bussMixer) m_bussMixer->releaseLock();
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::resetAllPlugins(bool discardEvents)
+{
+ // Not RT safe
+
+ // lock required here to protect against calling
+ // activate/deactivate at the same time as run()
+
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::resetAllPlugins!" << std::endl;
+ if (discardEvents) std::cerr << "(discardEvents true)" << std::endl;
+#endif
+
+ getLock();
+ if (m_bussMixer)
+ m_bussMixer->getLock();
+
+ for (SynthPluginMap::iterator j = m_synths.begin();
+ j != m_synths.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ int channels = 2;
+ if (m_bufferMap.find(id) != m_bufferMap.end()) {
+ channels = m_bufferMap[id].channels;
+ }
+
+ RunnablePluginInstance *instance = j->second;
+
+ if (instance) {
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::resetAllPlugins: (re)setting " << channels << " channels on synth for instrument " << id << std::endl;
+#endif
+
+ if (discardEvents)
+ instance->discardEvents();
+ instance->setIdealChannelCount(channels);
+ }
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ int channels = 2;
+ if (m_bufferMap.find(id) != m_bufferMap.end()) {
+ channels = m_bufferMap[id].channels;
+ }
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+
+ RunnablePluginInstance *instance = *i;
+
+ if (instance) {
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::resetAllPlugins: (re)setting " << channels << " channels on plugin for instrument " << id << std::endl;
+#endif
+
+ if (discardEvents)
+ instance->discardEvents();
+ instance->setIdealChannelCount(channels);
+ }
+ }
+ }
+
+ if (m_bussMixer)
+ m_bussMixer->releaseLock();
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::destroyAllPlugins()
+{
+ // Not RT safe
+
+ getLock();
+ if (m_bussMixer)
+ m_bussMixer->getLock();
+
+ // Delete immediately, as we're probably exiting here -- don't use
+ // the scavenger.
+
+ std::cerr << "AudioInstrumentMixer::destroyAllPlugins" << std::endl;
+
+ for (SynthPluginMap::iterator j = m_synths.begin();
+ j != m_synths.end(); ++j) {
+ RunnablePluginInstance *instance = j->second;
+ j->second = 0;
+ delete instance;
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+
+ RunnablePluginInstance *instance = *i;
+ *i = 0;
+ delete instance;
+ }
+ }
+
+ // and tell the driver to get rid of anything already scavenged.
+ m_driver->scavengePlugins();
+
+ if (m_bussMixer)
+ m_bussMixer->releaseLock();
+ releaseLock();
+}
+
+size_t
+AudioInstrumentMixer::getPluginLatency(unsigned int id)
+{
+ // Not RT safe
+
+ size_t latency = 0;
+
+ RunnablePluginInstance *synth = m_synths[id];
+ if (synth)
+ latency += m_synths[id]->getLatency();
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+ RunnablePluginInstance *plugin = *i;
+ if (plugin)
+ latency += plugin->getLatency();
+ }
+
+ return latency;
+}
+
+void
+AudioInstrumentMixer::generateBuffers()
+{
+ // Not RT safe
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ unsigned int maxChannels = 0;
+
+ int bufferSamples = m_blockSize;
+
+ if (!m_driver->getLowLatencyMode()) {
+ RealTime bufferLength = m_driver->getAudioMixBufferLength();
+ int bufferSamples = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+ bufferSamples = ((bufferSamples / m_blockSize) + 1) * m_blockSize;
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::generateBuffers: Buffer length is " << bufferLength << "; buffer samples " << bufferSamples << " (sample rate " << m_sampleRate << ")" << std::endl;
+#endif
+
+ }
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ // Get a fader for this instrument - if we can't then this
+ // isn't a valid audio track.
+ MappedAudioFader *fader = m_driver->getMappedStudio()->getAudioFader(id);
+
+ if (!fader) {
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::generateBuffers: no fader for audio instrument " << id << std::endl;
+#endif
+
+ continue;
+ }
+
+ float fch = 2;
+ (void)fader->getProperty(MappedAudioFader::Channels, fch);
+ unsigned int channels = (unsigned int)fch;
+
+ BufferRec &rec = m_bufferMap[id];
+
+ rec.channels = channels;
+
+ // We always have stereo buffers (for output of pan)
+ // even on a mono instrument.
+ if (channels < 2)
+ channels = 2;
+ if (channels > maxChannels)
+ maxChannels = channels;
+
+ bool replaceBuffers = (rec.buffers.size() > channels);
+
+ if (!replaceBuffers) {
+ for (size_t i = 0; i < rec.buffers.size(); ++i) {
+ if (rec.buffers[i]->getSize() != bufferSamples) {
+ replaceBuffers = true;
+ break;
+ }
+ }
+ }
+
+ if (replaceBuffers) {
+ for (size_t i = 0; i < rec.buffers.size(); ++i) {
+ delete rec.buffers[i];
+ }
+ rec.buffers.clear();
+ }
+
+ while (rec.buffers.size() < channels) {
+
+ // All our ringbuffers are set up for two readers: the
+ // buss mix thread and the main process thread for
+ // e.g. JACK. The main process thread gets the zero-id
+ // reader, so it gets the same API as if this was a
+ // single-reader buffer; the buss mixer has to remember to
+ // explicitly request reader 1.
+
+ RingBuffer<sample_t, 2> *rb =
+ new RingBuffer<sample_t, 2>(bufferSamples);
+
+ if (!rb->mlock()) {
+ // std::cerr << "WARNING: AudioInstrumentMixer::generateBuffers: couldn't lock ring buffer into real memory, performance may be impaired" << std::endl;
+ }
+ rec.buffers.push_back(rb);
+ }
+
+ float level = 0.0;
+ (void)fader->getProperty(MappedAudioFader::FaderLevel, level);
+
+ float pan = 0.0;
+ (void)fader->getProperty(MappedAudioFader::Pan, pan);
+
+ setInstrumentLevels(id, level, pan);
+ }
+
+ // Make room for up to 16 busses here, to avoid reshuffling later
+ int busses = 16;
+ if (m_bussMixer)
+ busses = std::max(busses, m_bussMixer->getBussCount());
+ for (int i = 0; i < busses; ++i) {
+ PluginList &list = m_plugins[i + 1];
+ while (list.size() < Instrument::PLUGIN_COUNT) {
+ list.push_back(0);
+ }
+ }
+
+ while (m_processBuffers.size() > maxChannels) {
+ std::vector<sample_t *>::iterator bi = m_processBuffers.end();
+ --bi;
+ delete[] *bi;
+ m_processBuffers.erase(bi);
+ }
+ while (m_processBuffers.size() < maxChannels) {
+ m_processBuffers.push_back(new sample_t[m_blockSize]);
+ }
+}
+
+void
+AudioInstrumentMixer::fillBuffers(const RealTime &currentTime)
+{
+ // Not RT safe
+
+ emptyBuffers(currentTime);
+
+ getLock();
+
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::fillBuffers(" << currentTime << ")" << std::endl;
+#endif
+
+ bool discard;
+ processBlocks(discard);
+
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::allocateBuffers()
+{
+ // Not RT safe
+
+ getLock();
+
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::allocateBuffers()" << std::endl;
+#endif
+
+ generateBuffers();
+
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::emptyBuffers(RealTime currentTime)
+{
+ // Not RT safe
+
+ getLock();
+
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::emptyBuffers(" << currentTime << ")" << std::endl;
+#endif
+
+ generateBuffers();
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ m_bufferMap[id].dormant = true;
+ m_bufferMap[id].muted = false;
+ m_bufferMap[id].zeroFrames = 0;
+ m_bufferMap[id].filledTo = currentTime;
+
+ for (size_t i = 0; i < m_bufferMap[id].buffers.size(); ++i) {
+ m_bufferMap[id].buffers[i]->reset();
+ }
+ }
+
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::setInstrumentLevels(InstrumentId id, float dB, float pan)
+{
+ // No requirement to be RT safe
+
+ BufferRec &rec = m_bufferMap[id];
+
+ float volume = AudioLevel::dB_to_multiplier(dB);
+
+ rec.gainLeft = volume * ((pan > 0.0) ? (1.0 - (pan / 100.0)) : 1.0);
+ rec.gainRight = volume * ((pan < 0.0) ? ((pan + 100.0) / 100.0) : 1.0);
+ rec.volume = volume;
+}
+
+void
+AudioInstrumentMixer::updateInstrumentMuteStates()
+{
+ SequencerDataBlock *sdb = m_driver->getSequencerDataBlock();
+ if (sdb) {
+ ControlBlock *cb = sdb->getControlBlock();
+ if (cb) {
+
+ for (BufferMap::iterator i = m_bufferMap.begin();
+ i != m_bufferMap.end(); ++i) {
+
+ InstrumentId id = i->first;
+ BufferRec &rec = i->second;
+
+ if (id >= SoftSynthInstrumentBase) {
+ rec.muted = cb->isInstrumentMuted(id);
+ } else {
+ rec.muted = cb->isInstrumentUnused(id);
+ }
+ }
+ }
+ }
+}
+
+void
+AudioInstrumentMixer::processBlocks(bool &readSomething)
+{
+ // Needs to be RT safe
+
+#ifdef DEBUG_MIXER
+ if (m_driver->isPlaying())
+ std::cerr << "AudioInstrumentMixer::processBlocks" << std::endl;
+#endif
+
+ // Profiler profiler("processBlocks", true);
+
+ const AudioPlayQueue *queue = m_driver->getAudioQueue();
+
+ for (BufferMap::iterator i = m_bufferMap.begin();
+ i != m_bufferMap.end(); ++i) {
+
+ InstrumentId id = i->first;
+ BufferRec &rec = i->second;
+
+ // This "muted" flag actually only strictly means muted when
+ // applied to synth instruments. For audio instruments it's
+ // only true if the instrument is not in use at all (see
+ // updateInstrumentMuteStates above). It's not safe to base
+ // the empty calculation on muted state for audio tracks,
+ // because that causes buffering problems when the mute is
+ // toggled for an audio track while it's playing a file.
+
+ bool empty = false;
+
+ if (rec.muted) {
+ empty = true;
+ } else {
+ if (id >= SoftSynthInstrumentBase) {
+ empty = (!m_synths[id] || m_synths[id]->isBypassed());
+ } else {
+ empty = !queue->haveFilesForInstrument(id);
+ }
+
+ if (empty) {
+ for (PluginList::iterator j = m_plugins[id].begin();
+ j != m_plugins[id].end(); ++j) {
+ if (*j != 0) {
+ empty = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!empty && rec.empty) {
+
+ // This instrument is becoming freshly non-empty. We need
+ // to set its filledTo field to match that of an existing
+ // non-empty instrument, if we can find one.
+
+ for (BufferMap::iterator j = m_bufferMap.begin();
+ j != m_bufferMap.end(); ++j) {
+
+ if (j->first == i->first)
+ continue;
+ if (j->second.empty)
+ continue;
+
+ rec.filledTo = j->second.filledTo;
+ break;
+ }
+ }
+
+ rec.empty = empty;
+
+ // For a while we were setting empty to true if the volume on
+ // the track was zero, but that breaks continuity if there is
+ // actually a file on the track -- processEmptyBlocks won't
+ // read it, so it'll fall behind if we put the volume up again.
+ }
+
+ bool more = true;
+
+ static const int MAX_FILES_PER_INSTRUMENT = 500;
+ static PlayableAudioFile *playing[MAX_FILES_PER_INSTRUMENT];
+
+ RealTime blockDuration = RealTime::frame2RealTime(m_blockSize, m_sampleRate);
+
+ while (more) {
+
+ more = false;
+
+ for (BufferMap::iterator i = m_bufferMap.begin();
+ i != m_bufferMap.end(); ++i) {
+
+ InstrumentId id = i->first;
+ BufferRec &rec = i->second;
+
+ if (rec.empty) {
+ rec.dormant = true;
+ continue;
+ }
+
+ size_t playCount = MAX_FILES_PER_INSTRUMENT;
+
+ if (id >= SoftSynthInstrumentBase)
+ playCount = 0;
+ else {
+ queue->getPlayingFilesForInstrument(rec.filledTo,
+ blockDuration, id,
+ playing, playCount);
+ }
+
+ if (processBlock(id, playing, playCount, readSomething)) {
+ more = true;
+ }
+ }
+ }
+}
+
+
+bool
+AudioInstrumentMixer::processBlock(InstrumentId id,
+ PlayableAudioFile **playing,
+ size_t playCount,
+ bool &readSomething)
+{
+ // Needs to be RT safe
+
+ // Profiler profiler("processBlock", true);
+
+ BufferRec &rec = m_bufferMap[id];
+ RealTime bufferTime = rec.filledTo;
+
+#ifdef DEBUG_MIXER
+ // if (m_driver->isPlaying()) {
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): buffer time is " << bufferTime << std::endl;
+ // }
+#endif
+
+ unsigned int channels = rec.channels;
+ if (channels > rec.buffers.size())
+ channels = rec.buffers.size();
+ if (channels > m_processBuffers.size())
+ channels = m_processBuffers.size();
+ if (channels == 0) {
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): nominal channels " << rec.channels << ", ring buffers " << rec.buffers.size() << ", process buffers " << m_processBuffers.size() << std::endl;
+#endif
+
+ return false; // buffers just haven't been set up yet
+ }
+
+ unsigned int targetChannels = channels;
+ if (targetChannels < 2)
+ targetChannels = 2; // fill at least two buffers
+
+ size_t minWriteSpace = 0;
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+ size_t thisWriteSpace = rec.buffers[ch]->getWriteSpace();
+ if (ch == 0 || thisWriteSpace < minWriteSpace) {
+ minWriteSpace = thisWriteSpace;
+ if (minWriteSpace < m_blockSize) {
+#ifdef DEBUG_MIXER
+ // if (m_driver->isPlaying()) {
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): only " << minWriteSpace << " write space on channel " << ch << " for block size " << m_blockSize << std::endl;
+ // }
+#endif
+
+ return false;
+ }
+ }
+ }
+
+ PluginList &plugins = m_plugins[id];
+
+#ifdef DEBUG_MIXER
+
+ if ((id % 100) == 0 && m_driver->isPlaying())
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): minWriteSpace is " << minWriteSpace << std::endl;
+#else
+#ifdef DEBUG_MIXER_LIGHTWEIGHT
+
+ if ((id % 100) == 0 && m_driver->isPlaying())
+ std::cout << minWriteSpace << "/" << rec.buffers[0]->getSize() << std::endl;
+#endif
+#endif
+
+#ifdef DEBUG_MIXER
+
+ if ((id % 100) == 0 && playCount > 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): " << playCount << " audio file(s) to consider" << std::endl;
+#endif
+
+ bool haveBlock = true;
+ bool haveMore = false;
+
+ for (size_t fileNo = 0; fileNo < playCount; ++fileNo) {
+
+ bool acceptable = false;
+ PlayableAudioFile *file = playing[fileNo];
+
+ size_t frames = file->getSampleFramesAvailable();
+ acceptable = ((frames >= m_blockSize) || file->isFullyBuffered());
+
+ if (acceptable &&
+ (minWriteSpace >= m_blockSize * 2) &&
+ (frames >= m_blockSize * 2)) {
+
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): will be asking for more" << std::endl;
+#endif
+
+ haveMore = true;
+ }
+
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): file has " << frames << " frames available" << std::endl;
+#endif
+
+ if (!acceptable) {
+
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): file " << file->getAudioFile()->getFilename() << " has " << frames << " frames available, says isBuffered " << file->isBuffered() << std::endl;
+
+ if (!m_driver->getLowLatencyMode()) {
+
+ // Not a serious problem, just block on this
+ // instrument and return to it a little later.
+ haveBlock = false;
+
+ } else {
+ // In low latency mode, this is a serious problem if
+ // the file has been buffered and simply isn't filling
+ // fast enough. Otherwise we have to assume that the
+ // problem is something like a new file being dropped
+ // in by unmute during playback, in which case we have
+ // to accept that it won't be available for a while
+ // and just read silence from it instead.
+ if (file->isBuffered()) {
+ m_driver->reportFailure(MappedEvent::FailureDiscUnderrun);
+ haveBlock = false;
+ } else {
+ // ignore happily.
+ }
+ }
+ }
+ }
+
+ if (!haveBlock) {
+ return false; // blocked;
+ }
+
+#ifdef DEBUG_MIXER
+ if (!haveMore) {
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): won't be asking for more" << std::endl;
+ }
+#endif
+
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+ memset(m_processBuffers[ch], 0, sizeof(sample_t) * m_blockSize);
+ }
+
+ RunnablePluginInstance *synth = m_synths[id];
+
+ if (synth && !synth->isBypassed()) {
+
+ synth->run(bufferTime);
+
+ unsigned int ch = 0;
+
+ while (ch < synth->getAudioOutputCount() && ch < channels) {
+ denormalKill(synth->getAudioOutputBuffers()[ch],
+ m_blockSize);
+ memcpy(m_processBuffers[ch],
+ synth->getAudioOutputBuffers()[ch],
+ m_blockSize * sizeof(sample_t));
+ ++ch;
+ }
+ }
+
+ if (haveBlock) {
+
+ // Mix in a block from each playing file on this instrument.
+
+ for (size_t fileNo = 0; fileNo < playCount; ++fileNo) {
+
+ PlayableAudioFile *file = playing[fileNo];
+
+ size_t offset = 0;
+ size_t blockSize = m_blockSize;
+
+ if (file->getStartTime() > bufferTime) {
+ offset = RealTime::realTime2Frame
+ (file->getStartTime() - bufferTime, m_sampleRate);
+ if (offset < blockSize)
+ blockSize -= offset;
+ else
+ blockSize = 0;
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::processBlock: file starts at offset " << offset << ", block size now " << blockSize << std::endl;
+#endif
+
+ }
+
+ //!!! This addSamples call is what is supposed to signal
+ // to a playable audio file when the end of the file has
+ // been reached. But for some playables it appears the
+ // file overruns, possibly due to rounding errors in
+ // sample rate conversion, and so we stop reading from it
+ // before it's actually done. I don't particularly mind
+ // that from a sound quality POV (after all it's badly
+ // resampled already) but unfortunately it means we leak
+ // pooled buffers.
+
+ if (blockSize > 0) {
+ file->addSamples(m_processBuffers, channels, blockSize, offset);
+ readSomething = true;
+ }
+ }
+ }
+
+ // Apply plugins. There are various copy-reducing
+ // optimisations available here, but we're not even going to
+ // think about them yet. Note that we force plugins to mono
+ // on a mono track, even though we have stereo output buffers
+ // -- stereo only comes into effect at the pan stage, and
+ // these are pre-fader plugins.
+
+ for (PluginList::iterator pli = plugins.begin();
+ pli != plugins.end(); ++pli) {
+
+ RunnablePluginInstance *plugin = *pli;
+ if (!plugin || plugin->isBypassed())
+ continue;
+
+ unsigned int ch = 0;
+
+ // If a plugin has more input channels than we have
+ // available, we duplicate up to stereo and leave any
+ // remaining channels empty.
+
+ while (ch < plugin->getAudioInputCount()) {
+
+ if (ch < channels || ch < 2) {
+ memcpy(plugin->getAudioInputBuffers()[ch],
+ m_processBuffers[ch % channels],
+ m_blockSize * sizeof(sample_t));
+ } else {
+ memset(plugin->getAudioInputBuffers()[ch], 0,
+ m_blockSize * sizeof(sample_t));
+ }
+ ++ch;
+ }
+
+#ifdef DEBUG_MIXER
+ std::cerr << "Running plugin with " << plugin->getAudioInputCount()
+ << " inputs, " << plugin->getAudioOutputCount() << " outputs" << std::endl;
+#endif
+
+ plugin->run(bufferTime);
+
+ ch = 0;
+
+ while (ch < plugin->getAudioOutputCount()) {
+
+ denormalKill(plugin->getAudioOutputBuffers()[ch],
+ m_blockSize);
+
+ if (ch < channels) {
+ memcpy(m_processBuffers[ch],
+ plugin->getAudioOutputBuffers()[ch],
+ m_blockSize * sizeof(sample_t));
+ } else if (ch == 1) {
+ // stereo output from plugin on a mono track
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_processBuffers[0][i] +=
+ plugin->getAudioOutputBuffers()[ch][i];
+ m_processBuffers[0][i] /= 2;
+ }
+ } else {
+ break;
+ }
+
+ ++ch;
+ }
+ }
+
+ // special handling for pan on mono tracks
+
+ bool allZeros = true;
+
+ if (targetChannels == 2 && channels == 1) {
+
+ for (size_t i = 0; i < m_blockSize; ++i) {
+
+ sample_t sample = m_processBuffers[0][i];
+
+ m_processBuffers[0][i] = sample * rec.gainLeft;
+ m_processBuffers[1][i] = sample * rec.gainRight;
+
+ if (allZeros && sample != 0.0)
+ allZeros = false;
+ }
+
+ rec.buffers[0]->write(m_processBuffers[0], m_blockSize);
+ rec.buffers[1]->write(m_processBuffers[1], m_blockSize);
+
+ } else {
+
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+
+ float gain = ((ch == 0) ? rec.gainLeft :
+ (ch == 1) ? rec.gainRight : rec.volume);
+
+ for (size_t i = 0; i < m_blockSize; ++i) {
+
+ // handle volume and pan
+ m_processBuffers[ch][i] *= gain;
+
+ if (allZeros && m_processBuffers[ch][i] != 0.0)
+ allZeros = false;
+ }
+
+ rec.buffers[ch]->write(m_processBuffers[ch], m_blockSize);
+ }
+ }
+
+ bool dormant = true;
+
+ if (allZeros) {
+ rec.zeroFrames += m_blockSize;
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+ if (rec.buffers[ch]->getReadSpace() > rec.zeroFrames) {
+ dormant = false;
+ }
+ }
+ } else {
+ rec.zeroFrames = 0;
+ dormant = false;
+ }
+
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0 && m_driver->isPlaying())
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): setting dormant to " << dormant << std::endl;
+#endif
+
+ rec.dormant = dormant;
+ bufferTime = bufferTime + RealTime::frame2RealTime(m_blockSize,
+ m_sampleRate);
+
+ rec.filledTo = bufferTime;
+
+#ifdef DEBUG_MIXER
+
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): done, returning " << haveMore << std::endl;
+#endif
+
+ return haveMore;
+}
+
+void
+AudioInstrumentMixer::kick(bool wantLock)
+{
+ // Needs to be RT safe if wantLock is not specified
+
+ if (wantLock)
+ getLock();
+
+ bool readSomething = false;
+ processBlocks(readSomething);
+ if (readSomething)
+ m_fileReader->signal();
+
+ if (wantLock)
+ releaseLock();
+}
+
+
+void
+AudioInstrumentMixer::threadRun()
+{
+ while (!m_exiting) {
+
+ if (m_driver->areClocksRunning()) {
+ kick(false);
+ }
+
+ RealTime t = m_driver->getAudioMixBufferLength();
+ t = t / 2;
+ if (t < RealTime(0, 10000000))
+ t = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ t = t + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+}
+
+
+
+AudioFileReader::AudioFileReader(SoundDriver *driver,
+ unsigned int sampleRate) :
+ AudioThread("AudioFileReader", driver, sampleRate)
+{
+ // nothing else here
+}
+
+AudioFileReader::~AudioFileReader()
+{}
+
+void
+AudioFileReader::fillBuffers(const RealTime &currentTime)
+{
+ getLock();
+
+ // Tell every audio file the play start time.
+
+ const AudioPlayQueue *queue = m_driver->getAudioQueue();
+
+ RealTime bufferLength = m_driver->getAudioReadBufferLength();
+ int bufferFrames = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+
+ int poolSize = queue->getMaxBuffersRequired() * 2 + 4;
+ PlayableAudioFile::setRingBufferPoolSizes(poolSize, bufferFrames);
+
+ const AudioPlayQueue::FileSet &files = queue->getAllScheduledFiles();
+
+#ifdef DEBUG_READER
+
+ std::cerr << "AudioFileReader::fillBuffers: have " << files.size() << " audio files total" << std::endl;
+#endif
+
+ for (AudioPlayQueue::FileSet::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ (*fi)->clearBuffers();
+ }
+
+ int allocated = 0;
+ for (AudioPlayQueue::FileSet::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ (*fi)->fillBuffers(currentTime);
+ if ((*fi)->getEndTime() >= currentTime) {
+ if (++allocated == poolSize)
+ break;
+ } // else the file's ring buffers will have been returned
+ }
+
+ releaseLock();
+}
+
+bool
+AudioFileReader::kick(bool wantLock)
+{
+ if (wantLock)
+ getLock();
+
+ RealTime now = m_driver->getSequencerTime();
+ const AudioPlayQueue *queue = m_driver->getAudioQueue();
+
+ bool someFilled = false;
+
+ // Tell files that are playing or will be playing in the next few
+ // seconds to update.
+
+ AudioPlayQueue::FileSet playing;
+
+ queue->getPlayingFiles
+ (now, RealTime(3, 0) + m_driver->getAudioReadBufferLength(), playing);
+
+ for (AudioPlayQueue::FileSet::iterator fi = playing.begin();
+ fi != playing.end(); ++fi) {
+
+ if (!(*fi)->isBuffered()) {
+ // fillBuffers has not been called on this file. This
+ // happens when a file is unmuted during playback. The
+ // results are unpredictable because we can no longer
+ // synchronise with the correct JACK callback slice at
+ // this point, but this is better than allowing the file
+ // to update from its start as would otherwise happen.
+ (*fi)->fillBuffers(now);
+ someFilled = true;
+ } else {
+ if ((*fi)->updateBuffers())
+ someFilled = true;
+ }
+ }
+
+ if (wantLock)
+ releaseLock();
+
+ return someFilled;
+}
+
+void
+AudioFileReader::threadRun()
+{
+ while (!m_exiting) {
+
+ // struct timeval now;
+ // gettimeofday(&now, 0);
+ // RealTime t = RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ bool someFilled = false;
+
+ if (m_driver->areClocksRunning()) {
+ someFilled = kick(false);
+ }
+
+ if (someFilled) {
+
+ releaseLock();
+ getLock();
+
+ } else {
+
+ RealTime bt = m_driver->getAudioReadBufferLength();
+ bt = bt / 2;
+ if (bt < RealTime(0, 10000000))
+ bt = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ RealTime t = bt + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+ }
+}
+
+
+
+AudioFileWriter::AudioFileWriter(SoundDriver *driver,
+ unsigned int sampleRate) :
+ AudioThread("AudioFileWriter", driver, sampleRate)
+{
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ for (InstrumentId id = instrumentBase;
+ id < instrumentBase + instrumentCount; ++id) {
+
+ // prefill with zero files in all slots, so that we can
+ // refer to the map without a lock (as the number of
+ // instruments won't change)
+
+ m_files[id] = FilePair(0, 0);
+ }
+}
+
+AudioFileWriter::~AudioFileWriter()
+{}
+
+
+bool
+AudioFileWriter::openRecordFile(InstrumentId id,
+ const std::string &fileName)
+{
+ getLock();
+
+ if (m_files[id].first) {
+ releaseLock();
+ std::cerr << "AudioFileWriter::openRecordFile: already have record file for instrument " << id << "!" << std::endl;
+ return false; // already have one
+ }
+
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::openRecordFile: instrument id is " << id << std::endl;
+#endif
+
+ MappedAudioFader *fader = m_driver->getMappedStudio()->getAudioFader(id);
+
+ RealTime bufferLength = m_driver->getAudioWriteBufferLength();
+ int bufferSamples = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+ bufferSamples = ((bufferSamples / 1024) + 1) * 1024;
+
+ if (fader) {
+ float fch = 2;
+ (void)fader->getProperty(MappedAudioFader::Channels, fch);
+ int channels = (int)fch;
+
+ RIFFAudioFile::SubFormat format = m_driver->getAudioRecFileFormat();
+
+ int bytesPerSample = (format == RIFFAudioFile::PCM ? 2 : 4) * channels;
+ int bitsPerSample = (format == RIFFAudioFile::PCM ? 16 : 32);
+
+ AudioFile *recordFile = 0;
+
+ try {
+ recordFile =
+ new WAVAudioFile(fileName,
+ channels, // channels
+ m_sampleRate, // samples per second
+ m_sampleRate *
+ bytesPerSample, // bytes per second
+ bytesPerSample, // bytes per frame
+ bitsPerSample); // bits per sample
+
+ // open the file for writing
+ //
+ if (!recordFile->write()) {
+ std::cerr << "AudioFileWriter::openRecordFile: failed to open " << fileName << " for writing" << std::endl;
+ delete recordFile;
+ releaseLock();
+ return false;
+ }
+ } catch (SoundFile::BadSoundFileException e) {
+ std::cerr << "AudioFileWriter::openRecordFile: failed to open " << fileName << " for writing: " << e.getMessage() << std::endl;
+ delete recordFile;
+ releaseLock();
+ return false;
+ }
+
+ RecordableAudioFile *raf = new RecordableAudioFile(recordFile,
+ bufferSamples);
+ m_files[id].second = raf;
+ m_files[id].first = recordFile;
+
+#ifdef DEBUG_WRITER
+
+ std::cerr << "AudioFileWriter::openRecordFile: created " << channels << "-channel file at " << fileName << " (id is " << recordFile->getId() << ")" << std::endl;
+#endif
+
+ releaseLock();
+ return true;
+ }
+
+ std::cerr << "AudioFileWriter::openRecordFile: no audio fader for record instrument " << id << "!" << std::endl;
+ releaseLock();
+ return false;
+}
+
+
+void
+AudioFileWriter::write(InstrumentId id,
+ const sample_t *samples,
+ int channel,
+ size_t sampleCount)
+{
+ if (!m_files[id].first)
+ return ; // no file
+ if (m_files[id].second->buffer(samples, channel, sampleCount) < sampleCount) {
+ m_driver->reportFailure(MappedEvent::FailureDiscOverrun);
+ }
+}
+
+bool
+AudioFileWriter::closeRecordFile(InstrumentId id, AudioFileId &returnedId)
+{
+ if (!m_files[id].first)
+ return false;
+
+ returnedId = m_files[id].first->getId();
+ m_files[id].second->setStatus(RecordableAudioFile::DEFUNCT);
+
+#ifdef DEBUG_WRITER
+
+ std::cerr << "AudioFileWriter::closeRecordFile: instrument " << id << " file set defunct (file ID is " << returnedId << ")" << std::endl;
+#endif
+
+ // Don't reset the file pointers here; that will be done in the
+ // next call to kick(). Doesn't really matter when that happens,
+ // but let's encourage it to happen soon just for certainty.
+ signal();
+
+ return true;
+}
+
+bool
+AudioFileWriter::haveRecordFileOpen(InstrumentId id)
+{
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ if (id < instrumentBase || id >= instrumentBase + instrumentCount) {
+ return false;
+ }
+
+ return (m_files[id].first &&
+ (m_files[id].second->getStatus() != RecordableAudioFile::DEFUNCT));
+}
+
+bool
+AudioFileWriter::haveRecordFilesOpen()
+{
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ for (InstrumentId id = instrumentBase; id < instrumentBase + instrumentCount; ++id) {
+
+ if (m_files[id].first &&
+ (m_files[id].second->getStatus() != RecordableAudioFile::DEFUNCT)) {
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::haveRecordFilesOpen: found open record file for instrument " << id << std::endl;
+#endif
+
+ return true;
+ }
+ }
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::haveRecordFilesOpen: nope" << std::endl;
+#endif
+
+ return false;
+}
+
+void
+AudioFileWriter::kick(bool wantLock)
+{
+ if (wantLock)
+ getLock();
+
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ for (InstrumentId id = instrumentBase;
+ id < instrumentBase + instrumentCount; ++id) {
+
+ if (!m_files[id].first)
+ continue;
+
+ RecordableAudioFile *raf = m_files[id].second;
+
+ if (raf->getStatus() == RecordableAudioFile::DEFUNCT) {
+
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::kick: found defunct file on instrument " << id << std::endl;
+#endif
+
+ m_files[id].first = 0;
+ delete raf; // also deletes the AudioFile
+ m_files[id].second = 0;
+
+ } else {
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::kick: writing file on instrument " << id << std::endl;
+#endif
+
+ raf->write();
+ }
+ }
+
+ if (wantLock)
+ releaseLock();
+}
+
+void
+AudioFileWriter::threadRun()
+{
+ while (!m_exiting) {
+
+ kick(false);
+
+ RealTime t = m_driver->getAudioWriteBufferLength();
+ t = t / 2;
+ if (t < RealTime(0, 10000000))
+ t = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ t = t + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+}
+
+
+}
+
diff --git a/src/sound/AudioProcess.h b/src/sound/AudioProcess.h
new file mode 100644
index 0000000..b517bc9
--- /dev/null
+++ b/src/sound/AudioProcess.h
@@ -0,0 +1,390 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIO_PROCESS_H_
+#define _AUDIO_PROCESS_H_
+
+#include "SoundDriver.h"
+#include "Instrument.h"
+#include "RealTime.h"
+#include "RingBuffer.h"
+#include "RunnablePluginInstance.h"
+#include "AudioPlayQueue.h"
+#include "RecordableAudioFile.h"
+
+namespace Rosegarden
+{
+
+class AudioThread
+{
+public:
+ typedef float sample_t;
+
+ AudioThread(std::string name, // for diagnostics
+ SoundDriver *driver,
+ unsigned int sampleRate);
+
+ virtual ~AudioThread();
+
+ // This is to be called by the owning class after construction.
+ void run();
+
+ // This is to be called by the owning class to cause the thread to
+ // exit and clean up, before destruction.
+ void terminate();
+
+ bool running() const { return m_running; }
+
+ int getLock();
+ int tryLock();
+ int releaseLock();
+ void signal();
+
+protected:
+ virtual void threadRun() = 0;
+ virtual int getPriority() { return 0; }
+
+ std::string m_name;
+
+ SoundDriver *m_driver;
+ unsigned int m_sampleRate;
+
+ pthread_t m_thread;
+ pthread_mutex_t m_lock;
+ pthread_cond_t m_condition;
+ bool m_running;
+ volatile bool m_exiting;
+
+private:
+ static void *staticThreadRun(void *arg);
+ static void staticThreadCleanup(void *arg);
+};
+
+
+class AudioInstrumentMixer;
+
+class AudioBussMixer : public AudioThread
+{
+public:
+ AudioBussMixer(SoundDriver *driver,
+ AudioInstrumentMixer *instrumentMixer,
+ unsigned int sampleRate,
+ unsigned int blockSize);
+
+ virtual ~AudioBussMixer();
+
+ void kick(bool wantLock = true, bool signalInstrumentMixer = true);
+
+ /**
+ * Prebuffer. This should be called only when the transport is
+ * not running. This also calls fillBuffers on the instrument
+ * mixer.
+ */
+ void fillBuffers(const RealTime &currentTime);
+
+ /**
+ * Empty and discard buffer contents.
+ */
+ void emptyBuffers();
+
+ int getBussCount() {
+ return m_bussCount;
+ }
+
+ /**
+ * A buss is "dormant" if every readable sample on every one of
+ * its buffers is zero. It can therefore be safely skipped during
+ * playback.
+ */
+ bool isBussDormant(int buss) {
+ return m_bufferMap[buss].dormant;
+ }
+
+ /**
+ * Busses are currently always stereo.
+ */
+ RingBuffer<sample_t> *getRingBuffer(int buss, unsigned int channel) {
+ if (channel < m_bufferMap[buss].buffers.size()) {
+ return m_bufferMap[buss].buffers[channel];
+ } else {
+ return 0;
+ }
+ }
+
+ /// For call from MappedStudio. Pan is in range -100.0 -> 100.0
+ void setBussLevels(int buss, float dB, float pan);
+
+ /// For call regularly from anywhere in a non-RT thread
+ void updateInstrumentConnections();
+
+protected:
+ virtual void threadRun();
+
+ void processBlocks();
+ void generateBuffers();
+
+ AudioInstrumentMixer *m_instrumentMixer;
+ unsigned int m_blockSize;
+ int m_bussCount;
+
+ std::vector<sample_t *> m_processBuffers;
+
+ struct BufferRec
+ {
+ BufferRec() : dormant(true), buffers(), instruments(),
+ gainLeft(0.0), gainRight(0.0) { }
+ ~BufferRec();
+
+ bool dormant;
+
+ std::vector<RingBuffer<sample_t> *> buffers;
+ std::vector<bool> instruments; // index is instrument id minus base
+
+ float gainLeft;
+ float gainRight;
+ };
+
+ typedef std::map<int, BufferRec> BufferMap;
+ BufferMap m_bufferMap;
+};
+
+
+class AudioFileReader;
+class AudioFileWriter;
+
+class AudioInstrumentMixer : public AudioThread
+{
+public:
+ typedef std::vector<RunnablePluginInstance *> PluginList;
+ typedef std::map<InstrumentId, PluginList> PluginMap;
+ typedef std::map<InstrumentId, RunnablePluginInstance *> SynthPluginMap;
+
+ AudioInstrumentMixer(SoundDriver *driver,
+ AudioFileReader *fileReader,
+ unsigned int sampleRate,
+ unsigned int blockSize);
+
+ virtual ~AudioInstrumentMixer();
+
+ void kick(bool wantLock = true);
+
+ void setBussMixer(AudioBussMixer *mixer) { m_bussMixer = mixer; }
+
+ void setPlugin(InstrumentId id, int position, QString identifier);
+ void removePlugin(InstrumentId id, int position);
+ void removeAllPlugins();
+
+ void setPluginPortValue(InstrumentId id, int position,
+ unsigned int port, float value);
+ float getPluginPortValue(InstrumentId id, int position,
+ unsigned int port);
+
+ void setPluginBypass(InstrumentId, int position, bool bypass);
+
+ QStringList getPluginPrograms(InstrumentId, int);
+ QString getPluginProgram(InstrumentId, int);
+ QString getPluginProgram(InstrumentId, int, int, int);
+ unsigned long getPluginProgram(InstrumentId, int, QString);
+ void setPluginProgram(InstrumentId, int, QString);
+
+ QString configurePlugin(InstrumentId, int, QString, QString);
+
+ void resetAllPlugins(bool discardEvents = false);
+ void discardPluginEvents();
+ void destroyAllPlugins();
+
+ RunnablePluginInstance *getSynthPlugin(InstrumentId id) { return m_synths[id]; }
+
+ /**
+ * Return the plugins intended for a particular buss. (By coincidence,
+ * this will also work for instruments, but it's not to be relied on.)
+ * It's purely by historical accident that the instrument mixer happens
+ * to hold buss plugins as well -- this could do with being refactored.
+ */
+ PluginList &getBussPlugins(unsigned int bussId) { return m_plugins[bussId]; }
+
+ /**
+ * Return the total of the plugin latencies for a given instrument
+ * or buss id.
+ */
+ size_t getPluginLatency(unsigned int id);
+
+ /**
+ * Prebuffer. This should be called only when the transport is
+ * not running.
+ */
+ void fillBuffers(const RealTime &currentTime);
+
+ /**
+ * Ensure plugins etc have enough buffers. This is also done by
+ * fillBuffers and only needs to be called here if the extra work
+ * involved in fillBuffers is not desirable.
+ */
+ void allocateBuffers();
+
+ /**
+ * Empty and discard buffer contents.
+ */
+ void emptyBuffers(RealTime currentTime = RealTime::zeroTime);
+
+ /**
+ * An instrument is "empty" if it has no audio files, synths or
+ * plugins assigned to it, and so cannot generate sound. Empty
+ * instruments can safely be ignored during playback.
+ */
+ bool isInstrumentEmpty(InstrumentId id) {
+ return m_bufferMap[id].empty;
+ }
+
+ /**
+ * An instrument is "dormant" if every readable sample on every
+ * one of its buffers is zero. Dormant instruments can safely be
+ * skipped rather than mixed during playback, but they should not
+ * be ignored (unless also empty).
+ */
+ bool isInstrumentDormant(InstrumentId id) {
+ return m_bufferMap[id].dormant;
+ }
+
+ /**
+ * We always have at least two channels (and hence buffers) by
+ * this point, because even on a mono instrument we still have a
+ * Pan setting which will have been applied by the time we get to
+ * these buffers.
+ */
+ RingBuffer<sample_t, 2> *getRingBuffer(InstrumentId id, unsigned int channel) {
+ if (channel < m_bufferMap[id].buffers.size()) {
+ return m_bufferMap[id].buffers[channel];
+ } else {
+ return 0;
+ }
+ }
+
+ /// For call from MappedStudio. Pan is in range -100.0 -> 100.0
+ void setInstrumentLevels(InstrumentId instrument, float dB, float pan);
+
+ /// For call regularly from anywhere in a non-RT thread
+ void updateInstrumentMuteStates();
+
+protected:
+ virtual void threadRun();
+
+ virtual int getPriority() { return 3; }
+
+ void processBlocks(bool &readSomething);
+ void processEmptyBlocks(InstrumentId id);
+ bool processBlock(InstrumentId id, PlayableAudioFile **, size_t, bool &readSomething);
+ void generateBuffers();
+
+ AudioFileReader *m_fileReader;
+ AudioBussMixer *m_bussMixer;
+ unsigned int m_blockSize;
+
+ // The plugin data structures will all be pre-sized and so of
+ // fixed size during normal run time; this will allow us to add
+ // and edit plugins without locking.
+ RunnablePluginInstance *getPluginInstance(InstrumentId, int);
+ PluginMap m_plugins;
+ SynthPluginMap m_synths;
+
+ // maintain the same number of these as the maximum number of
+ // channels on any audio instrument
+ std::vector<sample_t *> m_processBuffers;
+
+ struct BufferRec
+ {
+ BufferRec() : empty(true), dormant(true), zeroFrames(0),
+ filledTo(RealTime::zeroTime), channels(2),
+ buffers(), gainLeft(0.0), gainRight(0.0), volume(0.0),
+ muted(false) { }
+ ~BufferRec();
+
+ bool empty;
+ bool dormant;
+ size_t zeroFrames;
+
+ RealTime filledTo;
+ size_t channels;
+ std::vector<RingBuffer<sample_t, 2> *> buffers;
+
+ float gainLeft;
+ float gainRight;
+ float volume;
+ bool muted;
+ };
+
+ typedef std::map<InstrumentId, BufferRec> BufferMap;
+ BufferMap m_bufferMap;
+};
+
+
+class AudioFileReader : public AudioThread
+{
+public:
+ AudioFileReader(SoundDriver *driver,
+ unsigned int sampleRate);
+
+ virtual ~AudioFileReader();
+
+ bool kick(bool wantLock = true);
+
+ /**
+ * Prebuffer. This should be called only when the transport is
+ * not running.
+ */
+ void fillBuffers(const RealTime &currentTime);
+
+protected:
+ virtual void threadRun();
+};
+
+
+class AudioFileWriter : public AudioThread
+{
+public:
+ AudioFileWriter(SoundDriver *driver,
+ unsigned int sampleRate);
+
+ virtual ~AudioFileWriter();
+
+ void kick(bool wantLock = true);
+
+ bool openRecordFile(InstrumentId id, const std::string &fileName);
+ bool closeRecordFile(InstrumentId id, AudioFileId &returnedId);
+
+ bool haveRecordFileOpen(InstrumentId id);
+ bool haveRecordFilesOpen();
+
+ void write(InstrumentId id, const sample_t *, int channel, size_t samples);
+
+protected:
+ virtual void threadRun();
+
+ typedef std::pair<AudioFile *, RecordableAudioFile *> FilePair;
+ typedef std::map<InstrumentId, FilePair> FileMap;
+ FileMap m_files;
+};
+
+
+}
+
+#endif
+
diff --git a/src/sound/AudioTimeStretcher.cpp b/src/sound/AudioTimeStretcher.cpp
new file mode 100644
index 0000000..392693e
--- /dev/null
+++ b/src/sound/AudioTimeStretcher.cpp
@@ -0,0 +1,667 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Sonic Visualiser
+ An audio file viewer and annotation editor.
+ Centre for Digital Music, Queen Mary, University of London.
+ This file copyright 2006 Chris Cannam and QMUL.
+
+ 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 "AudioTimeStretcher.h"
+
+#include <iostream>
+#include <fstream>
+#include <cassert>
+#include <cstring>
+
+namespace Rosegarden
+{
+
+static double mod(double x, double y) { return x - (y * floor(x / y)); }
+static float modf(float x, float y) { return x - (y * floorf(x / y)); }
+
+static double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; }
+static float princargf(float a) { return modf(a + M_PI, -2 * M_PI) + M_PI; }
+
+
+//#define DEBUG_AUDIO_TIME_STRETCHER 1
+
+AudioTimeStretcher::AudioTimeStretcher(size_t sampleRate,
+ size_t channels,
+ float ratio,
+ bool sharpen,
+ size_t maxOutputBlockSize) :
+ m_sampleRate(sampleRate),
+ m_channels(channels),
+ m_maxOutputBlockSize(maxOutputBlockSize),
+ m_ratio(ratio),
+ m_sharpen(sharpen),
+ m_totalCount(0),
+ m_transientCount(0),
+ m_n2sum(0),
+ m_n2total(0),
+ m_adjustCount(50)
+{
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_mutex, &initialisingMutex, sizeof(pthread_mutex_t));
+
+ initialise();
+}
+
+AudioTimeStretcher::~AudioTimeStretcher()
+{
+ std::cerr << "AudioTimeStretcher::~AudioTimeStretcher" << std::endl;
+
+ std::cerr << "AudioTimeStretcher::~AudioTimeStretcher: actual ratio = " << (m_totalCount > 0 ? (float (m_n2total) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ", nominal = " << getRatio() << ")" << std::endl;
+
+ cleanup();
+
+ pthread_mutex_destroy(&m_mutex);
+}
+
+void
+AudioTimeStretcher::initialise()
+{
+ std::cerr << "AudioTimeStretcher::initialise" << std::endl;
+
+ calculateParameters();
+
+ m_analysisWindow = new SampleWindow<float>(SampleWindow<float>::Hanning, m_wlen);
+ m_synthesisWindow = new SampleWindow<float>(SampleWindow<float>::Hanning, m_wlen);
+
+ m_prevPhase = new float *[m_channels];
+ m_prevAdjustedPhase = new float *[m_channels];
+
+ m_prevTransientMag = (float *)fftwf_malloc(sizeof(float) * (m_wlen / 2 + 1));
+ m_prevTransientScore = 0;
+ m_prevTransient = false;
+
+ m_tempbuf = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+
+ m_time = new float *[m_channels];
+ m_freq = new fftwf_complex *[m_channels];
+ m_plan = new fftwf_plan[m_channels];
+ m_iplan = new fftwf_plan[m_channels];
+
+ m_inbuf = new RingBuffer<float> *[m_channels];
+ m_outbuf = new RingBuffer<float> *[m_channels];
+ m_mashbuf = new float *[m_channels];
+
+ m_modulationbuf = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ m_prevPhase[c] = (float *)fftwf_malloc(sizeof(float) * (m_wlen / 2 + 1));
+ m_prevAdjustedPhase[c] = (float *)fftwf_malloc(sizeof(float) * (m_wlen / 2 + 1));
+
+ m_time[c] = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+ m_freq[c] = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) *
+ (m_wlen / 2 + 1));
+
+ m_plan[c] = fftwf_plan_dft_r2c_1d(m_wlen, m_time[c], m_freq[c], FFTW_ESTIMATE);
+ m_iplan[c] = fftwf_plan_dft_c2r_1d(m_wlen, m_freq[c], m_time[c], FFTW_ESTIMATE);
+
+ m_outbuf[c] = new RingBuffer<float>
+ ((m_maxOutputBlockSize + m_wlen) * 2);
+ m_inbuf[c] = new RingBuffer<float>
+ (lrintf(m_outbuf[c]->getSize() / m_ratio) + m_wlen);
+
+ std::cerr << "making inbuf size " << m_inbuf[c]->getSize() << " (outbuf size is " << m_outbuf[c]->getSize() << ", ratio " << m_ratio << ")" << std::endl;
+
+
+ m_mashbuf[c] = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ m_mashbuf[c][i] = 0.0;
+ }
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+ m_prevPhase[c][i] = 0.0;
+ m_prevAdjustedPhase[c][i] = 0.0;
+ }
+ }
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ m_modulationbuf[i] = 0.0;
+ }
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+ m_prevTransientMag[i] = 0.0;
+ }
+}
+
+void
+AudioTimeStretcher::calculateParameters()
+{
+ std::cerr << "AudioTimeStretcher::calculateParameters" << std::endl;
+
+ m_wlen = 1024;
+
+ //!!! In transient sharpening mode, we need to pick the window
+ //length so as to be more or less fixed in audio duration (i.e. we
+ //need to exploit the sample rate)
+
+ //!!! have to work out the relationship between wlen and transient
+ //threshold
+
+ if (m_ratio < 1) {
+ if (m_ratio < 0.4) {
+ m_n1 = 1024;
+ m_wlen = 2048;
+ } else if (m_ratio < 0.8) {
+ m_n1 = 512;
+ } else {
+ m_n1 = 256;
+ }
+ if (shouldSharpen()) {
+ m_wlen = 2048;
+ }
+ m_n2 = lrintf(m_n1 * m_ratio);
+ } else {
+ if (m_ratio > 2) {
+ m_n2 = 512;
+ m_wlen = 4096;
+ } else if (m_ratio > 1.6) {
+ m_n2 = 384;
+ m_wlen = 2048;
+ } else {
+ m_n2 = 256;
+ }
+ if (shouldSharpen()) {
+ if (m_wlen < 2048) m_wlen = 2048;
+ }
+ m_n1 = lrintf(m_n2 / m_ratio);
+ if (m_n1 == 0) {
+ m_n1 = 1;
+ m_n2 = m_ratio;
+ }
+ }
+
+ m_transientThreshold = lrintf(m_wlen / 4.5);
+
+ m_totalCount = 0;
+ m_transientCount = 0;
+ m_n2sum = 0;
+ m_n2total = 0;
+ m_n2list.clear();
+
+ std::cerr << "AudioTimeStretcher: channels = " << m_channels
+ << ", ratio = " << m_ratio
+ << ", n1 = " << m_n1 << ", n2 = " << m_n2 << ", wlen = "
+ << m_wlen << ", max = " << m_maxOutputBlockSize << std::endl;
+// << ", outbuflen = " << m_outbuf[0]->getSize() << std::endl;
+}
+
+void
+AudioTimeStretcher::cleanup()
+{
+ std::cerr << "AudioTimeStretcher::cleanup" << std::endl;
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ fftwf_destroy_plan(m_plan[c]);
+ fftwf_destroy_plan(m_iplan[c]);
+
+ fftwf_free(m_time[c]);
+ fftwf_free(m_freq[c]);
+
+ fftwf_free(m_mashbuf[c]);
+ fftwf_free(m_prevPhase[c]);
+ fftwf_free(m_prevAdjustedPhase[c]);
+
+ delete m_inbuf[c];
+ delete m_outbuf[c];
+ }
+
+ fftwf_free(m_tempbuf);
+ fftwf_free(m_modulationbuf);
+ fftwf_free(m_prevTransientMag);
+
+ delete[] m_prevPhase;
+ delete[] m_prevAdjustedPhase;
+ delete[] m_inbuf;
+ delete[] m_outbuf;
+ delete[] m_mashbuf;
+ delete[] m_time;
+ delete[] m_freq;
+ delete[] m_plan;
+ delete[] m_iplan;
+
+ delete m_analysisWindow;
+ delete m_synthesisWindow;
+}
+
+void
+AudioTimeStretcher::setRatio(float ratio)
+{
+ pthread_mutex_lock(&m_mutex);
+
+ size_t formerWlen = m_wlen;
+ m_ratio = ratio;
+
+ std::cerr << "AudioTimeStretcher::setRatio: new ratio " << ratio
+ << std::endl;
+
+ calculateParameters();
+
+ if (m_wlen == formerWlen) {
+
+ // This is the only container whose size depends on m_ratio
+
+ RingBuffer<float> **newin = new RingBuffer<float> *[m_channels];
+
+ size_t formerSize = m_inbuf[0]->getSize();
+ size_t newSize = lrintf(m_outbuf[0]->getSize() / m_ratio) + m_wlen;
+
+ std::cerr << "resizing inbuf from " << formerSize << " to "
+ << newSize << " (outbuf size is " << m_outbuf[0]->getSize() << ", ratio " << m_ratio << ")" << std::endl;
+
+ if (formerSize != newSize) {
+
+ size_t ready = m_inbuf[0]->getReadSpace();
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ newin[c] = new RingBuffer<float>(newSize);
+ }
+
+ if (ready > 0) {
+
+ size_t copy = std::min(ready, newSize);
+ float *tmp = new float[ready];
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ m_inbuf[c]->read(tmp, ready);
+ newin[c]->write(tmp + ready - copy, copy);
+ }
+
+ delete[] tmp;
+ }
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ delete m_inbuf[c];
+ }
+
+ delete[] m_inbuf;
+ m_inbuf = newin;
+ }
+
+ } else {
+
+ std::cerr << "wlen changed" << std::endl;
+ cleanup();
+ initialise();
+ }
+
+ pthread_mutex_unlock(&m_mutex);
+}
+
+size_t
+AudioTimeStretcher::getProcessingLatency() const
+{
+ return getWindowSize() - getInputIncrement();
+}
+
+size_t
+AudioTimeStretcher::getRequiredInputSamples() const
+{
+ size_t rv;
+ pthread_mutex_lock(&m_mutex);
+
+ if (m_inbuf[0]->getReadSpace() >= m_wlen) rv = 0;
+ else rv = m_wlen - m_inbuf[0]->getReadSpace();
+
+ pthread_mutex_unlock(&m_mutex);
+ return rv;
+}
+
+void
+AudioTimeStretcher::putInput(float **input, size_t samples)
+{
+ pthread_mutex_lock(&m_mutex);
+
+ // We need to add samples from input to our internal buffer. When
+ // we have m_windowSize samples in the buffer, we can process it,
+ // move the samples back by m_n1 and write the output onto our
+ // internal output buffer. If we have (samples * ratio) samples
+ // in that, we can write m_n2 of them back to output and return
+ // (otherwise we have to write zeroes).
+
+ // When we process, we write m_wlen to our fixed output buffer
+ // (m_mashbuf). We then pull out the first m_n2 samples from that
+ // buffer, push them into the output ring buffer, and shift
+ // m_mashbuf left by that amount.
+
+ // The processing latency is then m_wlen - m_n2.
+
+ size_t consumed = 0;
+
+ while (consumed < samples) {
+
+ size_t writable = m_inbuf[0]->getWriteSpace();
+ writable = std::min(writable, samples - consumed);
+
+ if (writable == 0) {
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "WARNING: AudioTimeStretcher::putInput: writable == 0 (inbuf has " << m_inbuf[0]->getReadSpace() << " samples available for reading, space for " << m_inbuf[0]->getWriteSpace() << " more)" << std::endl;
+#endif
+ if (m_inbuf[0]->getReadSpace() < m_wlen ||
+ m_outbuf[0]->getWriteSpace() < m_n2) {
+ std::cerr << "WARNING: AudioTimeStretcher::putInput: Inbuf has " << m_inbuf[0]->getReadSpace() << ", outbuf has space for " << m_outbuf[0]->getWriteSpace() << " (n2 = " << m_n2 << ", wlen = " << m_wlen << "), won't be able to process" << std::endl;
+ break;
+ }
+ } else {
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "writing " << writable << " from index " << consumed << " to inbuf, consumed will be " << consumed + writable << std::endl;
+#endif
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ m_inbuf[c]->write(input[c] + consumed, writable);
+ }
+ consumed += writable;
+ }
+
+ while (m_inbuf[0]->getReadSpace() >= m_wlen &&
+ m_outbuf[0]->getWriteSpace() >= m_n2) {
+
+ // We know we have at least m_wlen samples available
+ // in m_inbuf. We need to peek m_wlen of them for
+ // processing, and then read m_n1 to advance the read
+ // pointer.
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ size_t got = m_inbuf[c]->peek(m_tempbuf, m_wlen);
+ assert(got == m_wlen);
+
+ analyseBlock(c, m_tempbuf);
+ }
+
+ bool transient = false;
+ if (shouldSharpen()) transient = isTransient();
+
+ size_t n2 = m_n2;
+
+ if (transient) {
+ n2 = m_n1;
+ }
+
+ ++m_totalCount;
+ if (transient) ++m_transientCount;
+
+ m_n2sum += n2;
+ m_n2total += n2;
+
+ if (m_totalCount > 50 && m_transientCount < m_totalCount) {
+
+ int fixed = m_transientCount * m_n1;
+
+ float idealTotal = m_totalCount * m_n1 * m_ratio;
+ float idealSquashy = idealTotal - fixed;
+
+ float squashyCount = m_totalCount - m_transientCount;
+
+ float fn2 = idealSquashy / squashyCount;
+
+ n2 = int(fn2);
+
+ float remainder = fn2 - n2;
+ if (drand48() < remainder) ++n2;
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ if (n2 != m_n2) {
+ std::cerr << m_n2 << " -> " << n2 << " (ideal = " << (idealSquashy / squashyCount) << ")" << std::endl;
+ }
+#endif
+ }
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ synthesiseBlock(c, m_mashbuf[c],
+ c == 0 ? m_modulationbuf : 0,
+ m_prevTransient ? m_n1 : m_n2);
+
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "writing first " << m_n2 << " from mashbuf, skipping " << m_n1 << " on inbuf " << std::endl;
+#endif
+ m_inbuf[c]->skip(m_n1);
+
+ for (size_t i = 0; i < n2; ++i) {
+ if (m_modulationbuf[i] > 0.f) {
+ m_mashbuf[c][i] /= m_modulationbuf[i];
+ }
+ }
+
+ m_outbuf[c]->write(m_mashbuf[c], n2);
+
+ for (size_t i = 0; i < m_wlen - n2; ++i) {
+ m_mashbuf[c][i] = m_mashbuf[c][i + n2];
+ }
+
+ for (size_t i = m_wlen - n2; i < m_wlen; ++i) {
+ m_mashbuf[c][i] = 0.0f;
+ }
+ }
+
+ m_prevTransient = transient;
+
+ for (size_t i = 0; i < m_wlen - n2; ++i) {
+ m_modulationbuf[i] = m_modulationbuf[i + n2];
+ }
+
+ for (size_t i = m_wlen - n2; i < m_wlen; ++i) {
+ m_modulationbuf[i] = 0.0f;
+ }
+
+ if (!transient) m_n2 = n2;
+ }
+
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "loop ended: inbuf read space " << m_inbuf[0]->getReadSpace() << ", outbuf write space " << m_outbuf[0]->getWriteSpace() << std::endl;
+#endif
+ }
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "AudioTimeStretcher::putInput returning" << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_mutex);
+
+// std::cerr << "ratio: nominal: " << getRatio() << " actual: "
+// << m_total2 << "/" << m_total1 << " = " << float(m_total2) / float(m_total1) << " ideal: " << m_ratio << std::endl;
+}
+
+size_t
+AudioTimeStretcher::getAvailableOutputSamples() const
+{
+ pthread_mutex_lock(&m_mutex);
+
+ size_t rv = m_outbuf[0]->getReadSpace();
+
+ pthread_mutex_unlock(&m_mutex);
+ return rv;
+}
+
+void
+AudioTimeStretcher::getOutput(float **output, size_t samples)
+{
+ pthread_mutex_lock(&m_mutex);
+
+ if (m_outbuf[0]->getReadSpace() < samples) {
+ std::cerr << "WARNING: AudioTimeStretcher::getOutput: not enough data (yet?) (" << m_outbuf[0]->getReadSpace() << " < " << samples << ")" << std::endl;
+ size_t fill = samples - m_outbuf[0]->getReadSpace();
+ for (size_t c = 0; c < m_channels; ++c) {
+ for (size_t i = 0; i < fill; ++i) {
+ output[c][i] = 0.0;
+ }
+ m_outbuf[c]->read(output[c] + fill, m_outbuf[c]->getReadSpace());
+ }
+ } else {
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "enough data - writing " << samples << " from outbuf" << std::endl;
+#endif
+ for (size_t c = 0; c < m_channels; ++c) {
+ m_outbuf[c]->read(output[c], samples);
+ }
+ }
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "AudioTimeStretcher::getOutput returning" << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_mutex);
+}
+
+void
+AudioTimeStretcher::analyseBlock(size_t c, float *buf)
+{
+ size_t i;
+
+ // buf contains m_wlen samples
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "AudioTimeStretcher::analyseBlock (channel " << c << ")" << std::endl;
+#endif
+
+ m_analysisWindow->cut(buf);
+
+ for (i = 0; i < m_wlen/2; ++i) {
+ float temp = buf[i];
+ buf[i] = buf[i + m_wlen/2];
+ buf[i + m_wlen/2] = temp;
+ }
+
+ for (i = 0; i < m_wlen; ++i) {
+ m_time[c][i] = buf[i];
+ }
+
+ fftwf_execute(m_plan[c]); // m_time -> m_freq
+}
+
+bool
+AudioTimeStretcher::isTransient()
+{
+ int count = 0;
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+
+ float real = 0.f, imag = 0.f;
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ real += m_freq[c][i][0];
+ imag += m_freq[c][i][1];
+ }
+
+ float sqrmag = (real * real + imag * imag);
+
+ if (m_prevTransientMag[i] > 0.f) {
+ float diff = 10.f * log10f(sqrmag / m_prevTransientMag[i]);
+ if (diff > 3.f) ++count;
+ }
+
+ m_prevTransientMag[i] = sqrmag;
+ }
+
+ bool isTransient = false;
+
+// if (count > m_transientThreshold &&
+// count > m_prevTransientScore * 1.2) {
+ if (count > m_prevTransientScore &&
+ count > m_transientThreshold &&
+ count - m_prevTransientScore > m_wlen / 20) {
+ isTransient = true;
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "isTransient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ", ratio = " << (m_totalCount > 0 ? (float (m_n2sum) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ", nominal = " << getRatio() << ")" << std::endl;
+// } else {
+// std::cerr << " !transient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ")" << std::endl;
+#endif
+ }
+
+ m_prevTransientScore = count;
+
+ return isTransient;
+}
+
+void
+AudioTimeStretcher::synthesiseBlock(size_t c,
+ float *out,
+ float *modulation,
+ size_t lastStep)
+{
+ bool unchanged = (lastStep == m_n1);
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+
+ float phase = princargf(atan2f(m_freq[c][i][1], m_freq[c][i][0]));
+ float adjustedPhase = phase;
+
+// float binfreq = float(m_sampleRate * i) / m_wlen;
+
+ if (!unchanged) {
+
+ float mag = sqrtf(m_freq[c][i][0] * m_freq[c][i][0] +
+ m_freq[c][i][1] * m_freq[c][i][1]);
+
+ float omega = (2 * M_PI * m_n1 * i) / m_wlen;
+
+ float expectedPhase = m_prevPhase[c][i] + omega;
+
+ float phaseError = princargf(phase - expectedPhase);
+
+ float phaseIncrement = (omega + phaseError) / m_n1;
+
+ adjustedPhase = m_prevAdjustedPhase[c][i] +
+ lastStep * phaseIncrement;
+
+ float real = mag * cosf(adjustedPhase);
+ float imag = mag * sinf(adjustedPhase);
+ m_freq[c][i][0] = real;
+ m_freq[c][i][1] = imag;
+ }
+
+ m_prevPhase[c][i] = phase;
+ m_prevAdjustedPhase[c][i] = adjustedPhase;
+ }
+
+ fftwf_execute(m_iplan[c]); // m_freq -> m_time, inverse fft
+
+ for (size_t i = 0; i < m_wlen/2; ++i) {
+ float temp = m_time[c][i];
+ m_time[c][i] = m_time[c][i + m_wlen/2];
+ m_time[c][i + m_wlen/2] = temp;
+ }
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ m_time[c][i] = m_time[c][i] / m_wlen;
+ }
+
+ m_synthesisWindow->cut(m_time[c]);
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ out[i] += m_time[c][i];
+ }
+
+ if (modulation) {
+
+ float area = m_analysisWindow->getArea();
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ float val = m_synthesisWindow->getValue(i);
+ modulation[i] += val * area;
+ }
+ }
+}
+
+
+
+}
+
diff --git a/src/sound/AudioTimeStretcher.h b/src/sound/AudioTimeStretcher.h
new file mode 100644
index 0000000..c5d0170
--- /dev/null
+++ b/src/sound/AudioTimeStretcher.h
@@ -0,0 +1,221 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ 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.
+*/
+
+/*
+ This file is derived from
+
+ Sonic Visualiser
+ An audio file viewer and annotation editor.
+ Centre for Digital Music, Queen Mary, University of London.
+ This file copyright 2006 Chris Cannam and QMUL.
+
+ 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_TIME_STRETCHER_H_
+#define _AUDIO_TIME_STRETCHER_H_
+
+#include "SampleWindow.h"
+#include "RingBuffer.h"
+
+#include <fftw3.h>
+#include <pthread.h>
+#include <list>
+
+namespace Rosegarden
+{
+
+/**
+ * A time stretcher that alters the performance speed of audio,
+ * preserving pitch.
+ *
+ * This is based on the straightforward phase vocoder with phase
+ * unwrapping (as in e.g. the DAFX book pp275-), with optional
+ * percussive transient detection to avoid smearing percussive notes
+ * and resynchronise phases, and adding a stream API for real-time
+ * use. Principles and methods from Chris Duxbury, AES 2002 and 2004
+ * thesis; Emmanuel Ravelli, DAFX 2005; Dan Barry, ISSC 2005 on
+ * percussion detection; code by Chris Cannam.
+ */
+
+class AudioTimeStretcher
+{
+public:
+ AudioTimeStretcher(size_t sampleRate,
+ size_t channels,
+ float ratio,
+ bool sharpen,
+ size_t maxOutputBlockSize);
+ virtual ~AudioTimeStretcher();
+
+ /**
+ * Return the number of samples that would need to be added via
+ * putInput in order to provoke the time stretcher into doing some
+ * time stretching and making more output samples available.
+ * This will be an estimate, if transient sharpening is on; the
+ * caller may need to do the put/get/test cycle more than once.
+ */
+ size_t getRequiredInputSamples() const;
+
+ /**
+ * Put (and possibly process) a given number of input samples.
+ * Number should usually equal the value returned from
+ * getRequiredInputSamples().
+ */
+ void putInput(float **input, size_t samples);
+
+ /**
+ * Get the number of processed samples ready for reading.
+ */
+ size_t getAvailableOutputSamples() const;
+
+ /**
+ * Get some processed samples.
+ */
+ void getOutput(float **output, size_t samples);
+
+ //!!! and reset?
+
+ /**
+ * Change the time stretch ratio.
+ */
+ void setRatio(float ratio);
+
+ /**
+ * Get the hop size for input.
+ */
+ size_t getInputIncrement() const { return m_n1; }
+
+ /**
+ * Get the hop size for output.
+ */
+ size_t getOutputIncrement() const { return m_n2; }
+
+ /**
+ * Get the window size for FFT processing.
+ */
+ size_t getWindowSize() const { return m_wlen; }
+
+ /**
+ * Get the stretch ratio.
+ */
+ float getRatio() const { return float(m_n2) / float(m_n1); }
+
+ /**
+ * Return whether this time stretcher will attempt to sharpen transients.
+ */
+ bool getSharpening() const { return m_sharpen; }
+
+ /**
+ * Return the number of channels for this time stretcher.
+ */
+ size_t getChannelCount() const { return m_channels; }
+
+ /**
+ * Get the latency added by the time stretcher, in sample frames.
+ * This will be exact if transient sharpening is off, or approximate
+ * if it is on.
+ */
+ size_t getProcessingLatency() const;
+
+protected:
+ /**
+ * Process a single phase vocoder frame from "in" into
+ * m_freq[channel].
+ */
+ void analyseBlock(size_t channel, float *in); // into m_freq[channel]
+
+ /**
+ * Examine m_freq[0..m_channels-1] and return whether a percussive
+ * transient is found.
+ */
+ bool isTransient();
+
+ /**
+ * Resynthesise from m_freq[channel] adding in to "out",
+ * adjusting phases on the basis of a prior step size of lastStep.
+ * Also add the window shape in to the modulation array (if
+ * present) -- for use in ensuring the output has the correct
+ * magnitude afterwards.
+ */
+ void synthesiseBlock(size_t channel, float *out, float *modulation,
+ size_t lastStep);
+
+ void initialise();
+ void calculateParameters();
+ void cleanup();
+
+ bool shouldSharpen() {
+ return m_sharpen && (m_ratio > 0.25);
+ }
+
+ size_t m_sampleRate;
+ size_t m_channels;
+ size_t m_maxOutputBlockSize;
+ float m_ratio;
+ bool m_sharpen;
+ size_t m_n1;
+ size_t m_n2;
+ size_t m_wlen;
+ SampleWindow<float> *m_analysisWindow;
+ SampleWindow<float> *m_synthesisWindow;
+
+ int m_totalCount;
+ int m_transientCount;
+
+ int m_n2sum;
+ int m_n2total;
+ std::list<int> m_n2list;
+ int m_adjustCount;
+
+ float **m_prevPhase;
+ float **m_prevAdjustedPhase;
+
+ float *m_prevTransientMag;
+ int m_prevTransientScore;
+ int m_transientThreshold;
+ bool m_prevTransient;
+
+ float *m_tempbuf;
+ float **m_time;
+ fftwf_complex **m_freq;
+ fftwf_plan *m_plan;
+ fftwf_plan *m_iplan;
+
+ RingBuffer<float> **m_inbuf;
+ RingBuffer<float> **m_outbuf;
+ float **m_mashbuf;
+ float *m_modulationbuf;
+
+ mutable pthread_mutex_t m_mutex;
+};
+
+}
+
+
+#endif
diff --git a/src/sound/Audit.cpp b/src/sound/Audit.cpp
new file mode 100644
index 0000000..25a6c8b
--- /dev/null
+++ b/src/sound/Audit.cpp
@@ -0,0 +1,30 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "Audit.h"
+
+namespace Rosegarden
+{
+
+std::string Audit::m_audit;
+
+}
+
diff --git a/src/sound/Audit.h b/src/sound/Audit.h
new file mode 100644
index 0000000..4e0a20e
--- /dev/null
+++ b/src/sound/Audit.h
@@ -0,0 +1,60 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIT_H_
+#define _AUDIT_H_
+
+#include <string>
+#include <iostream>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+// A staggeringly simple-minded audit trail implementation.
+
+namespace Rosegarden {
+
+class Audit : public std::stringstream
+{
+public:
+ Audit() { }
+
+ virtual ~Audit() {
+#if (__GNUC__ < 3)
+ *this << std::ends;
+#endif
+ std::cerr << str();
+ m_audit += str();
+ }
+
+ static std::string getAudit() { return m_audit; }
+
+protected:
+ static std::string m_audit;
+};
+
+}
+
+#endif
diff --git a/src/sound/BWFAudioFile.cpp b/src/sound/BWFAudioFile.cpp
new file mode 100644
index 0000000..c38820f
--- /dev/null
+++ b/src/sound/BWFAudioFile.cpp
@@ -0,0 +1,171 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "BWFAudioFile.h"
+#include "RealTime.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+
+namespace Rosegarden
+{
+
+BWFAudioFile::BWFAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName):
+ RIFFAudioFile(id, name, fileName)
+{
+ m_type = WAV;
+
+}
+
+BWFAudioFile::BWFAudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bytesPerSecond = 6000,
+ unsigned int bytesPerFrame = 2,
+ unsigned int bitsPerSample = 16):
+ RIFFAudioFile(0, "", fileName)
+{
+ m_type = WAV;
+ m_bitsPerSample = bitsPerSample;
+ m_sampleRate = sampleRate;
+ m_bytesPerSecond = bytesPerSecond;
+ m_bytesPerFrame = bytesPerFrame;
+ m_channels = channels;
+}
+
+BWFAudioFile::~BWFAudioFile()
+{}
+
+bool
+BWFAudioFile::open()
+{
+ // if already open
+ if (m_inFile && (*m_inFile))
+ return true;
+
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!(*m_inFile)) {
+ m_type = UNKNOWN;
+ return false;
+ }
+
+ // Get the file size and store it for comparison later
+ //
+ m_fileSize = m_fileInfo->size();
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException s) {
+ //throw(s);
+ return false;
+ }
+
+ return true;
+}
+
+// Open the file for writing, write out the header and move
+// to the data chunk to accept samples. We fill in all the
+// totals when we close().
+//
+bool
+BWFAudioFile::write()
+{
+ // close if we're open
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+ // open for writing
+ m_outFile = new std::ofstream(m_fileName.c_str(),
+ std::ios::out | std::ios::binary);
+
+ if (!(*m_outFile))
+ return false;
+
+ // write out format header chunk and prepare for sample writing
+ //
+ writeFormatChunk();
+
+ return true;
+}
+
+void
+BWFAudioFile::close()
+{
+ if (m_outFile == 0)
+ return ;
+
+ m_outFile->seekp(0, std::ios::end);
+ unsigned int totalSize = m_outFile->tellp();
+
+ // seek to first length position
+ m_outFile->seekp(4, std::ios::beg);
+
+ // write complete file size minus 8 bytes to here
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 8, 4));
+
+ // reseek from start forward 40
+ m_outFile->seekp(40, std::ios::beg);
+
+ // write the data chunk size to end
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 44, 4));
+
+ m_outFile->close();
+
+ delete m_outFile;
+ m_outFile = 0;
+}
+
+// Set the AudioFile meta data according to WAV file format specification.
+//
+void
+BWFAudioFile::parseHeader()
+{
+ // Read the format chunk and populate the file data. A plain WAV
+ // file only has this chunk. Exceptions tumble through.
+ //
+ readFormatChunk();
+
+}
+
+std::streampos
+BWFAudioFile::getDataOffset()
+{
+ return 0;
+}
+
+
+
+}
diff --git a/src/sound/BWFAudioFile.h b/src/sound/BWFAudioFile.h
new file mode 100644
index 0000000..d6717aa
--- /dev/null
+++ b/src/sound/BWFAudioFile.h
@@ -0,0 +1,94 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+// Specialisation of a RIFF file - the WAV defines a format chunk
+// holding audio file meta data and a data chunk with interleaved
+// sample bytes.
+//
+
+#include "RIFFAudioFile.h"
+
+
+#ifndef _BWFAUDIOFILE_H_
+#define _BWFAUDIOFILE_H_
+
+namespace Rosegarden
+{
+
+class BWFAudioFile : public RIFFAudioFile
+{
+public:
+ BWFAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName);
+
+ BWFAudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerFrame,
+ unsigned int bitsPerSample);
+
+ ~BWFAudioFile();
+
+ // Override these methods for the WAV
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ // Get all header information
+ //
+ void parseHeader();
+
+ //
+ //
+ //virtual std::vector<float> getPreview(const RealTime &resolution);
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset();
+
+ // Peak file name
+ //
+ virtual std::string getPeakFilename()
+ { return (m_fileName + std::string(".pk")); }
+
+
+ //!!! NOT IMPLEMENTED YET
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false) { return false; }
+
+protected:
+
+};
+
+}
+
+
+#endif // _BWFUDIOFILE_H_
diff --git a/src/sound/ControlBlock.cpp b/src/sound/ControlBlock.cpp
new file mode 100644
index 0000000..d0bb6a8
--- /dev/null
+++ b/src/sound/ControlBlock.cpp
@@ -0,0 +1,181 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <cstring>
+
+#include "ControlBlock.h"
+
+namespace Rosegarden
+{
+
+ControlBlock::ControlBlock(unsigned int maxTrackId)
+ : m_maxTrackId(maxTrackId),
+ m_solo(false),
+ m_routing(true),
+ m_thruFilter(0),
+ m_recordFilter(0),
+ m_selectedTrack(0)
+{
+ m_metronomeInfo.muted = true;
+ m_metronomeInfo.instrumentId = 0;
+ for (unsigned int i = 0; i < CONTROLBLOCK_MAX_NB_TRACKS; ++i) {
+ m_trackInfo[i].muted = true;
+ m_trackInfo[i].deleted = true;
+ m_trackInfo[i].armed = true;
+ m_trackInfo[i].instrumentId = 0;
+ }
+}
+
+ControlBlock::ControlBlock()
+{
+ // DO NOT initialize anything - this ctor is meant to be used by
+ // the sequencer, through a placement new over an mmapped file.
+}
+
+void ControlBlock::updateTrackData(Track* t)
+{
+ if (t) {
+ setInstrumentForTrack(t->getId(), t->getInstrument());
+ setTrackArmed(t->getId(), t->isArmed());
+ setTrackMuted(t->getId(), t->isMuted());
+ setTrackDeleted(t->getId(), false);
+ setTrackChannelFilter(t->getId(), t->getMidiInputChannel());
+ setTrackDeviceFilter(t->getId(), t->getMidiInputDevice());
+ if (t->getId() > m_maxTrackId)
+ m_maxTrackId = t->getId();
+ }
+}
+
+void ControlBlock::setInstrumentForTrack(TrackId trackId, InstrumentId instId)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].instrumentId = instId;
+}
+
+InstrumentId ControlBlock::getInstrumentForTrack(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].instrumentId;
+ return 0;
+}
+
+void ControlBlock::setTrackMuted(TrackId trackId, bool mute)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].muted = mute;
+}
+
+bool ControlBlock::isTrackMuted(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].muted;
+ return true;
+}
+
+void ControlBlock::setTrackArmed(TrackId trackId, bool armed)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].armed = armed;
+}
+
+bool ControlBlock::isTrackArmed(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].armed;
+ return false;
+}
+
+void ControlBlock::setTrackDeleted(TrackId trackId, bool deleted)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].deleted = deleted;
+}
+
+bool ControlBlock::isTrackDeleted(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].deleted;
+ return true;
+}
+
+void ControlBlock::setTrackChannelFilter(TrackId trackId, char channel)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].channelFilter = channel;
+}
+
+char ControlBlock::getTrackChannelFilter(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].channelFilter;
+ return -1;
+}
+
+void ControlBlock::setTrackDeviceFilter(TrackId trackId, DeviceId device)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].deviceFilter = device;
+}
+
+DeviceId ControlBlock::getTrackDeviceFilter(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].deviceFilter;
+ return Device::ALL_DEVICES;
+}
+
+bool ControlBlock::isInstrumentMuted(InstrumentId instrumentId) const
+{
+ for (unsigned int i = 0; i <= m_maxTrackId; ++i) {
+ if (m_trackInfo[i].instrumentId == instrumentId &&
+ !m_trackInfo[i].deleted && !m_trackInfo[i].muted)
+ return false;
+ }
+ return true;
+}
+
+bool ControlBlock::isInstrumentUnused(InstrumentId instrumentId) const
+{
+ for (unsigned int i = 0; i <= m_maxTrackId; ++i) {
+ if (m_trackInfo[i].instrumentId == instrumentId &&
+ !m_trackInfo[i].deleted)
+ return false;
+ }
+ return true;
+}
+
+InstrumentId ControlBlock::getInstrumentForEvent(unsigned int dev, unsigned int chan)
+{
+ for (unsigned int i = 0; i <= m_maxTrackId; ++i) {
+ if (!m_trackInfo[i].deleted && m_trackInfo[i].armed) {
+ if (((m_trackInfo[i].deviceFilter == Device::ALL_DEVICES) ||
+ (m_trackInfo[i].deviceFilter == dev)) &&
+ ((m_trackInfo[i].channelFilter == -1) ||
+ (m_trackInfo[i].channelFilter == chan)))
+ return m_trackInfo[i].instrumentId;
+ }
+ }
+ // There is not a matching filter, return the selected track instrument
+ return getInstrumentForTrack(getSelectedTrack());
+}
+
+
+}
diff --git a/src/sound/ControlBlock.h b/src/sound/ControlBlock.h
new file mode 100644
index 0000000..6db0ee5
--- /dev/null
+++ b/src/sound/ControlBlock.h
@@ -0,0 +1,128 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _CONTROLBLOCK_H_
+#define _CONTROLBLOCK_H_
+
+#include "MidiProgram.h"
+#include "Track.h"
+
+namespace Rosegarden
+{
+
+/**
+ * ONLY PUT PLAIN DATA HERE - NO POINTERS EVER
+ */
+struct TrackInfo
+{
+ bool deleted;
+ bool muted;
+ bool armed;
+ char channelFilter;
+ DeviceId deviceFilter;
+ InstrumentId instrumentId;
+};
+
+#define CONTROLBLOCK_MAX_NB_TRACKS 1024 // can't be a symbol
+
+/**
+ * Sequencer control block, mmapped by the GUI
+ */
+class ControlBlock
+{
+public:
+ /// ctor for GUI
+ ControlBlock(unsigned int maxTrackId);
+
+ /// ctor for sequencer - all data is read from mmapped file
+ ControlBlock();
+
+ unsigned int getMaxTrackId() const { return m_maxTrackId; }
+ void updateTrackData(Track*);
+
+ void setInstrumentForTrack(TrackId trackId, InstrumentId);
+ InstrumentId getInstrumentForTrack(TrackId trackId) const;
+
+ void setTrackArmed(TrackId trackId, bool);
+ bool isTrackArmed(TrackId trackId) const;
+
+ void setTrackMuted(TrackId trackId, bool);
+ bool isTrackMuted(TrackId trackId) const;
+
+ void setTrackDeleted(TrackId trackId, bool);
+ bool isTrackDeleted(TrackId trackId) const;
+
+ void setTrackChannelFilter(TrackId trackId, char);
+ char getTrackChannelFilter(TrackId trackId) const;
+
+ void setTrackDeviceFilter(TrackId trackId, DeviceId);
+ DeviceId getTrackDeviceFilter(TrackId trackId) const;
+
+ bool isInstrumentMuted(InstrumentId instrumentId) const;
+ bool isInstrumentUnused(InstrumentId instrumentId) const;
+
+ void setInstrumentForMetronome(InstrumentId instId) { m_metronomeInfo.instrumentId = instId; }
+ InstrumentId getInstrumentForMetronome() const { return m_metronomeInfo.instrumentId; }
+
+ void setMetronomeMuted(bool mute) { m_metronomeInfo.muted = mute; }
+ bool isMetronomeMuted() const { return m_metronomeInfo.muted; }
+
+ bool isSolo() const { return m_solo; }
+ void setSolo(bool value) { m_solo = value; }
+ TrackId getSelectedTrack() const { return m_selectedTrack; }
+ void setSelectedTrack(TrackId track) { m_selectedTrack = track; }
+
+ void setThruFilter(MidiFilter filter) { m_thruFilter = filter; }
+ MidiFilter getThruFilter() const { return m_thruFilter; }
+
+ void setRecordFilter(MidiFilter filter) { m_recordFilter = filter; }
+ MidiFilter getRecordFilter() const { return m_recordFilter; }
+
+ void setMidiRoutingEnabled(bool enabled) { m_routing = enabled; }
+ bool isMidiRoutingEnabled() const { return m_routing; }
+
+ /**
+ * Gets an InstrumentId for the given DeviceId and Channel. If there
+ * is an armed track having a matching device and channel filters,
+ * this method returns the instrument assigned to the track, even if
+ * there are more tracks matching the same filters. If there is not a
+ * single match, it returns the instrument assigned to the selected
+ * track.
+ */
+ InstrumentId getInstrumentForEvent(unsigned int dev,
+ unsigned int chan);
+
+protected:
+ //--------------- Data members ---------------------------------
+ // PUT ONLY PLAIN DATA HERE - NO POINTERS EVER
+ unsigned int m_maxTrackId;
+ bool m_solo;
+ bool m_routing;
+ MidiFilter m_thruFilter;
+ MidiFilter m_recordFilter;
+ TrackId m_selectedTrack;
+ TrackInfo m_metronomeInfo;
+ TrackInfo m_trackInfo[CONTROLBLOCK_MAX_NB_TRACKS]; // should be high enough for the moment
+};
+
+}
+
+#endif
diff --git a/src/sound/DSSIPluginFactory.cpp b/src/sound/DSSIPluginFactory.cpp
new file mode 100644
index 0000000..8447450
--- /dev/null
+++ b/src/sound/DSSIPluginFactory.cpp
@@ -0,0 +1,396 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "DSSIPluginFactory.h"
+#include <iostream>
+#include <cstdlib>
+
+#ifdef HAVE_DSSI
+
+#include <dlfcn.h>
+#include "AudioPluginInstance.h"
+#include "DSSIPluginInstance.h"
+#include "MappedStudio.h"
+#include "PluginIdentifier.h"
+
+#ifdef HAVE_LIBLRDF
+#include "lrdf.h"
+#endif // HAVE_LIBLRDF
+
+namespace Rosegarden
+{
+
+DSSIPluginFactory::DSSIPluginFactory() :
+ LADSPAPluginFactory()
+{
+ // nothing else to do
+}
+
+DSSIPluginFactory::~DSSIPluginFactory()
+{
+ // nothing else to do here either
+}
+
+void
+DSSIPluginFactory::enumeratePlugins(MappedObjectPropertyList &list)
+{
+ for (std::vector<QString>::iterator i = m_identifiers.begin();
+ i != m_identifiers.end(); ++i) {
+
+ const DSSI_Descriptor *ddesc = getDSSIDescriptor(*i);
+ if (!ddesc)
+ continue;
+
+ const LADSPA_Descriptor *descriptor = ddesc->LADSPA_Plugin;
+ if (!descriptor)
+ continue;
+
+ // std::cerr << "DSSIPluginFactory::enumeratePlugins: Name " << (descriptor->Name ? descriptor->Name : "NONE" ) << std::endl;
+
+ list.push_back(*i);
+ list.push_back(descriptor->Name);
+ list.push_back(QString("%1").arg(descriptor->UniqueID));
+ list.push_back(descriptor->Label);
+ list.push_back(descriptor->Maker);
+ list.push_back(descriptor->Copyright);
+ list.push_back((ddesc->run_synth || ddesc->run_multiple_synths) ? "true" : "false");
+ list.push_back(ddesc->run_multiple_synths ? "true" : "false");
+ list.push_back(m_taxonomy[descriptor->UniqueID]);
+ list.push_back(QString("%1").arg(descriptor->PortCount));
+
+ for (unsigned long p = 0; p < descriptor->PortCount; ++p) {
+
+ int type = 0;
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Control;
+ } else {
+ type |= PluginPort::Audio;
+ }
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Input;
+ } else {
+ type |= PluginPort::Output;
+ }
+
+ list.push_back(QString("%1").arg(p));
+ list.push_back(descriptor->PortNames[p]);
+ list.push_back(QString("%1").arg(type));
+ list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
+ }
+ }
+
+ unloadUnusedLibraries();
+}
+
+
+void
+DSSIPluginFactory::populatePluginSlot(QString identifier, MappedPluginSlot &slot)
+{
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier);
+ if (!descriptor)
+ return ;
+
+ if (descriptor) {
+
+ slot.setProperty(MappedPluginSlot::Label, descriptor->Label);
+ slot.setProperty(MappedPluginSlot::PluginName, descriptor->Name);
+ slot.setProperty(MappedPluginSlot::Author, descriptor->Maker);
+ slot.setProperty(MappedPluginSlot::Copyright, descriptor->Copyright);
+ slot.setProperty(MappedPluginSlot::PortCount, descriptor->PortCount);
+ slot.setProperty(MappedPluginSlot::Category, m_taxonomy[descriptor->UniqueID]);
+
+ slot.destroyChildren();
+
+ for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+ LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ MappedStudio *studio = dynamic_cast<MappedStudio *>(slot.getParent());
+ if (!studio) {
+ std::cerr << "WARNING: DSSIPluginFactory::populatePluginSlot: can't find studio" << std::endl;
+ return ;
+ }
+
+ MappedPluginPort *port =
+ dynamic_cast<MappedPluginPort *>
+ (studio->createObject(MappedObject::PluginPort));
+
+ slot.addChild(port);
+ port->setParent(&slot);
+
+ port->setProperty(MappedPluginPort::PortNumber, i);
+ port->setProperty(MappedPluginPort::Name,
+ descriptor->PortNames[i]);
+ port->setProperty(MappedPluginPort::Maximum,
+ getPortMaximum(descriptor, i));
+ port->setProperty(MappedPluginPort::Minimum,
+ getPortMinimum(descriptor, i));
+ port->setProperty(MappedPluginPort::Default,
+ getPortDefault(descriptor, i));
+ port->setProperty(MappedPluginPort::DisplayHint,
+ getPortDisplayHint(descriptor, i));
+ }
+ }
+ }
+
+ //!!! leak here if the plugin is not instantiated too...?
+}
+
+RunnablePluginInstance *
+DSSIPluginFactory::instantiatePlugin(QString identifier,
+ int instrument,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels)
+{
+ const DSSI_Descriptor *descriptor = getDSSIDescriptor(identifier);
+
+ if (descriptor) {
+
+ DSSIPluginInstance *instance =
+ new DSSIPluginInstance
+ (this, instrument, identifier, position, sampleRate, blockSize, channels,
+ descriptor);
+
+ m_instances.insert(instance);
+
+ return instance;
+ }
+
+ return 0;
+}
+
+
+const DSSI_Descriptor *
+DSSIPluginFactory::getDSSIDescriptor(QString identifier)
+{
+ QString type, soname, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ loadLibrary(soname);
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: loadLibrary failed for " << soname << std::endl;
+ return 0;
+ }
+ }
+
+ void *libraryHandle = m_libraryHandles[soname];
+
+ DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function)
+ dlsym(libraryHandle, "dssi_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No descriptor function in library " << soname << std::endl;
+ return 0;
+ }
+
+ const DSSI_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+ if (descriptor->LADSPA_Plugin->Label == label)
+ return descriptor;
+ ++index;
+ }
+
+ std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No such plugin as " << label << " in library " << soname << std::endl;
+
+ return 0;
+}
+
+const LADSPA_Descriptor *
+DSSIPluginFactory::getLADSPADescriptor(QString identifier)
+{
+ const DSSI_Descriptor *dssiDescriptor = getDSSIDescriptor(identifier);
+ if (dssiDescriptor)
+ return dssiDescriptor->LADSPA_Plugin;
+ else
+ return 0;
+}
+
+
+std::vector<QString>
+DSSIPluginFactory::getPluginPath()
+{
+ std::vector<QString> pathList;
+ std::string path;
+
+ char *cpath = getenv("DSSI_PATH");
+ if (cpath)
+ path = cpath;
+
+ if (path == "") {
+ path = "/usr/local/lib/dssi:/usr/lib/dssi";
+ char *home = getenv("HOME");
+ if (home)
+ path = std::string(home) + "/.dssi:" + path;
+ }
+
+ std::string::size_type index = 0, newindex = 0;
+
+ while ((newindex = path.find(':', index)) < path.size()) {
+ pathList.push_back(path.substr(index, newindex - index).c_str());
+ index = newindex + 1;
+ }
+
+ pathList.push_back(path.substr(index).c_str());
+
+ return pathList;
+}
+
+
+#ifdef HAVE_LIBLRDF
+std::vector<QString>
+DSSIPluginFactory::getLRDFPath(QString &baseUri)
+{
+ std::vector<QString> pathList = getPluginPath();
+ std::vector<QString> lrdfPaths;
+
+ lrdfPaths.push_back("/usr/local/share/dssi/rdf");
+ lrdfPaths.push_back("/usr/share/dssi/rdf");
+
+ lrdfPaths.push_back("/usr/local/share/ladspa/rdf");
+ lrdfPaths.push_back("/usr/share/ladspa/rdf");
+
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+ lrdfPaths.push_back(*i + "/rdf");
+ }
+
+#ifdef DSSI_BASE
+ baseUri = DSSI_BASE;
+#else
+
+ baseUri = "http://dssi.sourceforge.net/ontology#";
+#endif
+
+ return lrdfPaths;
+}
+#endif
+
+
+void
+DSSIPluginFactory::discoverPlugins(QString soName)
+{
+ void *libraryHandle = dlopen(soName.data(), RTLD_LAZY);
+
+ if (!libraryHandle) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: couldn't dlopen "
+ << soName << " - " << dlerror() << std::endl;
+ return ;
+ }
+
+ DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function)
+ dlsym(libraryHandle, "dssi_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No descriptor function in " << soName << std::endl;
+ return ;
+ }
+
+ const DSSI_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+
+ const LADSPA_Descriptor * ladspaDescriptor = descriptor->LADSPA_Plugin;
+ if (!ladspaDescriptor) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No LADSPA descriptor for plugin " << index << " in " << soName << std::endl;
+ ++index;
+ continue;
+ }
+
+#ifdef HAVE_LIBLRDF
+ char *def_uri = 0;
+ lrdf_defaults *defs = 0;
+
+ QString category = m_taxonomy[ladspaDescriptor->UniqueID];
+
+ if (category == "" && ladspaDescriptor->Name != 0) {
+ std::string name = ladspaDescriptor->Name;
+ if (name.length() > 4 &&
+ name.substr(name.length() - 4) == " VST") {
+ if (descriptor->run_synth || descriptor->run_multiple_synths) {
+ category = "VST instruments";
+ } else {
+ category = "VST effects";
+ }
+ m_taxonomy[ladspaDescriptor->UniqueID] = category;
+ }
+ }
+
+ // std::cerr << "Plugin id is " << ladspaDescriptor->UniqueID
+ // << ", category is \"" << (category ? category : QString("(none)"))
+ // << "\", name is " << ladspaDescriptor->Name
+ // << ", label is " << ladspaDescriptor->Label
+ // << std::endl;
+
+ def_uri = lrdf_get_default_uri(ladspaDescriptor->UniqueID);
+ if (def_uri) {
+ defs = lrdf_get_setting_values(def_uri);
+ }
+
+ int controlPortNumber = 1;
+
+ for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
+
+ if (def_uri && defs) {
+
+ for (int j = 0; j < defs->count; j++) {
+ if (defs->items[j].pid == controlPortNumber) {
+ // std::cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << ladspaDescriptor->PortNames[i] << std::endl;
+ m_portDefaults[ladspaDescriptor->UniqueID][i] =
+ defs->items[j].value;
+ }
+ }
+ }
+
+ ++controlPortNumber;
+ }
+ }
+#endif // HAVE_LIBLRDF
+
+ QString identifier = PluginIdentifier::createIdentifier
+ ("dssi", soName, ladspaDescriptor->Label);
+ m_identifiers.push_back(identifier);
+
+ ++index;
+ }
+
+ if (dlclose(libraryHandle) != 0) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins - can't unload " << libraryHandle << std::endl;
+ return ;
+ }
+}
+
+
+}
+
+#endif // HAVE_DSSI
+
diff --git a/src/sound/DSSIPluginFactory.h b/src/sound/DSSIPluginFactory.h
new file mode 100644
index 0000000..8c1bd7c
--- /dev/null
+++ b/src/sound/DSSIPluginFactory.h
@@ -0,0 +1,72 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _DSSI_PLUGIN_FACTORY_H_
+#define _DSSI_PLUGIN_FACTORY_H_
+
+#ifdef HAVE_DSSI
+
+#include "LADSPAPluginFactory.h"
+#include <dssi.h>
+
+namespace Rosegarden
+{
+
+class DSSIPluginInstance;
+
+class DSSIPluginFactory : public LADSPAPluginFactory
+{
+public:
+ virtual ~DSSIPluginFactory();
+
+ virtual void enumeratePlugins(MappedObjectPropertyList &list);
+
+ virtual void populatePluginSlot(QString identifier, MappedPluginSlot &slot);
+
+ virtual RunnablePluginInstance *instantiatePlugin(QString identifier,
+ int instrumentId,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels);
+
+protected:
+ DSSIPluginFactory();
+ friend class PluginFactory;
+
+ virtual std::vector<QString> getPluginPath();
+
+#ifdef HAVE_LIBLRDF
+ virtual std::vector<QString> getLRDFPath(QString &baseUri);
+#endif
+
+ virtual void discoverPlugins(QString soName);
+
+ virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier);
+ virtual const DSSI_Descriptor *getDSSIDescriptor(QString identifier);
+};
+
+}
+
+#endif
+
+#endif
+
diff --git a/src/sound/DSSIPluginInstance.cpp b/src/sound/DSSIPluginInstance.cpp
new file mode 100644
index 0000000..2ceb0df
--- /dev/null
+++ b/src/sound/DSSIPluginInstance.cpp
@@ -0,0 +1,1208 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <iostream>
+#include <cassert>
+#include <cstdlib>
+
+#include "DSSIPluginInstance.h"
+#include "PluginIdentifier.h"
+#include "LADSPAPluginFactory.h"
+
+#ifdef HAVE_DSSI
+
+//#define DEBUG_DSSI 1
+//#define DEBUG_DSSI_PROCESS 1
+
+namespace Rosegarden
+{
+
+#define EVENT_BUFFER_SIZE 1023
+
+DSSIPluginInstance::GroupMap DSSIPluginInstance::m_groupMap;
+snd_seq_event_t **DSSIPluginInstance::m_groupLocalEventBuffers = 0;
+size_t DSSIPluginInstance::m_groupLocalEventBufferCount = 0;
+Scavenger<ScavengerArrayWrapper<snd_seq_event_t *> > DSSIPluginInstance::m_bufferScavenger(2, 10);
+
+
+DSSIPluginInstance::DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const DSSI_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_descriptor(descriptor),
+ m_programCacheValid(false),
+ m_eventBuffer(EVENT_BUFFER_SIZE),
+ m_blockSize(blockSize),
+ m_idealChannelCount(idealChannelCount),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_runSinceReset(false),
+ m_bypassed(false),
+ m_grouped(false)
+{
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_processLock, &initialisingMutex, sizeof(pthread_mutex_t));
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::DSSIPluginInstance(" << identifier << ")"
+ << std::endl;
+#endif
+
+ init();
+
+ m_inputBuffers = new sample_t * [m_audioPortsIn.size()];
+ m_outputBuffers = new sample_t * [m_outputBufferCount];
+
+ for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+ m_inputBuffers[i] = new sample_t[blockSize];
+ }
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ m_outputBuffers[i] = new sample_t[blockSize];
+ }
+
+ m_ownBuffers = true;
+
+ m_pending.lsb = m_pending.msb = m_pending.program = -1;
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ initialiseGroupMembership();
+ }
+}
+
+DSSIPluginInstance::DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const DSSI_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_descriptor(descriptor),
+ m_eventBuffer(EVENT_BUFFER_SIZE),
+ m_blockSize(blockSize),
+ m_inputBuffers(inputBuffers),
+ m_outputBuffers(outputBuffers),
+ m_ownBuffers(false),
+ m_idealChannelCount(0),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_runSinceReset(false),
+ m_bypassed(false),
+ m_grouped(false)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::DSSIPluginInstance[buffers supplied](" << identifier << ")"
+ << std::endl;
+#endif
+
+ init();
+
+ m_pending.lsb = m_pending.msb = m_pending.program = -1;
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ if (m_descriptor->run_multiple_synths) {
+ m_grouped = true;
+ initialiseGroupMembership();
+ }
+ }
+}
+
+
+void
+DSSIPluginInstance::init()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::init" << std::endl;
+#endif
+
+ // Discover ports numbers and identities
+ //
+ const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin;
+
+ for (unsigned long i = 0; i < descriptor->PortCount; ++i) {
+ if (LADSPA_IS_PORT_AUDIO(descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+ m_audioPortsIn.push_back(i);
+ } else {
+ m_audioPortsOut.push_back(i);
+ }
+ } else
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+
+ m_controlPortsIn.push_back(std::pair<unsigned long, LADSPA_Data*>
+ (i, data));
+
+ m_backupControlPortsIn.push_back(0.0);
+ m_portChangedSinceProgramChange.push_back(false);
+
+ } else {
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+ m_controlPortsOut.push_back(
+ std::pair<unsigned long, LADSPA_Data*>(i, data));
+ if (!strcmp(descriptor->PortNames[i], "latency") ||
+ !strcmp(descriptor->PortNames[i], "_latency")) {
+#ifdef DEBUG_DSSI
+ std::cerr << "Wooo! We have a latency port!" << std::endl;
+#endif
+
+ m_latencyPort = data;
+ }
+ }
+ }
+#ifdef DEBUG_DSSI
+ else
+ std::cerr << "DSSIPluginInstance::DSSIPluginInstance - "
+ << "unrecognised port type" << std::endl;
+#endif
+
+ }
+
+ m_outputBufferCount = std::max(m_idealChannelCount, m_audioPortsOut.size());
+}
+
+size_t
+DSSIPluginInstance::getLatency()
+{
+#ifdef DEBUG_DSSI
+ // std::cerr << "DSSIPluginInstance::getLatency(): m_latencyPort " << m_latencyPort << ", m_run " << m_run << std::endl;
+#endif
+
+ if (m_latencyPort) {
+ if (!m_run) {
+ for (int i = 0; i < getAudioInputCount(); ++i) {
+ for (int j = 0; j < m_blockSize; ++j) {
+ m_inputBuffers[i][j] = 0.f;
+ }
+ }
+ run(RealTime::zeroTime);
+ }
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::getLatency(): latency is " << (size_t)(*m_latencyPort + 0.1) << std::endl;
+#endif
+
+ return (size_t)(*m_latencyPort + 0.1);
+ }
+ return 0;
+}
+
+void
+DSSIPluginInstance::silence()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::silence: m_run " << m_run << ", m_runSinceReset " << m_runSinceReset << std::endl;
+#endif
+
+ if (m_run && !m_runSinceReset) {
+ return ;
+ }
+ if (m_instanceHandle != 0) {
+ deactivate();
+ activate();
+ }
+ m_runSinceReset = false;
+}
+
+void
+DSSIPluginInstance::discardEvents()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::discardEvents" << std::endl;
+#endif
+
+ m_eventBuffer.reset();
+}
+
+void
+DSSIPluginInstance::setIdealChannelCount(size_t channels)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::setIdealChannelCount: channel count "
+ << channels << " (was " << m_idealChannelCount << ")" << std::endl;
+#endif
+
+ if (channels == m_idealChannelCount) {
+ silence();
+ return ;
+ }
+
+ if (m_instanceHandle != 0) {
+ deactivate();
+ }
+
+ m_idealChannelCount = channels;
+
+ if (channels > m_outputBufferCount) {
+
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ delete[] m_outputBuffers[i];
+ }
+
+ delete[] m_outputBuffers;
+
+ m_outputBufferCount = channels;
+
+ m_outputBuffers = new sample_t * [m_outputBufferCount];
+
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ m_outputBuffers[i] = new sample_t[m_blockSize];
+ }
+
+ connectPorts();
+ }
+
+ if (m_instanceHandle != 0) {
+ activate();
+ }
+}
+
+void
+DSSIPluginInstance::detachFromGroup()
+{
+ if (!m_grouped)
+ return ;
+ m_groupMap[m_identifier].erase(this);
+ m_grouped = false;
+}
+
+void
+DSSIPluginInstance::initialiseGroupMembership()
+{
+ if (!m_descriptor->run_multiple_synths) {
+ m_grouped = false;
+ return ;
+ }
+
+ //!!! GroupMap is not actually thread-safe.
+
+ size_t pluginsInGroup = m_groupMap[m_identifier].size();
+
+ if (++pluginsInGroup > m_groupLocalEventBufferCount) {
+
+ size_t nextBufferCount = pluginsInGroup * 2;
+
+ snd_seq_event_t **eventLocalBuffers = new snd_seq_event_t * [nextBufferCount];
+
+ for (size_t i = 0; i < m_groupLocalEventBufferCount; ++i) {
+ eventLocalBuffers[i] = m_groupLocalEventBuffers[i];
+ }
+ for (size_t i = m_groupLocalEventBufferCount; i < nextBufferCount; ++i) {
+ eventLocalBuffers[i] = new snd_seq_event_t[EVENT_BUFFER_SIZE];
+ }
+
+ if (m_groupLocalEventBuffers) {
+ m_bufferScavenger.claim(new ScavengerArrayWrapper<snd_seq_event_t *>
+ (m_groupLocalEventBuffers));
+ }
+
+ m_groupLocalEventBuffers = eventLocalBuffers;
+ m_groupLocalEventBufferCount = nextBufferCount;
+ }
+
+ m_grouped = true;
+ m_groupMap[m_identifier].insert(this);
+}
+
+DSSIPluginInstance::~DSSIPluginInstance()
+{
+// std::cerr << "DSSIPluginInstance::~DSSIPluginInstance" << std::endl;
+
+ detachFromGroup();
+
+ if (m_instanceHandle != 0) {
+ deactivate();
+ }
+
+ cleanup();
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i)
+ delete m_controlPortsIn[i].second;
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i)
+ delete m_controlPortsOut[i].second;
+
+ m_controlPortsIn.clear();
+ m_controlPortsOut.clear();
+
+ if (m_ownBuffers) {
+ for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+ delete[] m_inputBuffers[i];
+ }
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ delete[] m_outputBuffers[i];
+ }
+
+ delete[] m_inputBuffers;
+ delete[] m_outputBuffers;
+ }
+
+ m_audioPortsIn.clear();
+ m_audioPortsOut.clear();
+}
+
+
+void
+DSSIPluginInstance::instantiate(unsigned long sampleRate)
+{
+#ifdef DEBUG_DSSI
+ std::cout << "DSSIPluginInstance::instantiate - plugin unique id = "
+ << m_descriptor->LADSPA_Plugin->UniqueID << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin;
+
+ if (!descriptor->instantiate) {
+ std::cerr << "Bad plugin: plugin id " << descriptor->UniqueID
+ << ":" << descriptor->Label
+ << " has no instantiate method!" << std::endl;
+ return ;
+ }
+
+ m_instanceHandle = descriptor->instantiate(descriptor, sampleRate);
+
+ if (m_instanceHandle) {
+
+ if (m_descriptor->get_midi_controller_for_port) {
+
+ for (unsigned long i = 0; i < descriptor->PortCount; ++i) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+ LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ int controller = m_descriptor->get_midi_controller_for_port
+ (m_instanceHandle, i);
+
+ if (controller != 0 && controller != 32 &&
+ DSSI_IS_CC(controller)) {
+
+ m_controllerMap[DSSI_CC_NUMBER(controller)] = i;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+DSSIPluginInstance::checkProgramCache()
+{
+ if (m_programCacheValid)
+ return ;
+ m_cachedPrograms.clear();
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::checkProgramCache" << std::endl;
+#endif
+
+ if (!m_descriptor || !m_descriptor->get_program) {
+ m_programCacheValid = true;
+ return ;
+ }
+
+ unsigned long index = 0;
+ const DSSI_Program_Descriptor *programDescriptor;
+ while ((programDescriptor = m_descriptor->get_program(m_instanceHandle, index))) {
+ ++index;
+ ProgramDescriptor d;
+ d.bank = programDescriptor->Bank;
+ d.program = programDescriptor->Program;
+ d.name = QString("%1. %2").arg(index).arg(programDescriptor->Name);
+ m_cachedPrograms.push_back(d);
+ }
+
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::checkProgramCache: have " << m_cachedPrograms.size() << " programs" << std::endl;
+#endif
+
+ m_programCacheValid = true;
+}
+
+QStringList
+DSSIPluginInstance::getPrograms()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getPrograms" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return QStringList();
+
+ checkProgramCache();
+
+ QStringList programs;
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+ programs.push_back(i->name);
+ }
+
+ return programs;
+}
+
+QString
+DSSIPluginInstance::getProgram(int bank, int program)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getProgram(" << bank << "," << program << ")" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return QString();
+
+ checkProgramCache();
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+ if (i->bank == bank && i->program == program)
+ return i->name;
+ }
+
+ return QString();
+}
+
+unsigned long
+DSSIPluginInstance::getProgram(QString name)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getProgram(" << name << ")" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return 0;
+
+ checkProgramCache();
+
+ unsigned long rv;
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+ if (i->name == name) {
+ rv = i->bank;
+ rv = (rv << 16) + i->program;
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+QString
+DSSIPluginInstance::getCurrentProgram()
+{
+ return m_program;
+}
+
+void
+DSSIPluginInstance::selectProgram(QString program)
+{
+ selectProgramAux(program, true);
+}
+
+void
+DSSIPluginInstance::selectProgramAux(QString program, bool backupPortValues)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance[" << this << "]::selectProgram(" << program << ", " << backupPortValues << ")" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ checkProgramCache();
+
+ if (!m_descriptor->select_program)
+ return ;
+
+ bool found = false;
+ unsigned long bankNo = 0, programNo = 0;
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+
+ if (i->name == program) {
+
+ bankNo = i->bank;
+ programNo = i->program;
+ found = true;
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::selectProgram(" << program << "): found at bank " << bankNo << ", program " << programNo << std::endl;
+#endif
+
+ break;
+ }
+ }
+
+ if (!found)
+ return ;
+ m_program = program;
+
+ // DSSI select_program is an audio context call
+ pthread_mutex_lock(&m_processLock);
+ m_descriptor->select_program(m_instanceHandle, bankNo, programNo);
+ pthread_mutex_unlock(&m_processLock);
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::selectProgram(" << program << "): made select_program(" << bankNo << "," << programNo << ") call" << std::endl;
+#endif
+
+ if (backupPortValues) {
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
+ m_portChangedSinceProgramChange[i] = false;
+ }
+ }
+}
+
+void
+DSSIPluginInstance::activate()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance[" << this << "]::activate" << std::endl;
+#endif
+
+ if (!m_descriptor || !m_descriptor->LADSPA_Plugin->activate)
+ return ;
+ m_descriptor->LADSPA_Plugin->activate(m_instanceHandle);
+
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ if (m_portChangedSinceProgramChange[i]) {
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << std::endl;
+#endif
+
+ *m_controlPortsIn[i].second = m_backupControlPortsIn[i];
+ }
+ }
+
+ if (m_program) {
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::activate: restoring program " << m_program << std::endl;
+#endif
+
+ selectProgramAux(m_program, false);
+
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ if (m_portChangedSinceProgramChange[i]) {
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << std::endl;
+#endif
+
+ *m_controlPortsIn[i].second = m_backupControlPortsIn[i];
+ }
+ }
+ }
+}
+
+void
+DSSIPluginInstance::connectPorts()
+{
+ if (!m_descriptor || !m_descriptor->LADSPA_Plugin->connect_port)
+ return ;
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::connectPorts: " << m_audioPortsIn.size()
+ << " audio ports in, " << m_audioPortsOut.size() << " out, "
+ << m_outputBufferCount << " output buffers" << std::endl;
+#endif
+
+ assert(sizeof(LADSPA_Data) == sizeof(float));
+ assert(sizeof(sample_t) == sizeof(float));
+
+ int inbuf = 0, outbuf = 0;
+
+ for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_audioPortsIn[i],
+ (LADSPA_Data *)m_inputBuffers[inbuf]);
+ ++inbuf;
+ }
+
+ for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_audioPortsOut[i],
+ (LADSPA_Data *)m_outputBuffers[outbuf]);
+ ++outbuf;
+ }
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_controlPortsIn[i].first,
+ m_controlPortsIn[i].second);
+ }
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_controlPortsOut[i].first,
+ m_controlPortsOut[i].second);
+ }
+}
+
+void
+DSSIPluginInstance::setPortValue(unsigned int portNumber, float value)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance[" << this << "]::setPortValue(" << portNumber << ") to " << value << std::endl;
+#endif
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
+ if (f) {
+ if (value < f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber)) {
+ value = f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber);
+ }
+ if (value > f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber)) {
+ value = f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber);
+ }
+ }
+ (*m_controlPortsIn[i].second) = value;
+ m_backupControlPortsIn[i] = value;
+ m_portChangedSinceProgramChange[i] = true;
+ }
+ }
+}
+
+void
+DSSIPluginInstance::setPortValueFromController(unsigned int port, int cv)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::setPortValueFromController(" << port << ") to " << cv << std::endl;
+#endif
+
+ const LADSPA_Descriptor *p = m_descriptor->LADSPA_Plugin;
+ LADSPA_PortRangeHintDescriptor d = p->PortRangeHints[port].HintDescriptor;
+ LADSPA_Data lb = p->PortRangeHints[port].LowerBound;
+ LADSPA_Data ub = p->PortRangeHints[port].UpperBound;
+
+ float value = (float)cv;
+
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ /* unbounded: might as well leave the value alone. */
+ } else {
+ /* bounded above only. just shift the range. */
+ value = ub - 127.0f + value;
+ }
+ } else {
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ /* bounded below only. just shift the range. */
+ value = lb + value;
+ } else {
+ /* bounded both ends. more interesting. */
+ /* XXX !!! todo: fill in logarithmic, sample rate &c */
+ value = lb + ((ub - lb) * value / 127.0f);
+ }
+ }
+
+ setPortValue(port, value);
+}
+
+float
+DSSIPluginInstance::getPortValue(unsigned int portNumber)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getPortValue(" << portNumber << ")" << std::endl;
+#endif
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ return (*m_controlPortsIn[i].second);
+ }
+ }
+
+ return 0.0;
+}
+
+QString
+DSSIPluginInstance::configure(QString key,
+ QString value)
+{
+ if (!m_descriptor || !m_descriptor->configure)
+ return QString();
+
+ if (key == PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY) {
+#ifdef DSSI_PROJECT_DIRECTORY_KEY
+ key = DSSI_PROJECT_DIRECTORY_KEY;
+#else
+
+ return QString();
+#endif
+
+ }
+
+
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::configure(" << key << "," << value << ")" << std::endl;
+#endif
+
+ char *message = m_descriptor->configure(m_instanceHandle, key.data(), value.data());
+
+ m_programCacheValid = false;
+
+ QString qm;
+
+ // Ignore return values from reserved key configuration calls such
+ // as project directory
+#ifdef DSSI_RESERVED_CONFIGURE_PREFIX
+
+ if (key.startsWith(DSSI_RESERVED_CONFIGURE_PREFIX)) {
+ return qm;
+ }
+#endif
+
+ if (message) {
+ if (m_descriptor->LADSPA_Plugin && m_descriptor->LADSPA_Plugin->Label) {
+ qm = QString(m_descriptor->LADSPA_Plugin->Label) + ": ";
+ }
+ qm = qm + message;
+ free(message);
+ }
+
+ return qm;
+}
+
+void
+DSSIPluginInstance::sendEvent(const RealTime &eventTime,
+ const void *e)
+{
+ snd_seq_event_t *event = (snd_seq_event_t *)e;
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::sendEvent at " << eventTime << std::endl;
+#endif
+
+ snd_seq_event_t ev(*event);
+
+ ev.time.time.tv_sec = eventTime.sec;
+ ev.time.time.tv_nsec = eventTime.nsec;
+
+ // DSSI doesn't use MIDI channels, it uses run_multiple_synths instead.
+ ev.data.note.channel = 0;
+
+ m_eventBuffer.write(&ev, 1);
+}
+
+bool
+DSSIPluginInstance::handleController(snd_seq_event_t *ev)
+{
+ int controller = ev->data.control.param;
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::handleController " << controller << std::endl;
+#endif
+
+ if (controller == 0) { // bank select MSB
+
+ m_pending.msb = ev->data.control.value;
+
+ } else if (controller == 32) { // bank select LSB
+
+ m_pending.lsb = ev->data.control.value;
+
+ } else if (controller > 0 && controller < 128) {
+
+ if (m_controllerMap.find(controller) != m_controllerMap.end()) {
+ int port = m_controllerMap[controller];
+ setPortValueFromController(port, ev->data.control.value);
+ } else {
+ return true; // pass through to plugin
+ }
+ }
+
+ return false;
+}
+
+void
+DSSIPluginInstance::run(const RealTime &blockTime)
+{
+ static snd_seq_event_t localEventBuffer[EVENT_BUFFER_SIZE];
+ int evCount = 0;
+
+ bool needLock = false;
+ if (m_descriptor->select_program)
+ needLock = true;
+
+ if (needLock) {
+ if (pthread_mutex_trylock(&m_processLock) != 0) {
+ for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
+ memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
+ }
+ return ;
+ }
+ }
+
+ if (m_grouped) {
+ runGrouped(blockTime);
+ goto done;
+ }
+
+ if (!m_descriptor || !m_descriptor->run_synth) {
+ m_eventBuffer.skip(m_eventBuffer.getReadSpace());
+ if (m_descriptor->LADSPA_Plugin->run) {
+ m_descriptor->LADSPA_Plugin->run(m_instanceHandle, m_blockSize);
+ } else {
+ for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
+ memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
+ }
+ }
+ m_run = true;
+ m_runSinceReset = true;
+ if (needLock)
+ pthread_mutex_unlock(&m_processLock);
+ return ;
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::run(" << blockTime << ")" << std::endl;
+#endif
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ if (m_eventBuffer.getReadSpace() > 0) {
+ std::cerr << "DSSIPluginInstance::run: event buffer has "
+ << m_eventBuffer.getReadSpace() << " event(s) in it" << std::endl;
+ }
+#endif
+
+ while (m_eventBuffer.getReadSpace() > 0) {
+
+ snd_seq_event_t *ev = localEventBuffer + evCount;
+ *ev = m_eventBuffer.peek();
+ bool accept = true;
+
+ RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+
+ int frameOffset = 0;
+ if (evTime > blockTime) {
+ frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::run: evTime " << evTime << ", blockTime " << blockTime << ", frameOffset " << frameOffset
+ << ", blockSize " << m_blockSize << std::endl;
+ std::cerr << "Type: " << int(ev->type) << ", pitch: " << int(ev->data.note.note) << ", velocity: " << int(ev->data.note.velocity) << std::endl;
+#endif
+
+ if (frameOffset >= int(m_blockSize))
+ break;
+ if (frameOffset < 0)
+ frameOffset = 0;
+
+ ev->time.tick = frameOffset;
+ m_eventBuffer.skip(1);
+
+ if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
+ accept = handleController(ev);
+ } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
+ m_pending.program = ev->data.control.value;
+ accept = false;
+ }
+
+ if (accept) {
+ if (++evCount >= EVENT_BUFFER_SIZE)
+ break;
+ }
+ }
+
+ if (m_pending.program >= 0 && m_descriptor->select_program) {
+
+ int program = m_pending.program;
+ int bank = m_pending.lsb + 128 * m_pending.msb;
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::run: making select_program(" << bank << "," << program << " call" << std::endl;
+#endif
+
+ m_pending.lsb = m_pending.msb = m_pending.program = -1;
+ m_descriptor->select_program(m_instanceHandle, bank, program);
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::run: made select_program(" << bank << "," << program << " call" << std::endl;
+#endif
+
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::run: running with " << evCount << " events"
+ << std::endl;
+#endif
+
+ m_descriptor->run_synth(m_instanceHandle, m_blockSize,
+ localEventBuffer, evCount);
+
+#ifdef DEBUG_DSSI_PROCESS
+ // for (int i = 0; i < m_blockSize; ++i) {
+ // std::cout << m_outputBuffers[0][i] << " ";
+ // if (i % 8 == 0) std::cout << std::endl;
+ // }
+#endif
+
+done:
+ if (needLock)
+ pthread_mutex_unlock(&m_processLock);
+
+ if (m_audioPortsOut.size() == 0) {
+ // copy inputs to outputs
+ for (size_t ch = 0; ch < m_idealChannelCount; ++ch) {
+ size_t sch = ch % m_audioPortsIn.size();
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_outputBuffers[ch][i] = m_inputBuffers[sch][i];
+ }
+ }
+ } else if (m_idealChannelCount < m_audioPortsOut.size()) {
+ if (m_idealChannelCount == 1) {
+ // mix down to mono
+ for (size_t ch = 1; ch < m_audioPortsOut.size(); ++ch) {
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_outputBuffers[0][i] += m_outputBuffers[ch][i];
+ }
+ }
+ }
+ } else if (m_idealChannelCount > m_audioPortsOut.size()) {
+ // duplicate
+ for (size_t ch = m_audioPortsOut.size(); ch < m_idealChannelCount; ++ch) {
+ size_t sch = (ch - m_audioPortsOut.size()) % m_audioPortsOut.size();
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_outputBuffers[ch][i] = m_outputBuffers[sch][i];
+ }
+ }
+ }
+
+ m_lastRunTime = blockTime;
+ m_run = true;
+ m_runSinceReset = true;
+}
+
+void
+DSSIPluginInstance::runGrouped(const RealTime &blockTime)
+{
+ // If something else in our group has just been called for this
+ // block time (but we haven't) then we should just write out the
+ // results and return; if we have just been called for this block
+ // time or nothing else in the group has been, we should run the
+ // whole group.
+
+ bool needRun = true;
+
+ PluginSet &s = m_groupMap[m_identifier];
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): this is " << this << "; " << s.size() << " elements in m_groupMap[" << m_identifier << "]" << std::endl;
+#endif
+
+ if (m_lastRunTime != blockTime) {
+ for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
+ DSSIPluginInstance *instance = *i;
+ if (instance != this && instance->m_lastRunTime == blockTime) {
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): plugin " << instance << " has already been run" << std::endl;
+#endif
+
+ needRun = false;
+ }
+ }
+ }
+
+ if (!needRun) {
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): already run, returning" << std::endl;
+#endif
+
+ return ;
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): I'm the first, running" << std::endl;
+#endif
+
+ size_t index = 0;
+ unsigned long *counts = (unsigned long *)
+ alloca(m_groupLocalEventBufferCount * sizeof(unsigned long));
+ LADSPA_Handle *instances = (LADSPA_Handle *)
+ alloca(m_groupLocalEventBufferCount * sizeof(LADSPA_Handle));
+
+ for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
+
+ if (index >= m_groupLocalEventBufferCount)
+ break;
+
+ DSSIPluginInstance *instance = *i;
+ counts[index] = 0;
+ instances[index] = instance->m_instanceHandle;
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): running " << instance << std::endl;
+#endif
+
+ if (instance->m_pending.program >= 0 &&
+ instance->m_descriptor->select_program) {
+ int program = instance->m_pending.program;
+ int bank = instance->m_pending.lsb + 128 * instance->m_pending.msb;
+ instance->m_pending.lsb = instance->m_pending.msb = instance->m_pending.program = -1;
+ instance->m_descriptor->select_program
+ (instance->m_instanceHandle, bank, program);
+ }
+
+ while (instance->m_eventBuffer.getReadSpace() > 0) {
+
+ snd_seq_event_t *ev = m_groupLocalEventBuffers[index] + counts[index];
+ *ev = instance->m_eventBuffer.peek();
+ bool accept = true;
+
+ RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+
+ int frameOffset = 0;
+ if (evTime > blockTime) {
+ frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped: evTime " << evTime << ", frameOffset " << frameOffset
+ << ", block size " << m_blockSize << std::endl;
+#endif
+
+ if (frameOffset >= int(m_blockSize))
+ break;
+ if (frameOffset < 0)
+ frameOffset = 0;
+
+ ev->time.tick = frameOffset;
+ instance->m_eventBuffer.skip(1);
+
+ if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
+ accept = instance->handleController(ev);
+ } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
+ instance->m_pending.program = ev->data.control.value;
+ accept = false;
+ }
+
+ if (accept) {
+ if (++counts[index] >= EVENT_BUFFER_SIZE)
+ break;
+ }
+ }
+
+ ++index;
+ }
+
+ m_descriptor->run_multiple_synths(index,
+ instances,
+ m_blockSize,
+ m_groupLocalEventBuffers,
+ counts);
+}
+
+
+void
+DSSIPluginInstance::deactivate()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::deactivate " << m_identifier << std::endl;
+#endif
+
+ if (!m_descriptor || !m_descriptor->LADSPA_Plugin->deactivate)
+ return ;
+
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
+ }
+
+ m_descriptor->LADSPA_Plugin->deactivate(m_instanceHandle);
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::deactivate " << m_identifier << " done" << std::endl;
+#endif
+
+ m_bufferScavenger.scavenge();
+}
+
+void
+DSSIPluginInstance::cleanup()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::cleanup " << m_identifier << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ if (!m_descriptor->LADSPA_Plugin->cleanup) {
+ std::cerr << "Bad plugin: plugin id "
+ << m_descriptor->LADSPA_Plugin->UniqueID
+ << ":" << m_descriptor->LADSPA_Plugin->Label
+ << " has no cleanup method!" << std::endl;
+ return ;
+ }
+
+ m_descriptor->LADSPA_Plugin->cleanup(m_instanceHandle);
+ m_instanceHandle = 0;
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::cleanup " << m_identifier << " done" << std::endl;
+#endif
+}
+
+
+
+}
+
+#endif // HAVE_DSSI
+
+
diff --git a/src/sound/DSSIPluginInstance.h b/src/sound/DSSIPluginInstance.h
new file mode 100644
index 0000000..eca6327
--- /dev/null
+++ b/src/sound/DSSIPluginInstance.h
@@ -0,0 +1,193 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _DSSIPLUGININSTANCE_H_
+#define _DSSIPLUGININSTANCE_H_
+
+#include <vector>
+#include <set>
+#include <map>
+#include <qstring.h>
+#include "Instrument.h"
+
+#ifdef HAVE_DSSI
+
+#include <dssi.h>
+#include "RingBuffer.h"
+#include "RunnablePluginInstance.h"
+#include "Scavenger.h"
+#include <pthread.h>
+
+namespace Rosegarden
+{
+
+class DSSIPluginInstance : public RunnablePluginInstance
+{
+public:
+ virtual ~DSSIPluginInstance();
+
+ virtual bool isOK() const { return m_instanceHandle != 0; }
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ virtual QString getIdentifier() const { return m_identifier; }
+ int getPosition() const { return m_position; }
+
+ virtual void run(const RealTime &);
+
+ virtual void setPortValue(unsigned int portNumber, float value);
+ virtual float getPortValue(unsigned int portNumber);
+ virtual QString configure(QString key, QString value);
+ virtual void sendEvent(const RealTime &eventTime,
+ const void *event);
+
+ virtual size_t getBufferSize() { return m_blockSize; }
+ virtual size_t getAudioInputCount() { return m_audioPortsIn.size(); }
+ virtual size_t getAudioOutputCount() { return m_idealChannelCount; }
+ virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; }
+ virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; }
+
+ virtual QStringList getPrograms();
+ virtual QString getCurrentProgram();
+ virtual QString getProgram(int bank, int program);
+ virtual unsigned long getProgram(QString name);
+ virtual void selectProgram(QString program);
+
+ virtual bool isBypassed() const { return m_bypassed; }
+ virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; }
+
+ virtual size_t getLatency();
+
+ virtual void silence();
+ virtual void discardEvents();
+ virtual void setIdealChannelCount(size_t channels); // may re-instantiate
+
+ virtual bool isInGroup() const { return m_grouped; }
+ virtual void detachFromGroup();
+
+protected:
+ // To be constructed only by DSSIPluginFactory
+ friend class DSSIPluginFactory;
+
+ // Constructor that creates the buffers internally
+ //
+ DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const DSSI_Descriptor* descriptor);
+
+ // Constructor that uses shared buffers
+ //
+ DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const DSSI_Descriptor* descriptor);
+
+ void init();
+ void instantiate(unsigned long sampleRate);
+ void cleanup();
+ void activate();
+ void deactivate();
+ void connectPorts();
+
+ bool handleController(snd_seq_event_t *ev);
+ void setPortValueFromController(unsigned int portNumber, int controlValue);
+ void selectProgramAux(QString program, bool backupPortValues);
+ void checkProgramCache();
+
+ void initialiseGroupMembership();
+ void runGrouped(const RealTime &);
+
+ InstrumentId m_instrument;
+ int m_position;
+ LADSPA_Handle m_instanceHandle;
+ const DSSI_Descriptor *m_descriptor;
+
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsIn;
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsOut;
+
+ std::vector<LADSPA_Data> m_backupControlPortsIn;
+ std::vector<bool> m_portChangedSinceProgramChange;
+
+ std::map<int, int> m_controllerMap;
+
+ std::vector<int> m_audioPortsIn;
+ std::vector<int> m_audioPortsOut;
+
+ struct ProgramControl {
+ int msb;
+ int lsb;
+ int program;
+ };
+ ProgramControl m_pending;
+
+ struct ProgramDescriptor {
+ int bank;
+ int program;
+ QString name;
+ };
+ std::vector<ProgramDescriptor> m_cachedPrograms;
+ bool m_programCacheValid;
+
+ RingBuffer<snd_seq_event_t> m_eventBuffer;
+
+ size_t m_blockSize;
+ sample_t **m_inputBuffers;
+ sample_t **m_outputBuffers;
+ bool m_ownBuffers;
+ size_t m_idealChannelCount;
+ size_t m_outputBufferCount;
+ size_t m_sampleRate;
+ float *m_latencyPort;
+
+ bool m_run;
+ bool m_runSinceReset;
+
+ bool m_bypassed;
+ QString m_program;
+ bool m_grouped;
+ RealTime m_lastRunTime;
+
+ pthread_mutex_t m_processLock;
+
+ typedef std::set<DSSIPluginInstance *> PluginSet;
+ typedef std::map<QString, PluginSet> GroupMap;
+ static GroupMap m_groupMap;
+ static snd_seq_event_t **m_groupLocalEventBuffers;
+ static size_t m_groupLocalEventBufferCount;
+
+ static Scavenger<ScavengerArrayWrapper<snd_seq_event_t *> > m_bufferScavenger;
+};
+
+};
+
+#endif // HAVE_DSSI
+
+#endif // _DSSIPLUGININSTANCE_H_
+
diff --git a/src/sound/DummyDriver.h b/src/sound/DummyDriver.h
new file mode 100644
index 0000000..838e7bd
--- /dev/null
+++ b/src/sound/DummyDriver.h
@@ -0,0 +1,166 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "SoundDriver.h"
+
+// An empty sound driver for when we don't want sound support
+// but still want to build the sequencer.
+//
+
+#ifndef _DUMMYDRIVER_H_
+#define _DUMMYDRIVER_H_
+
+namespace Rosegarden
+{
+
+class DummyDriver : public SoundDriver
+{
+public:
+ DummyDriver(MappedStudio *studio):
+ SoundDriver(studio, std::string("DummyDriver - no sound")) { }
+ DummyDriver(MappedStudio *studio, const std::string & name):
+ SoundDriver(studio, std::string("DummyDriver: " + name)) { }
+ virtual ~DummyDriver() { }
+
+ virtual bool initialise() { m_recordComposition.clear(); return true; }
+ virtual void initialisePlayback(const RealTime & /*position*/) { }
+ virtual void stopPlayback() { }
+ virtual void punchOut() { }
+ virtual void resetPlayback(const RealTime & /*old position*/,
+ const RealTime & /*position*/) { }
+ virtual void allNotesOff() { }
+
+ virtual RealTime getSequencerTime() { return RealTime(0, 0);}
+
+ virtual MappedComposition* getMappedComposition()
+ { return &m_recordComposition;}
+
+ virtual void processEventsOut(const MappedComposition & /*mC*/) { }
+
+ virtual void processEventsOut(const MappedComposition &,
+ const RealTime &,
+ const RealTime &) { }
+
+ // Activate a recording state
+ //
+ virtual bool record(RecordStatus /*recordStatus*/,
+ const std::vector<InstrumentId> */*armedInstruments = 0*/,
+ const std::vector<QString> */*audioFileNames = 0*/)
+ { return false; }
+
+ // Process anything that's pending
+ //
+ virtual void processPending() { }
+
+ // Sample rate
+ //
+ virtual unsigned int getSampleRate() const { return 0; }
+
+ // Return the last recorded audio level
+ //
+ virtual float getLastRecordedAudioLevel() { return 0.0; }
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId /*id*/,
+ QString /*pluginIdent*/,
+ int /*position*/) { }
+
+ virtual void removePluginInstance(InstrumentId /*id*/,
+ int /*position*/) { }
+
+ virtual void removePluginInstances() { }
+
+ virtual void setPluginInstancePortValue(InstrumentId /*id*/,
+ int /*position*/,
+ unsigned long /*portNumber*/,
+ float /*value*/) { }
+
+ virtual float getPluginInstancePortValue(InstrumentId ,
+ int ,
+ unsigned long ) { return 0; }
+
+ virtual void setPluginInstanceBypass(InstrumentId /*id*/,
+ int /*position*/,
+ bool /*value*/) { }
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId ,
+ int ) { return QStringList(); }
+
+ virtual QString getPluginInstanceProgram(InstrumentId,
+ int ) { return QString(); }
+
+ virtual QString getPluginInstanceProgram(InstrumentId,
+ int,
+ int,
+ int) { return QString(); }
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId,
+ int ,
+ QString) { return 0; }
+
+ virtual void setPluginInstanceProgram(InstrumentId,
+ int ,
+ QString ) { }
+
+ virtual QString configurePlugin(InstrumentId,
+ int,
+ QString ,
+ QString ) { return QString(); }
+
+ virtual void setAudioBussLevels(int ,
+ float ,
+ float ) { }
+
+ virtual void setAudioInstrumentLevels(InstrumentId,
+ float,
+ float) { }
+
+ virtual bool checkForNewClients() { return false; }
+
+ virtual void setLoop(const RealTime &/*loopStart*/,
+ const RealTime &/*loopEnd*/) { }
+
+ virtual std::vector<PlayableAudioFile*> getPlayingAudioFiles()
+ { return std::vector<PlayableAudioFile*>(); }
+
+ virtual void getAudioInstrumentNumbers(InstrumentId &i, int &n) {
+ i = 0; n = 0;
+ }
+ virtual void getSoftSynthInstrumentNumbers(InstrumentId &i, int &n) {
+ i = 0; n = 0;
+ }
+
+ virtual void claimUnwantedPlugin(void *plugin) { }
+ virtual void scavengePlugins() { }
+
+ virtual bool areClocksRunning() const { return true; }
+
+protected:
+ virtual void processMidiOut(const MappedComposition & /*mC*/,
+ const RealTime &, const RealTime &) { }
+ virtual void generateInstruments() { }
+
+};
+
+}
+
+#endif // _DUMMYDRIVER_H_
+
diff --git a/src/sound/ExternalTransport.h b/src/sound/ExternalTransport.h
new file mode 100644
index 0000000..f40a5a2
--- /dev/null
+++ b/src/sound/ExternalTransport.h
@@ -0,0 +1,67 @@
+// -*- c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _EXTERNAL_TRANSPORT_H_
+#define _EXTERNAL_TRANSPORT_H_
+
+namespace Rosegarden {
+
+/**
+ * Simple interface that we can pass to low-level audio code and on
+ * which it can call back when something external requests a transport
+ * change. The callback is asynchronous, and there's a method for the
+ * low-level code to use to find out whether its request has finished
+ * synchronising yet.
+ *
+ * (Each of the transportXX functions returns a token which can then
+ * be passed to isTransportSyncComplete.)
+ */
+
+class ExternalTransport
+{
+public:
+ typedef unsigned long TransportToken;
+
+ enum TransportRequest {
+ TransportNoChange,
+ TransportStop,
+ TransportStart,
+ TransportPlay,
+ TransportRecord,
+ TransportJumpToTime, // time arg required
+ TransportStartAtTime, // time arg required
+ TransportStopAtTime // time arg required
+ };
+
+ virtual TransportToken transportChange(TransportRequest) = 0;
+ virtual TransportToken transportJump(TransportRequest, RealTime) = 0;
+
+ virtual bool isTransportSyncComplete(TransportToken token) = 0;
+
+ // The value returned here is a constant (within the context of a
+ // particular ExternalTransport object) that is guaranteed never
+ // to be returned by any of the transport request methods.
+ virtual TransportToken getInvalidTransportToken() const = 0;
+};
+
+}
+
+#endif
+
diff --git a/src/sound/JackDriver.cpp b/src/sound/JackDriver.cpp
new file mode 100644
index 0000000..24eb6fe
--- /dev/null
+++ b/src/sound/JackDriver.cpp
@@ -0,0 +1,2480 @@
+
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "JackDriver.h"
+#include "AlsaDriver.h"
+#include "MappedStudio.h"
+#include "AudioProcess.h"
+#include "Profiler.h"
+#include "AudioLevel.h"
+#include "Audit.h"
+#include "PluginFactory.h"
+
+#ifdef HAVE_ALSA
+#ifdef HAVE_LIBJACK
+
+//#define DEBUG_JACK_DRIVER 1
+//#define DEBUG_JACK_TRANSPORT 1
+//#define DEBUG_JACK_PROCESS 1
+//#define DEBUG_JACK_XRUN 1
+
+namespace Rosegarden
+{
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+static unsigned long framesThisPlay = 0;
+static RealTime startTime;
+#endif
+
+JackDriver::JackDriver(AlsaDriver *alsaDriver) :
+ m_client(0),
+ m_bufferSize(0),
+ m_sampleRate(0),
+ m_tempOutBuffer(0),
+ m_jackTransportEnabled(false),
+ m_jackTransportMaster(false),
+ m_waiting(false),
+ m_waitingState(JackTransportStopped),
+ m_waitingToken(0),
+ m_ignoreProcessTransportCount(0),
+ m_bussMixer(0),
+ m_instrumentMixer(0),
+ m_fileReader(0),
+ m_fileWriter(0),
+ m_alsaDriver(alsaDriver),
+ m_masterLevel(1.0),
+ m_directMasterAudioInstruments(0L),
+ m_directMasterSynthInstruments(0L),
+ m_haveAsyncAudioEvent(false),
+ m_kickedOutAt(0),
+ m_framesProcessed(0),
+ m_ok(false)
+{
+ assert(sizeof(sample_t) == sizeof(float));
+ initialise();
+}
+
+JackDriver::~JackDriver()
+{
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver" << std::endl;
+#endif
+
+ m_ok = false; // prevent any more work in process()
+
+ if (m_client) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::shutdown - deactivating JACK client"
+ << std::endl;
+#endif
+
+ if (jack_deactivate(m_client)) {
+ std::cerr << "JackDriver::shutdown - deactivation failed"
+ << std::endl;
+ }
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating buss mixer" << std::endl;
+#endif
+
+ AudioBussMixer *bussMixer = m_bussMixer;
+ m_bussMixer = 0;
+ if (bussMixer)
+ bussMixer->terminate();
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating instrument mixer" << std::endl;
+#endif
+
+ AudioInstrumentMixer *instrumentMixer = m_instrumentMixer;
+ m_instrumentMixer = 0;
+ if (instrumentMixer) {
+ instrumentMixer->terminate();
+ instrumentMixer->destroyAllPlugins();
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating file reader" << std::endl;
+#endif
+
+ AudioFileReader *reader = m_fileReader;
+ m_fileReader = 0;
+ if (reader)
+ reader->terminate();
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating file writer" << std::endl;
+#endif
+
+ AudioFileWriter *writer = m_fileWriter;
+ m_fileWriter = 0;
+ if (writer)
+ writer->terminate();
+
+ if (m_client) {
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::shutdown - tearing down JACK client"
+ << std::endl;
+#endif
+
+ for (unsigned int i = 0; i < m_inputPorts.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering input " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_inputPorts[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister input port " << i + 1
+ << std::endl;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_outputSubmasters.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering output sub " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_outputSubmasters[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister output submaster " << i + 1 << std::endl;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_outputMonitors.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering output mon " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_outputMonitors[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister output monitor " << i + 1 << std::endl;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_outputMasters.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering output master " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_outputMasters[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister output master " << i + 1 << std::endl;
+ }
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "closing client" << std::endl;
+#endif
+
+ jack_client_close(m_client);
+ std::cerr << "done" << std::endl;
+ m_client = 0;
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver: deleting mixers etc" << std::endl;
+#endif
+
+ delete bussMixer;
+ delete instrumentMixer;
+ delete reader;
+ delete writer;
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::~JackDriver exiting" << std::endl;
+#endif
+}
+
+void
+JackDriver::initialise(bool reinitialise)
+{
+ m_ok = false;
+
+ Audit audit;
+ audit << std::endl;
+
+ std::string jackClientName = "rosegarden";
+
+ // attempt connection to JACK server
+ //
+ if ((m_client = jack_client_new(jackClientName.c_str())) == 0) {
+ audit << "JackDriver::initialiseAudio - "
+ << "JACK server not running"
+ << std::endl;
+ return ;
+ }
+
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_alsaDriver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+ for (InstrumentId id = instrumentBase;
+ id < instrumentBase + instrumentCount; ++id) {
+ // prefill so that we can refer to the map without a lock (as
+ // the number of instruments won't change)
+ m_recordInputs[id] = RecordInputDesc(1000, -1, 0.0);
+ }
+
+ // set callbacks
+ //
+ jack_set_process_callback(m_client, jackProcessStatic, this);
+ jack_set_buffer_size_callback(m_client, jackBufferSize, this);
+ jack_set_sample_rate_callback(m_client, jackSampleRate, this);
+ jack_on_shutdown(m_client, jackShutdown, this);
+ jack_set_xrun_callback(m_client, jackXRun, this);
+ jack_set_sync_callback(m_client, jackSyncCallback, this);
+
+ // get and report the sample rate and buffer size
+ //
+ m_sampleRate = jack_get_sample_rate(m_client);
+ m_bufferSize = jack_get_buffer_size(m_client);
+
+ audit << "JackDriver::initialiseAudio - JACK sample rate = "
+ << m_sampleRate << "Hz, buffer size = " << m_bufferSize
+ << std::endl;
+
+ PluginFactory::setSampleRate(m_sampleRate);
+
+ // Get the initial buffer size before we activate the client
+ //
+
+ if (!reinitialise) {
+
+ // create processing buffer(s)
+ //
+ m_tempOutBuffer = new sample_t[m_bufferSize];
+
+ audit << "JackDriver::initialiseAudio - "
+ << "creating disk thread" << std::endl;
+
+ m_fileReader = new AudioFileReader(m_alsaDriver, m_sampleRate);
+ m_fileWriter = new AudioFileWriter(m_alsaDriver, m_sampleRate);
+ m_instrumentMixer = new AudioInstrumentMixer
+ (m_alsaDriver, m_fileReader, m_sampleRate, m_bufferSize);
+ m_bussMixer = new AudioBussMixer
+ (m_alsaDriver, m_instrumentMixer, m_sampleRate, m_bufferSize);
+ m_instrumentMixer->setBussMixer(m_bussMixer);
+
+ // We run the file reader whatever, but we only run the other
+ // threads (instrument mixer, buss mixer, file writer) when we
+ // actually need them. (See updateAudioData and createRecordFile.)
+ m_fileReader->run();
+ }
+
+ // Create and connect the default numbers of ports. We always create
+ // one stereo pair each of master and monitor outs, and then we create
+ // record ins, fader outs and submaster outs according to the user's
+ // preferences. Since we don't know the user's preferences yet, we'll
+ // start by creating one pair of record ins and no fader or submaster
+ // outs.
+ //
+ m_outputMasters.clear();
+ m_outputMonitors.clear();
+ m_outputSubmasters.clear();
+ m_outputInstruments.clear();
+ m_inputPorts.clear();
+
+ if (!createMainOutputs()) { // one stereo pair master, one pair monitor
+ audit << "JackDriver::initialise - "
+ << "failed to create main outputs!" << std::endl;
+ return ;
+ }
+
+ if (!createRecordInputs(1)) {
+ audit << "JackDriver::initialise - "
+ << "failed to create record inputs!" << std::endl;
+ return ;
+ }
+
+ if (jack_activate(m_client)) {
+ audit << "JackDriver::initialise - "
+ << "client activation failed" << std::endl;
+ return ;
+ }
+
+ // Now set up the default connections.
+
+ std::string playback_1, playback_2;
+
+ const char **ports =
+ jack_get_ports(m_client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsInput);
+
+ if (ports) {
+ if (ports[0])
+ playback_1 = std::string(ports[0]);
+ if (ports[1])
+ playback_2 = std::string(ports[1]);
+
+ // count ports
+ unsigned int i = 0;
+ for (i = 0; ports[i]; i++)
+ ;
+ audit << "JackDriver::initialiseAudio - "
+ << "found " << i << " JACK physical outputs"
+ << std::endl;
+ } else
+ audit << "JackDriver::initialiseAudio - "
+ << "no JACK physical outputs found"
+ << std::endl;
+ free(ports);
+
+ if (playback_1 != "") {
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << jack_port_name(m_outputMasters[0])
+ << "\" to \"" << playback_1.c_str() << "\""
+ << std::endl;
+
+ // connect our client up to the ALSA ports - first left output
+ //
+ if (jack_connect(m_client, jack_port_name(m_outputMasters[0]),
+ playback_1.c_str())) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ return ;
+ }
+
+ /*
+ if (jack_connect(m_client, jack_port_name(m_outputMonitors[0]),
+ playback_1.c_str()))
+ {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ return;
+ }
+ */
+ }
+
+ if (playback_2 != "") {
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << jack_port_name(m_outputMasters[1])
+ << "\" to \"" << playback_2.c_str() << "\""
+ << std::endl;
+
+ if (jack_connect(m_client, jack_port_name(m_outputMasters[1]),
+ playback_2.c_str())) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ }
+
+ /*
+ if (jack_connect(m_client, jack_port_name(m_outputMonitors[1]),
+ playback_2.c_str()))
+ {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ }
+ */
+ }
+
+
+ std::string capture_1, capture_2;
+
+ ports =
+ jack_get_ports(m_client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsOutput);
+
+ if (ports) {
+ if (ports[0])
+ capture_1 = std::string(ports[0]);
+ if (ports[1])
+ capture_2 = std::string(ports[1]);
+
+ // count ports
+ unsigned int i = 0;
+ for (i = 0; ports[i]; i++)
+ ;
+ audit << "JackDriver::initialiseAudio - "
+ << "found " << i << " JACK physical inputs"
+ << std::endl;
+ } else
+ audit << "JackDriver::initialiseAudio - "
+ << "no JACK physical inputs found"
+ << std::endl;
+ free(ports);
+
+ if (capture_1 != "") {
+
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << capture_1.c_str()
+ << "\" to \"" << jack_port_name(m_inputPorts[0]) << "\""
+ << std::endl;
+
+ if (jack_connect(m_client, capture_1.c_str(),
+ jack_port_name(m_inputPorts[0]))) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK input port" << std::endl;
+ }
+ }
+
+ if (capture_2 != "") {
+
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << capture_2.c_str()
+ << "\" to \"" << jack_port_name(m_inputPorts[1]) << "\""
+ << std::endl;
+
+ if (jack_connect(m_client, capture_2.c_str(),
+ jack_port_name(m_inputPorts[1]))) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK input port" << std::endl;
+ }
+ }
+
+ audit << "JackDriver::initialiseAudio - "
+ << "initialised JACK audio subsystem"
+ << std::endl;
+
+ m_ok = true;
+}
+
+bool
+JackDriver::createMainOutputs()
+{
+ if (!m_client)
+ return false;
+
+ jack_port_t *port = jack_port_register
+ (m_client, "master out L",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMasters.push_back(port);
+
+ port = jack_port_register
+ (m_client, "master out R",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMasters.push_back(port);
+
+ port = jack_port_register
+ (m_client, "record monitor out L",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMonitors.push_back(port);
+
+ port = jack_port_register
+ (m_client, "record monitor out R",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMonitors.push_back(port);
+
+ return true;
+}
+
+bool
+JackDriver::createFaderOutputs(int audioPairs, int synthPairs)
+{
+ if (!m_client)
+ return false;
+
+ int pairs = audioPairs + synthPairs;
+ int pairsNow = m_outputInstruments.size() / 2;
+ if (pairs == pairsNow)
+ return true;
+
+ for (int i = pairsNow; i < pairs; ++i) {
+
+ char namebuffer[22];
+ jack_port_t *port;
+
+ if (i < audioPairs) {
+ snprintf(namebuffer, 21, "audio fader %d out L", i + 1);
+ } else {
+ snprintf(namebuffer, 21, "synth fader %d out L", i - audioPairs + 1);
+ }
+
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputInstruments.push_back(port);
+
+ if (i < audioPairs) {
+ snprintf(namebuffer, 21, "audio fader %d out R", i + 1);
+ } else {
+ snprintf(namebuffer, 21, "synth fader %d out R", i - audioPairs + 1);
+ }
+
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputInstruments.push_back(port);
+ }
+
+ while ((int)m_outputInstruments.size() > pairs * 2) {
+ std::vector<jack_port_t *>::iterator itr = m_outputInstruments.end();
+ --itr;
+ jack_port_unregister(m_client, *itr);
+ m_outputInstruments.erase(itr);
+ }
+
+ return true;
+}
+
+bool
+JackDriver::createSubmasterOutputs(int pairs)
+{
+ if (!m_client)
+ return false;
+
+ int pairsNow = m_outputSubmasters.size() / 2;
+ if (pairs == pairsNow)
+ return true;
+
+ for (int i = pairsNow; i < pairs; ++i) {
+
+ char namebuffer[22];
+ jack_port_t *port;
+
+ snprintf(namebuffer, 21, "submaster %d out L", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputSubmasters.push_back(port);
+
+ snprintf(namebuffer, 21, "submaster %d out R", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputSubmasters.push_back(port);
+ }
+
+ while ((int)m_outputSubmasters.size() > pairs * 2) {
+ std::vector<jack_port_t *>::iterator itr = m_outputSubmasters.end();
+ --itr;
+ jack_port_unregister(m_client, *itr);
+ m_outputSubmasters.erase(itr);
+ }
+
+ return true;
+}
+
+bool
+JackDriver::createRecordInputs(int pairs)
+{
+ if (!m_client)
+ return false;
+
+ int pairsNow = m_inputPorts.size() / 2;
+ if (pairs == pairsNow)
+ return true;
+
+ for (int i = pairsNow; i < pairs; ++i) {
+
+ char namebuffer[22];
+ jack_port_t *port;
+
+ snprintf(namebuffer, 21, "record in %d L", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput,
+ 0);
+ if (!port)
+ return false;
+ m_inputPorts.push_back(port);
+
+ snprintf(namebuffer, 21, "record in %d R", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput,
+ 0);
+ if (!port)
+ return false;
+ m_inputPorts.push_back(port);
+ }
+
+ while ((int)m_outputSubmasters.size() > pairs * 2) {
+ std::vector<jack_port_t *>::iterator itr = m_outputSubmasters.end();
+ --itr;
+ jack_port_unregister(m_client, *itr);
+ m_outputSubmasters.erase(itr);
+ }
+
+ return true;
+}
+
+
+void
+JackDriver::setAudioPorts(bool faderOuts, bool submasterOuts)
+{
+ if (!m_client)
+ return ;
+
+ Audit audit;
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::setAudioPorts(" << faderOuts << "," << submasterOuts << ")" << std::endl;
+#endif
+
+ if (!m_client) {
+ std::cerr << "JackDriver::setAudioPorts(" << faderOuts << "," << submasterOuts << "): no client yet" << std::endl;
+ return ;
+ }
+
+ if (faderOuts) {
+ InstrumentId instrumentBase;
+ int audioInstruments;
+ int synthInstruments;
+ m_alsaDriver->getAudioInstrumentNumbers(instrumentBase, audioInstruments);
+ m_alsaDriver->getSoftSynthInstrumentNumbers(instrumentBase, synthInstruments);
+ if (!createFaderOutputs(audioInstruments, synthInstruments)) {
+ m_ok = false;
+ audit << "Failed to create fader outs!" << std::endl;
+ return ;
+ }
+ } else {
+ createFaderOutputs(0, 0);
+ }
+
+ if (submasterOuts) {
+
+ // one fewer than returned here, because the master has a buss object too
+ if (!createSubmasterOutputs
+ (m_alsaDriver->getMappedStudio()->getObjectCount
+ (MappedObject::AudioBuss) - 1)) {
+ m_ok = false;
+ audit << "Failed to create submaster outs!" << std::endl;
+ return ;
+ }
+
+ } else {
+ createSubmasterOutputs(0);
+ }
+}
+
+RealTime
+JackDriver::getAudioPlayLatency() const
+{
+ if (!m_client)
+ return RealTime::zeroTime;
+
+ jack_nframes_t latency =
+ jack_port_get_total_latency(m_client, m_outputMasters[0]);
+
+ return RealTime::frame2RealTime(latency, m_sampleRate);
+}
+
+RealTime
+JackDriver::getAudioRecordLatency() const
+{
+ if (!m_client)
+ return RealTime::zeroTime;
+
+ jack_nframes_t latency =
+ jack_port_get_total_latency(m_client, m_inputPorts[0]);
+
+ return RealTime::frame2RealTime(latency, m_sampleRate);
+}
+
+RealTime
+JackDriver::getInstrumentPlayLatency(InstrumentId id) const
+{
+ if (m_instrumentLatencies.find(id) == m_instrumentLatencies.end()) {
+ return RealTime::zeroTime;
+ } else {
+ return m_instrumentLatencies.find(id)->second;
+ }
+}
+
+RealTime
+JackDriver::getMaximumPlayLatency() const
+{
+ return m_maxInstrumentLatency;
+}
+
+int
+JackDriver::jackProcessStatic(jack_nframes_t nframes, void *arg)
+{
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+ if (inst)
+ return inst->jackProcess(nframes);
+ else
+ return 0;
+}
+
+int
+JackDriver::jackProcess(jack_nframes_t nframes)
+{
+ if (!m_ok || !m_client) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: not OK" << std::endl;
+#endif
+
+ return 0;
+ }
+
+ if (!m_bussMixer) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: no buss mixer" << std::endl;
+#endif
+
+ return jackProcessEmpty(nframes);
+ }
+
+ if (m_alsaDriver->areClocksRunning()) {
+ m_alsaDriver->checkTimerSync(m_framesProcessed);
+ } else {
+ m_alsaDriver->checkTimerSync(0);
+ }
+
+ bool lowLatencyMode = m_alsaDriver->getLowLatencyMode();
+ bool clocksRunning = m_alsaDriver->areClocksRunning();
+ bool playing = m_alsaDriver->isPlaying();
+ bool asyncAudio = m_haveAsyncAudioEvent;
+
+#ifdef DEBUG_JACK_PROCESS
+
+ Profiler profiler("jackProcess", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler("jackProcess", false);
+#endif
+#endif
+
+ if (lowLatencyMode) {
+ if (clocksRunning) {
+ if (playing || asyncAudio) {
+
+ if (m_instrumentMixer->tryLock() == 0) {
+ m_instrumentMixer->kick(false);
+ m_instrumentMixer->releaseLock();
+ //#ifdef DEBUG_JACK_PROCESS
+ } else {
+ std::cerr << "JackDriver::jackProcess: no instrument mixer lock available" << std::endl;
+ //#endif
+ }
+ if (m_bussMixer->getBussCount() > 0) {
+ if (m_bussMixer->tryLock() == 0) {
+ m_bussMixer->kick(false, false);
+ m_bussMixer->releaseLock();
+ //#ifdef DEBUG_JACK_PROCESS
+ } else {
+ std::cerr << "JackDriver::jackProcess: no buss mixer lock available" << std::endl;
+ //#endif
+ }
+ }
+ }
+ }
+ }
+
+ if (jack_cpu_load(m_client) > 97.0) {
+ reportFailure(MappedEvent::FailureCPUOverload);
+ return jackProcessEmpty(nframes);
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ Profiler profiler2("jackProcess post mix", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler2("jackProcess post mix", false);
+#endif
+#endif
+
+ SequencerDataBlock *sdb = m_alsaDriver->getSequencerDataBlock();
+
+ jack_position_t position;
+ jack_transport_state_t state = JackTransportRolling;
+ bool doneRecord = false;
+
+ int ignoreCount = m_ignoreProcessTransportCount;
+ if (ignoreCount > 0)
+ --m_ignoreProcessTransportCount;
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_alsaDriver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ if (m_jackTransportEnabled) {
+
+ state = jack_transport_query(m_client, &position);
+
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcess: JACK transport state is " << state << std::endl;
+#endif
+
+ if (state == JackTransportStopped) {
+ if (playing && clocksRunning && !m_waiting) {
+ ExternalTransport *transport =
+ m_alsaDriver->getExternalTransportControl();
+ if (transport) {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackProcess: JACK transport stopped externally at " << position.frame << std::endl;
+#endif
+
+ m_waitingToken =
+ transport->transportJump
+ (ExternalTransport::TransportStopAtTime,
+ RealTime::frame2RealTime(position.frame,
+ position.frame_rate));
+ }
+ } else if (clocksRunning) {
+ if (!asyncAudio) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: no interesting async events" << std::endl;
+#endif
+ // do this before record monitor, otherwise we lose monitor out
+ jackProcessEmpty(nframes);
+ }
+
+ // for monitoring:
+ int rv = 0;
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ int irv = jackProcessRecord(id, nframes, 0, 0, clocksRunning);
+ if (irv != 0)
+ rv = irv;
+ }
+ doneRecord = true;
+
+ if (!asyncAudio) {
+ return rv;
+ }
+
+ } else {
+ return jackProcessEmpty(nframes);
+ }
+ } else if (state == JackTransportStarting) {
+ return jackProcessEmpty(nframes);
+ } else if (state != JackTransportRolling) {
+ std::cerr << "JackDriver::jackProcess: unexpected JACK transport state " << state << std::endl;
+ }
+ }
+
+ if (state == JackTransportRolling) { // also covers not-on-transport case
+ if (m_waiting) {
+ if (ignoreCount > 0) {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackProcess: transport rolling, but we're ignoring it (count = " << ignoreCount << ")" << std::endl;
+#endif
+
+ } else {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackProcess: transport rolling, telling ALSA driver to go!" << std::endl;
+#endif
+
+ m_alsaDriver->startClocksApproved();
+ m_waiting = false;
+ }
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess (rolling or not on JACK transport)" << std::endl;
+#endif
+
+ if (!clocksRunning) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: clocks stopped" << std::endl;
+#endif
+
+ return jackProcessEmpty(nframes);
+
+ } else if (!playing) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: not playing" << std::endl;
+#endif
+
+ if (!asyncAudio) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: no interesting async events" << std::endl;
+#endif
+ // do this before record monitor, otherwise we lose monitor out
+ jackProcessEmpty(nframes);
+ }
+
+ // for monitoring:
+ int rv = 0;
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ int irv = jackProcessRecord(id, nframes, 0, 0, clocksRunning);
+ if (irv != 0)
+ rv = irv;
+ }
+ doneRecord = true;
+
+ if (!asyncAudio) {
+ return rv;
+ }
+ }
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ Profiler profiler3("jackProcess post transport", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler3("jackProcess post transport", false);
+#endif
+#endif
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_alsaDriver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ // We always have the master out
+
+ sample_t *master[2] = {
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[0], nframes)),
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[1], nframes))
+ };
+
+ memset(master[0], 0, nframes * sizeof(sample_t));
+ memset(master[1], 0, nframes * sizeof(sample_t));
+
+ // Reset monitor outs (if present) here prior to mixing
+
+ if (m_outputMonitors.size() > 0) {
+ sample_t *buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ if (m_outputMonitors.size() > 1) {
+ sample_t *buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ int bussCount = m_bussMixer->getBussCount();
+
+ // If we have any busses, then we just mix from them (but we still
+ // need to keep ourselves up to date by reading and monitoring the
+ // instruments). If we have no busses, mix direct from instruments.
+
+ for (int buss = 0; buss < bussCount; ++buss) {
+
+ sample_t *submaster[2] = { 0, 0 };
+ sample_t peak[2] = { 0.0, 0.0 };
+
+ if ((int)m_outputSubmasters.size() > buss * 2 + 1) {
+ submaster[0] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputSubmasters[buss * 2], nframes));
+ submaster[1] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputSubmasters[buss * 2 + 1], nframes));
+ }
+
+ if (!submaster[0])
+ submaster[0] = m_tempOutBuffer;
+ if (!submaster[1])
+ submaster[1] = m_tempOutBuffer;
+
+ for (int ch = 0; ch < 2; ++ch) {
+
+ RingBuffer<AudioBussMixer::sample_t> *rb =
+ m_bussMixer->getRingBuffer(buss, ch);
+
+ if (!rb || m_bussMixer->isBussDormant(buss)) {
+ if (rb)
+ rb->skip(nframes);
+ if (submaster[ch])
+ memset(submaster[ch], 0, nframes * sizeof(sample_t));
+ } else {
+ size_t actual = rb->read(submaster[ch], nframes);
+ if (actual < nframes) {
+ reportFailure(MappedEvent::FailureBussMixUnderrun);
+ }
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = submaster[ch][i];
+ if (sample > peak[ch])
+ peak[ch] = sample;
+ master[ch][i] += sample;
+ }
+ }
+ }
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (peak[0], 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (peak[1], 127, AudioLevel::LongFader);
+
+ sdb->setSubmasterLevel(buss, info);
+ }
+
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ if (buss + 1 == m_recordInputs[id].input) {
+ jackProcessRecord(id, nframes, submaster[0], submaster[1], clocksRunning);
+ }
+ }
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: have " << audioInstruments << " audio and " << synthInstruments << " synth instruments and " << bussCount << " busses" << std::endl;
+#endif
+
+ bool allInstrumentsDormant = true;
+ static RealTime dormantTime = RealTime::zeroTime;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ sample_t *instrument[2] = { 0, 0 };
+ sample_t peak[2] = { 0.0, 0.0 };
+
+ if (int(m_outputInstruments.size()) > i * 2 + 1) {
+ instrument[0] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputInstruments[i * 2], nframes));
+ instrument[1] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputInstruments[i * 2 + 1], nframes));
+ }
+
+ if (!instrument[0])
+ instrument[0] = m_tempOutBuffer;
+ if (!instrument[1])
+ instrument[1] = m_tempOutBuffer;
+
+ for (int ch = 0; ch < 2; ++ch) {
+
+ // We always need to read from an instrument's ring buffer
+ // to keep the instrument moving along, as well as for
+ // monitoring. If the instrument is connected straight to
+ // the master, then we also need to mix from it. (We have
+ // that information cached courtesy of updateAudioData.)
+
+ bool directToMaster = false;
+ if (i < audioInstruments) {
+ directToMaster = (m_directMasterAudioInstruments & (1 << i));
+ } else {
+ directToMaster = (m_directMasterSynthInstruments &
+ (1 << (i - audioInstruments)));
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ if (id == 1000 || id == 10000) {
+ std::cerr << "JackDriver::jackProcess: instrument id " << id << ", base " << audioInstrumentBase << ", direct masters " << m_directMasterAudioInstruments << ": " << directToMaster << std::endl;
+ }
+#endif
+
+ RingBuffer<AudioInstrumentMixer::sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (!rb || m_instrumentMixer->isInstrumentDormant(id)) {
+#ifdef DEBUG_JACK_PROCESS
+ if (id == 1000 || id == 10000) {
+ if (rb) {
+ std::cerr << "JackDriver::jackProcess: instrument " << id << " dormant" << std::endl;
+ } else {
+ std::cerr << "JackDriver::jackProcess: instrument " << id << " has no ring buffer for channel " << ch << std::endl;
+ }
+ }
+#endif
+ if (rb)
+ rb->skip(nframes);
+ if (instrument[ch])
+ memset(instrument[ch], 0, nframes * sizeof(sample_t));
+
+ } else {
+
+ allInstrumentsDormant = false;
+
+ size_t actual = rb->read(instrument[ch], nframes);
+
+#ifdef DEBUG_JACK_PROCESS
+
+ if (id == 1000) {
+ std::cerr << "JackDriver::jackProcess: read " << actual << " of " << nframes << " frames for instrument " << id << " channel " << ch << std::endl;
+ }
+#endif
+
+ if (actual < nframes) {
+
+ std::cerr << "JackDriver::jackProcess: read " << actual << " of " << nframes << " frames for " << id << " ch " << ch << " (pl " << playing << ", cl " << clocksRunning << ", aa " << asyncAudio << ")" << std::endl;
+
+ reportFailure(MappedEvent::FailureMixUnderrun);
+ }
+ for (size_t f = 0; f < nframes; ++f) {
+ sample_t sample = instrument[ch][f];
+ if (sample > peak[ch])
+ peak[ch] = sample;
+ if (directToMaster)
+ master[ch][f] += sample;
+ }
+ }
+
+ // If the instrument is connected straight to master we
+ // also need to skip() on the buss mixer's reader for it,
+ // otherwise it'll block because the buss mixer isn't
+ // needing to read it.
+
+ if (rb && directToMaster) {
+ rb->skip(nframes, 1); // 1 is the buss mixer's reader (magic)
+ }
+ }
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (peak[0], 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (peak[1], 127, AudioLevel::LongFader);
+
+ sdb->setInstrumentLevel(id, info);
+ }
+ }
+
+ if (asyncAudio) {
+ if (!allInstrumentsDormant) {
+ dormantTime = RealTime::zeroTime;
+ } else {
+ dormantTime = dormantTime +
+ RealTime::frame2RealTime(m_bufferSize, m_sampleRate);
+ if (dormantTime > RealTime(10, 0)) {
+ std::cerr << "JackDriver: dormantTime = " << dormantTime << ", resetting m_haveAsyncAudioEvent" << std::endl;
+ m_haveAsyncAudioEvent = false;
+ }
+ }
+ }
+
+ // Get master fader levels. There's no pan on the master.
+ float gain = AudioLevel::dB_to_multiplier(m_masterLevel);
+ float masterPeak[2] = { 0.0, 0.0 };
+
+ for (int ch = 0; ch < 2; ++ch) {
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = master[ch][i] * gain;
+ if (sample > masterPeak[ch])
+ masterPeak[ch] = sample;
+ master[ch][i] = sample;
+ }
+ }
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (masterPeak[0], 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (masterPeak[1], 127, AudioLevel::LongFader);
+
+ sdb->setMasterLevel(info);
+ }
+
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ if (m_recordInputs[id].input == 0) {
+ jackProcessRecord(id, nframes, master[0], master[1], clocksRunning);
+ } else if (m_recordInputs[id].input < 1000) { // buss, already done
+ // nothing
+ } else if (!doneRecord) {
+ jackProcessRecord(id, nframes, 0, 0, clocksRunning);
+ }
+ }
+
+ if (playing) {
+ if (!lowLatencyMode) {
+ if (m_bussMixer->getBussCount() == 0) {
+ m_instrumentMixer->signal();
+ } else {
+ m_bussMixer->signal();
+ }
+ }
+ }
+
+ m_framesProcessed += nframes;
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+
+ framesThisPlay += nframes; //!!!
+#endif
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcess: " << nframes << " frames, " << framesThisPlay << " this play, " << m_framesProcessed << " total" << std::endl;
+#endif
+
+ return 0;
+}
+
+int
+JackDriver::jackProcessEmpty(jack_nframes_t nframes)
+{
+ sample_t *buffer;
+
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcessEmpty" << std::endl;
+#endif
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[0], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[1], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ for (unsigned int i = 0; i < m_outputSubmasters.size(); ++i) {
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputSubmasters[i], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ for (unsigned int i = 0; i < m_outputInstruments.size(); ++i) {
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputInstruments[i], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ m_framesProcessed += nframes;
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+
+ framesThisPlay += nframes;
+#endif
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcess: " << nframes << " frames, " << framesThisPlay << " this play, " << m_framesProcessed << " total" << std::endl;
+#endif
+
+ return 0;
+}
+
+int
+JackDriver::jackProcessRecord(InstrumentId id,
+ jack_nframes_t nframes,
+ sample_t *sourceBufferLeft,
+ sample_t *sourceBufferRight,
+ bool clocksRunning)
+{
+#ifdef DEBUG_JACK_PROCESS
+ Profiler profiler("jackProcessRecord", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler("jackProcessRecord", false);
+#endif
+#endif
+
+ SequencerDataBlock *sdb = m_alsaDriver->getSequencerDataBlock();
+ bool wroteSomething = false;
+ sample_t peakLeft = 0.0, peakRight = 0.0;
+
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): clocksRunning " << clocksRunning << std::endl;
+#endif
+
+ // Get input buffers
+ //
+ sample_t *inputBufferLeft = 0, *inputBufferRight = 0;
+
+ int recInput = m_recordInputs[id].input;
+
+ int channel = m_recordInputs[id].channel;
+ int channels = (channel == -1 ? 2 : 1);
+ if (channels == 2)
+ channel = 0;
+
+ float level = m_recordInputs[id].level;
+
+ if (sourceBufferLeft) {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): buss input provided" << std::endl;
+#endif
+
+ inputBufferLeft = sourceBufferLeft;
+ if (sourceBufferRight)
+ inputBufferRight = sourceBufferRight;
+
+ } else if (recInput < 1000) {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): no known input" << std::endl;
+#endif
+
+ return 0;
+
+ } else {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): record input " << recInput << std::endl;
+#endif
+
+ int input = recInput - 1000;
+
+ int port = input * channels + channel;
+ int portRight = input * channels + 1;
+
+ if (port < int(m_inputPorts.size())) {
+ inputBufferLeft = static_cast<sample_t*>
+ (jack_port_get_buffer(m_inputPorts[port], nframes));
+ }
+
+ if (channels == 2 && portRight < int(m_inputPorts.size())) {
+ inputBufferRight = static_cast<sample_t*>
+ (jack_port_get_buffer(m_inputPorts[portRight], nframes));
+ }
+ }
+
+ float gain = AudioLevel::dB_to_multiplier(level);
+
+ if (m_alsaDriver->getRecordStatus() == RECORD_ON &&
+ clocksRunning &&
+ m_fileWriter->haveRecordFileOpen(id)) {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): recording" << std::endl;
+#endif
+
+ memset(m_tempOutBuffer, 0, nframes * sizeof(sample_t));
+
+ if (inputBufferLeft) {
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferLeft[i] * gain;
+ if (sample > peakLeft)
+ peakLeft = sample;
+ m_tempOutBuffer[i] = sample;
+ }
+
+ if (m_outputMonitors.size() > 0) {
+ sample_t *buf =
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ if (buf) {
+ for (size_t i = 0; i < nframes; ++i) {
+ buf[i] += m_tempOutBuffer[i];
+ }
+ }
+ }
+
+ m_fileWriter->write(id, m_tempOutBuffer, 0, nframes);
+ }
+
+ if (channels == 2) {
+
+ if (inputBufferRight) {
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferRight[i] * gain;
+ if (sample > peakRight)
+ peakRight = sample;
+ m_tempOutBuffer[i] = sample;
+ }
+ if (m_outputMonitors.size() > 1) {
+ sample_t *buf =
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ if (buf) {
+ for (size_t i = 0; i < nframes; ++i) {
+ buf[i] += m_tempOutBuffer[i];
+ }
+ }
+ }
+ }
+
+ m_fileWriter->write(id, m_tempOutBuffer, 1, nframes);
+ }
+
+ wroteSomething = true;
+
+ } else {
+
+ // want peak levels and monitors anyway, even if not recording
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): monitoring only" << std::endl;
+#endif
+
+ if (inputBufferLeft) {
+
+ sample_t *buf = 0;
+ if (m_outputMonitors.size() > 0) {
+ buf = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ }
+
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferLeft[i] * gain;
+ if (sample > peakLeft)
+ peakLeft = sample;
+ if (buf)
+ buf[i] = sample;
+ }
+
+ if (channels == 2 && inputBufferRight) {
+
+ buf = 0;
+ if (m_outputMonitors.size() > 1) {
+ buf = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ }
+
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferRight[i] * gain;
+ if (sample > peakRight)
+ peakRight = sample;
+ if (buf)
+ buf[i] = sample;
+ }
+ }
+ }
+ }
+
+ if (channels < 2)
+ peakRight = peakLeft;
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (peakLeft, 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (peakRight, 127, AudioLevel::LongFader);
+ sdb->setInstrumentRecordLevel(id, info);
+ }
+
+ if (wroteSomething) {
+ m_fileWriter->signal();
+ }
+
+ return 0;
+}
+
+
+int
+JackDriver::jackSyncCallback(jack_transport_state_t state,
+ jack_position_t *position,
+ void *arg)
+{
+ JackDriver *inst = (JackDriver *)arg;
+ if (!inst)
+ return true; // or rather, return "huh?"
+
+ inst->m_alsaDriver->checkTimerSync(0); // reset, as not processing
+
+ if (!inst->m_jackTransportEnabled)
+ return true; // ignore
+
+ ExternalTransport *transport =
+ inst->m_alsaDriver->getExternalTransportControl();
+ if (!transport)
+ return true;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: state " << state << " [" << (state == 0 ? "stopped" : state == 1 ? "rolling" : state == 2 ? "looping" : state == 3 ? "starting" : "unknown") << "], frame " << position->frame << ", waiting " << inst->m_waiting << ", playing " << inst->m_alsaDriver->isPlaying() << std::endl;
+
+ std::cerr << "JackDriver::jackSyncCallback: m_waitingState " << inst->m_waitingState << ", unique_1 " << position->unique_1 << ", unique_2 " << position->unique_2 << std::endl;
+
+ std::cerr << "JackDriver::jackSyncCallback: rate " << position->frame_rate << ", bar " << position->bar << ", beat " << position->beat << ", tick " << position->tick << ", bpm " << position->beats_per_minute << std::endl;
+
+#endif
+
+ ExternalTransport::TransportRequest request =
+ ExternalTransport::TransportNoChange;
+
+ if (inst->m_alsaDriver->isPlaying()) {
+
+ if (state == JackTransportStarting) {
+ request = ExternalTransport::TransportJumpToTime;
+ } else if (state == JackTransportStopped) {
+ request = ExternalTransport::TransportStop;
+ }
+
+ } else {
+
+ if (state == JackTransportStarting) {
+ request = ExternalTransport::TransportStartAtTime;
+ } else if (state == JackTransportStopped) {
+ request = ExternalTransport::TransportNoChange;
+ }
+ }
+
+ if (!inst->m_waiting || inst->m_waitingState != state) {
+
+ if (request == ExternalTransport::TransportJumpToTime ||
+ request == ExternalTransport::TransportStartAtTime) {
+
+ RealTime rt = RealTime::frame2RealTime(position->frame,
+ position->frame_rate);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: Requesting jump to " << rt << std::endl;
+#endif
+
+ inst->m_waitingToken = transport->transportJump(request, rt);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
+#endif
+
+ } else if (request == ExternalTransport::TransportStop) {
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Requesting state change to " << request << std::endl;
+#endif
+
+ inst->m_waitingToken = transport->transportChange(request);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
+#endif
+
+ } else if (request == ExternalTransport::TransportNoChange) {
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Requesting no state change!" << std::endl;
+#endif
+
+ inst->m_waitingToken = transport->transportChange(request);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
+#endif
+
+ }
+
+ inst->m_waiting = true;
+ inst->m_waitingState = state;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: Setting waiting to " << inst->m_waiting << " and waiting state to " << inst->m_waitingState << " (request was " << request << ")" << std::endl;
+#endif
+
+ return 0;
+
+ } else {
+
+ if (transport->isTransportSyncComplete(inst->m_waitingToken)) {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Sync complete" << std::endl;
+#endif
+
+ return 1;
+ } else {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Sync not complete" << std::endl;
+#endif
+
+ return 0;
+ }
+ }
+}
+
+bool
+JackDriver::relocateTransportInternal(bool alsoStart)
+{
+ if (!m_client)
+ return true;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ const char *fn = (alsoStart ?
+ "JackDriver::startTransport" :
+ "JackDriver::relocateTransport");
+#endif
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << fn << std::endl;
+#else
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::relocateTransportInternal" << std::endl;
+#endif
+#endif
+
+ // m_waiting is true if we are waiting for the JACK transport
+ // to finish a change of state.
+
+ if (m_jackTransportEnabled) {
+
+ // If on the transport, we never return true here -- instead
+ // the JACK process calls startClocksApproved() to signal to
+ // the ALSA driver that it's time to go. But we do use this
+ // to manage our JACK transport state requests.
+
+ // Where did this request come from? Are we just responding
+ // to an external sync?
+
+ ExternalTransport *transport =
+ m_alsaDriver->getExternalTransportControl();
+
+ if (transport) {
+ if (transport->isTransportSyncComplete(m_waitingToken)) {
+
+ // Nope, this came from Rosegarden
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << fn << ": asking JACK transport to start, setting wait state" << std::endl;
+#endif
+
+ m_waiting = true;
+ m_waitingState = JackTransportStarting;
+
+ long frame = RealTime::realTime2Frame
+ (m_alsaDriver->getSequencerTime(), m_sampleRate);
+
+ if (frame < 0) {
+ // JACK Transport doesn't support preroll and
+ // can't set transport position to before zero
+ // (frame count is unsigned), so there's no very
+ // satisfactory fix for what to do for count-in
+ // bars. Let's just start at zero instead.
+ jack_transport_locate(m_client, 0);
+ } else {
+ jack_transport_locate(m_client, frame);
+ }
+
+ if (alsoStart) {
+ jack_transport_start(m_client);
+ m_ignoreProcessTransportCount = 1;
+ } else {
+ m_ignoreProcessTransportCount = 2;
+ }
+ } else {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << fn << ": waiting already" << std::endl;
+#endif
+
+ }
+ }
+ return false;
+ }
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+ framesThisPlay = 0; //!!!
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ startTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); //!!!
+#endif
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << fn << ": not on JACK transport, accepting right away" << std::endl;
+#endif
+
+ return true;
+}
+
+bool
+JackDriver::startTransport()
+{
+ return relocateTransportInternal(true);
+}
+
+bool
+JackDriver::relocateTransport()
+{
+
+ return relocateTransportInternal(false);
+}
+
+void
+JackDriver::stopTransport()
+{
+ if (!m_client)
+ return ;
+
+ std::cerr << "JackDriver::stopTransport: resetting m_haveAsyncAudioEvent" << std::endl;
+ m_haveAsyncAudioEvent = false;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ RealTime endTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); //!!!
+ std::cerr << "\nJackDriver::stop: frames this play: " << framesThisPlay << ", elapsed " << (endTime - startTime) << std::endl;
+#endif
+
+ if (m_jackTransportEnabled) {
+
+ // Where did this request come from? Is this a result of our
+ // sync to a transport that has in fact already stopped?
+
+ ExternalTransport *transport =
+ m_alsaDriver->getExternalTransportControl();
+
+ if (transport) {
+ if (transport->isTransportSyncComplete(m_waitingToken)) {
+
+ // No, we have no outstanding external requests; this
+ // must have genuinely been requested from within
+ // Rosegarden, so:
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::stop: internal request, asking JACK transport to stop" << std::endl;
+#endif
+
+ jack_transport_stop(m_client);
+
+ } else {
+ // Nothing to do
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::stop: external request, JACK transport is already stopped" << std::endl;
+#endif
+
+ }
+ }
+ }
+
+ if (m_instrumentMixer)
+ m_instrumentMixer->resetAllPlugins(true); // discard events too
+}
+
+
+// Pick up any change of buffer size
+//
+int
+JackDriver::jackBufferSize(jack_nframes_t nframes, void *arg)
+{
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::jackBufferSize - buffer size changed to "
+ << nframes << std::endl;
+#endif
+
+ inst->m_bufferSize = nframes;
+
+ // Recreate our temporary mix buffers to the new size
+ //
+ //!!! need buffer size change callbacks on plugins (so long as they
+ // have internal buffers) and the mix manager, with locks acquired
+ // appropriately
+
+ delete [] inst->m_tempOutBuffer;
+ inst->m_tempOutBuffer = new sample_t[inst->m_bufferSize];
+
+ return 0;
+}
+
+// Sample rate change
+//
+int
+JackDriver::jackSampleRate(jack_nframes_t nframes, void *arg)
+{
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::jackSampleRate - sample rate changed to "
+ << nframes << std::endl;
+#endif
+
+ inst->m_sampleRate = nframes;
+
+ return 0;
+}
+
+void
+JackDriver::jackShutdown(void *arg)
+{
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::jackShutdown() - callback received - "
+ << "informing GUI" << std::endl;
+#endif
+
+#ifdef DEBUG_JACK_XRUN
+
+ std::cerr << "JackDriver::jackShutdown" << std::endl;
+ Profiles::getInstance()->dump();
+#endif
+
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+ inst->m_ok = false;
+ inst->m_kickedOutAt = time(0);
+ inst->reportFailure(MappedEvent::FailureJackDied);
+}
+
+int
+JackDriver::jackXRun(void *arg)
+{
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::jackXRun" << std::endl;
+#endif
+
+#ifdef DEBUG_JACK_XRUN
+
+ std::cerr << "JackDriver::jackXRun" << std::endl;
+ Profiles::getInstance()->dump();
+#endif
+
+ // Report to GUI
+ //
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+ inst->reportFailure(MappedEvent::FailureXRuns);
+
+ return 0;
+}
+
+
+void
+
+JackDriver::restoreIfRestorable()
+{
+ if (m_kickedOutAt == 0)
+ return ;
+
+ if (m_client) {
+ jack_client_close(m_client);
+ std::cerr << "closed client" << std::endl;
+ m_client = 0;
+ }
+
+ time_t now = time(0);
+
+ if (now < m_kickedOutAt || now >= m_kickedOutAt + 3) {
+
+ if (m_instrumentMixer)
+ m_instrumentMixer->resetAllPlugins(true);
+ std::cerr << "reset plugins" << std::endl;
+
+ initialise(true);
+
+ if (m_ok) {
+ reportFailure(MappedEvent::FailureJackRestart);
+ } else {
+ reportFailure(MappedEvent::FailureJackRestartFailed);
+ }
+
+ m_kickedOutAt = 0;
+ }
+}
+
+void
+JackDriver::prepareAudio()
+{
+ if (!m_instrumentMixer)
+ return ;
+
+ // This is used when restarting clocks after repositioning, but
+ // when not actually playing (yet). We need to do things like
+ // regenerating the processing buffers here. prebufferAudio()
+ // also does all of this, but rather more besides.
+
+ m_instrumentMixer->allocateBuffers();
+ m_instrumentMixer->resetAllPlugins(false);
+}
+
+void
+JackDriver::prebufferAudio()
+{
+ if (!m_instrumentMixer)
+ return ;
+
+ // We want this to happen when repositioning during playback, and
+ // stopTransport no longer happens then, so we call it from here.
+ // NB. Don't want to discard events here as this is called after
+ // pushing events to the soft synth queues at startup
+ m_instrumentMixer->resetAllPlugins(false);
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::prebufferAudio: sequencer time is "
+ << m_alsaDriver->getSequencerTime() << std::endl;
+#endif
+
+ RealTime sliceStart = getNextSliceStart(m_alsaDriver->getSequencerTime());
+
+ m_fileReader->fillBuffers(sliceStart);
+
+ if (m_bussMixer->getBussCount() > 0) {
+ m_bussMixer->fillBuffers(sliceStart); // also calls on m_instrumentMixer
+ } else {
+ m_instrumentMixer->fillBuffers(sliceStart);
+ }
+}
+
+void
+JackDriver::kickAudio()
+{
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::kickAudio" << std::endl;
+#endif
+
+ if (m_fileReader)
+ m_fileReader->kick();
+ if (m_instrumentMixer)
+ m_instrumentMixer->kick();
+ if (m_bussMixer)
+ m_bussMixer->kick();
+ if (m_fileWriter)
+ m_fileWriter->kick();
+}
+
+void
+JackDriver::updateAudioData()
+{
+ if (!m_ok || !m_client)
+ return ;
+
+#ifdef DEBUG_JACK_DRIVER
+ // std::cerr << "JackDriver::updateAudioData starting" << std::endl;
+#endif
+
+ MappedAudioBuss *mbuss =
+ m_alsaDriver->getMappedStudio()->getAudioBuss(0);
+
+ if (mbuss) {
+ float level = 0.0;
+ (void)mbuss->getProperty(MappedAudioBuss::Level, level);
+ m_masterLevel = level;
+ }
+
+ unsigned long directMasterAudioInstruments = 0L;
+ unsigned long directMasterSynthInstruments = 0L;
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_alsaDriver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_alsaDriver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ RealTime jackLatency = getAudioPlayLatency();
+ RealTime maxLatency = RealTime::zeroTime;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ MappedAudioFader *fader = m_alsaDriver->getMappedStudio()->getAudioFader(id);
+ if (!fader)
+ continue;
+
+ float f = 2;
+ (void)fader->getProperty(MappedAudioFader::Channels, f);
+ int channels = (int)f;
+
+ int inputChannel = -1;
+ if (channels == 1) {
+ float f = 0;
+ (void)fader->getProperty(MappedAudioFader::InputChannel, f);
+ inputChannel = (int)f;
+ }
+
+ float level = 0.0;
+ (void)fader->getProperty(MappedAudioFader::FaderRecordLevel, level);
+
+ // Like in base/Instrument.h, we use numbers < 1000 to
+ // mean buss numbers and >= 1000 to mean record ins
+ // when recording the record input number.
+
+ MappedObjectValueList connections = fader->getConnections
+ (MappedConnectableObject::In);
+ int input = 1000;
+
+ if (connections.empty()) {
+
+ std::cerr << "No connections in for record instrument "
+ << (id) << " (mapped id " << fader->getId() << ")" << std::endl;
+
+ // oh dear.
+ input = 1000;
+
+ } else if (*connections.begin() == mbuss->getId()) {
+
+ input = 0;
+
+ } else {
+
+ MappedObject *obj = m_alsaDriver->getMappedStudio()->
+ getObjectById(MappedObjectId(*connections.begin()));
+
+ if (!obj) {
+
+ std::cerr << "No such object as " << *connections.begin() << std::endl;
+ input = 1000;
+ } else if (obj->getType() == MappedObject::AudioBuss) {
+ input = (int)((MappedAudioBuss *)obj)->getBussId();
+ } else if (obj->getType() == MappedObject::AudioInput) {
+ input = (int)((MappedAudioInput *)obj)->getInputNumber()
+ + 1000;
+ } else {
+ std::cerr << "Object " << *connections.begin() << " is not buss or input" << std::endl;
+ input = 1000;
+ }
+ }
+
+ if (m_recordInputs[id].input != input) {
+ std::cerr << "Changing record input for instrument "
+ << id << " to " << input << std::endl;
+ }
+ m_recordInputs[id] = RecordInputDesc(input, inputChannel, level);
+
+ size_t pluginLatency = 0;
+ bool empty = m_instrumentMixer->isInstrumentEmpty(id);
+
+ if (!empty) {
+ pluginLatency = m_instrumentMixer->getPluginLatency(id);
+ }
+
+ // If we find the object is connected to no output, or to buss
+ // number 0 (the master), then we set the bit appropriately.
+
+ connections = fader->getConnections(MappedConnectableObject::Out);
+
+ if (connections.empty() || (*connections.begin() == mbuss->getId())) {
+ if (i < audioInstruments) {
+ directMasterAudioInstruments |= (1 << i);
+ } else {
+ directMasterSynthInstruments |= (1 << (i - audioInstruments));
+ }
+ } else if (!empty) {
+ pluginLatency +=
+ m_instrumentMixer->getPluginLatency((unsigned int) * connections.begin());
+ }
+
+ if (empty) {
+ m_instrumentLatencies[id] = RealTime::zeroTime;
+ } else {
+ m_instrumentLatencies[id] = jackLatency +
+ RealTime::frame2RealTime(pluginLatency, m_sampleRate);
+ if (m_instrumentLatencies[id] > maxLatency) {
+ maxLatency = m_instrumentLatencies[id];
+ }
+ }
+ }
+
+ m_maxInstrumentLatency = maxLatency;
+ m_directMasterAudioInstruments = directMasterAudioInstruments;
+ m_directMasterSynthInstruments = directMasterSynthInstruments;
+ m_maxInstrumentLatency = maxLatency;
+
+ int inputs = m_alsaDriver->getMappedStudio()->
+ getObjectCount(MappedObject::AudioInput);
+
+ if (m_client) {
+ // this will return with no work if the inputs are already correct:
+ createRecordInputs(inputs);
+ }
+
+ m_bussMixer->updateInstrumentConnections();
+ m_instrumentMixer->updateInstrumentMuteStates();
+
+ if (m_bussMixer->getBussCount() == 0 || m_alsaDriver->getLowLatencyMode()) {
+ if (m_bussMixer->running()) {
+ m_bussMixer->terminate();
+ }
+ } else {
+ if (!m_bussMixer->running()) {
+ m_bussMixer->run();
+ }
+ }
+
+ if (m_alsaDriver->getLowLatencyMode()) {
+ if (m_instrumentMixer->running()) {
+ m_instrumentMixer->terminate();
+ }
+ } else {
+ if (!m_instrumentMixer->running()) {
+ m_instrumentMixer->run();
+ }
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ // std::cerr << "JackDriver::updateAudioData exiting" << std::endl;
+#endif
+}
+
+void
+JackDriver::setAudioBussLevels(int bussNo, float dB, float pan)
+{
+ if (m_bussMixer) {
+ m_bussMixer->setBussLevels(bussNo, dB, pan);
+ }
+}
+
+void
+JackDriver::setAudioInstrumentLevels(InstrumentId instrument, float dB, float pan)
+{
+ if (m_instrumentMixer) {
+ m_instrumentMixer->setInstrumentLevels(instrument, dB, pan);
+ }
+}
+
+RealTime
+JackDriver::getNextSliceStart(const RealTime &now) const
+{
+ jack_nframes_t frame;
+ bool neg = false;
+
+ if (now < RealTime::zeroTime) {
+ neg = true;
+ frame = RealTime::realTime2Frame(RealTime::zeroTime - now, m_sampleRate);
+ } else {
+ frame = RealTime::realTime2Frame(now, m_sampleRate);
+ }
+
+ jack_nframes_t rounded = frame;
+ rounded /= m_bufferSize;
+ rounded *= m_bufferSize;
+
+ RealTime roundrt;
+
+ if (rounded == frame)
+ roundrt = RealTime::frame2RealTime(rounded, m_sampleRate);
+ else if (neg)
+ roundrt = RealTime::frame2RealTime(rounded - m_bufferSize, m_sampleRate);
+ else
+ roundrt = RealTime::frame2RealTime(rounded + m_bufferSize, m_sampleRate);
+
+ if (neg)
+ roundrt = RealTime::zeroTime - roundrt;
+
+ return roundrt;
+}
+
+
+int
+JackDriver::getAudioQueueLocks()
+{
+ // We have to lock the mixers first, because the mixers can try to
+ // lock the disk manager from within a locked section -- so if we
+ // locked the disk manager first we would risk deadlock when
+ // trying to acquire the instrument mixer lock
+
+ int rv = 0;
+ if (m_bussMixer) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: trying to lock buss mixer" << std::endl;
+#endif
+
+ rv = m_bussMixer->getLock();
+ if (rv)
+ return rv;
+ }
+ if (m_instrumentMixer) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for instrument mixer" << std::endl;
+#endif
+
+ rv = m_instrumentMixer->getLock();
+ if (rv)
+ return rv;
+ }
+ if (m_fileReader) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for disk reader" << std::endl;
+#endif
+
+ rv = m_fileReader->getLock();
+ if (rv)
+ return rv;
+ }
+ if (m_fileWriter) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for disk writer" << std::endl;
+#endif
+
+ rv = m_fileWriter->getLock();
+ }
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok" << std::endl;
+#endif
+
+ return rv;
+}
+
+int
+JackDriver::tryAudioQueueLocks()
+{
+ int rv = 0;
+ if (m_bussMixer) {
+ rv = m_bussMixer->tryLock();
+ if (rv)
+ return rv;
+ }
+ if (m_instrumentMixer) {
+ rv = m_instrumentMixer->tryLock();
+ if (rv) {
+ if (m_bussMixer) {
+ m_bussMixer->releaseLock();
+ }
+ }
+ }
+ if (m_fileReader) {
+ rv = m_fileReader->tryLock();
+ if (rv) {
+ if (m_instrumentMixer) {
+ m_instrumentMixer->releaseLock();
+ }
+ if (m_bussMixer) {
+ m_bussMixer->releaseLock();
+ }
+ }
+ }
+ if (m_fileWriter) {
+ rv = m_fileWriter->tryLock();
+ if (rv) {
+ if (m_fileReader) {
+ m_fileReader->releaseLock();
+ }
+ if (m_instrumentMixer) {
+ m_instrumentMixer->releaseLock();
+ }
+ if (m_bussMixer) {
+ m_bussMixer->releaseLock();
+ }
+ }
+ }
+ return rv;
+}
+
+int
+JackDriver::releaseAudioQueueLocks()
+{
+ int rv = 0;
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::releaseAudioQueueLocks" << std::endl;
+#endif
+
+ if (m_fileWriter)
+ rv = m_fileWriter->releaseLock();
+ if (m_fileReader)
+ rv = m_fileReader->releaseLock();
+ if (m_instrumentMixer)
+ rv = m_instrumentMixer->releaseLock();
+ if (m_bussMixer)
+ rv = m_bussMixer->releaseLock();
+ return rv;
+}
+
+
+void
+JackDriver::setPluginInstance(InstrumentId id, QString identifier,
+ int position)
+{
+ if (m_instrumentMixer) {
+ m_instrumentMixer->setPlugin(id, position, identifier);
+ }
+ if (!m_alsaDriver->isPlaying()) {
+ prebufferAudio(); // to ensure the plugin's ringbuffers are generated
+ }
+}
+
+void
+JackDriver::removePluginInstance(InstrumentId id, int position)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->removePlugin(id, position);
+}
+
+void
+JackDriver::removePluginInstances()
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->removeAllPlugins();
+}
+
+void
+JackDriver::setPluginInstancePortValue(InstrumentId id, int position,
+ unsigned long portNumber,
+ float value)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->setPluginPortValue(id, position, portNumber, value);
+}
+
+float
+JackDriver::getPluginInstancePortValue(InstrumentId id, int position,
+ unsigned long portNumber)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginPortValue(id, position, portNumber);
+ return 0;
+}
+
+void
+JackDriver::setPluginInstanceBypass(InstrumentId id, int position, bool value)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->setPluginBypass(id, position, value);
+}
+
+QStringList
+JackDriver::getPluginInstancePrograms(InstrumentId id, int position)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginPrograms(id, position);
+ return QStringList();
+}
+
+QString
+JackDriver::getPluginInstanceProgram(InstrumentId id, int position)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginProgram(id, position);
+ return QString();
+}
+
+QString
+JackDriver::getPluginInstanceProgram(InstrumentId id, int position,
+ int bank, int program)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginProgram(id, position, bank, program);
+ return QString();
+}
+
+unsigned long
+JackDriver::getPluginInstanceProgram(InstrumentId id, int position, QString name)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginProgram(id, position, name);
+ return 0;
+}
+
+void
+JackDriver::setPluginInstanceProgram(InstrumentId id, int position, QString program)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->setPluginProgram(id, position, program);
+}
+
+QString
+JackDriver::configurePlugin(InstrumentId id, int position, QString key, QString value)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->configurePlugin(id, position, key, value);
+ return QString();
+}
+
+RunnablePluginInstance *
+JackDriver::getSynthPlugin(InstrumentId id)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getSynthPlugin(id);
+ else
+ return 0;
+}
+
+void
+JackDriver::clearSynthPluginEvents()
+{
+ if (!m_instrumentMixer) return;
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::clearSynthPluginEvents" << std::endl;
+#endif
+
+ m_instrumentMixer->discardPluginEvents();
+}
+
+bool
+JackDriver::openRecordFile(InstrumentId id,
+ const std::string &filename)
+{
+ if (m_fileWriter) {
+ if (!m_fileWriter->running()) {
+ m_fileWriter->run();
+ }
+ return m_fileWriter->openRecordFile(id, filename);
+ } else {
+ std::cerr << "JackDriver::openRecordFile: No file writer available!" << std::endl;
+ return false;
+ }
+}
+
+bool
+JackDriver::closeRecordFile(InstrumentId id,
+ AudioFileId &returnedId)
+{
+ if (m_fileWriter) {
+ return m_fileWriter->closeRecordFile(id, returnedId);
+ if (m_fileWriter->running() && !m_fileWriter->haveRecordFilesOpen()) {
+ m_fileWriter->terminate();
+ }
+ } else
+ return false;
+}
+
+
+void
+JackDriver::reportFailure(MappedEvent::FailureCode code)
+{
+ if (m_alsaDriver)
+ m_alsaDriver->reportFailure(code);
+}
+
+
+}
+
+#endif // HAVE_LIBJACK
+#endif // HAVE_ALSA
diff --git a/src/sound/JackDriver.h b/src/sound/JackDriver.h
new file mode 100644
index 0000000..b46080d
--- /dev/null
+++ b/src/sound/JackDriver.h
@@ -0,0 +1,297 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _JACKDRIVER_H_
+#define _JACKDRIVER_H_
+
+#ifdef HAVE_ALSA
+#ifdef HAVE_LIBJACK
+
+#include "RunnablePluginInstance.h"
+#include <jack/jack.h>
+#include "SoundDriver.h"
+#include "Instrument.h"
+#include "RealTime.h"
+#include "ExternalTransport.h"
+#include <qstringlist.h>
+
+namespace Rosegarden
+{
+
+class AlsaDriver;
+class AudioBussMixer;
+class AudioInstrumentMixer;
+class AudioFileReader;
+class AudioFileWriter;
+
+class JackDriver
+{
+public:
+ // convenience
+ typedef jack_default_audio_sample_t sample_t;
+
+ JackDriver(AlsaDriver *alsaDriver);
+ virtual ~JackDriver();
+
+ bool isOK() const { return m_ok; }
+
+ bool isTransportEnabled() { return m_jackTransportEnabled; }
+ bool isTransportMaster () { return m_jackTransportMaster; }
+
+ void setTransportEnabled(bool e) { m_jackTransportEnabled = e; }
+ void setTransportMaster (bool m) { m_jackTransportMaster = m; }
+
+ // These methods call back on the sound driver if necessary to
+ // establish the current transport location to start at or
+ // relocate to. startTransport and relocateTransport return true
+ // if they have completed and the sound driver can safely call
+ // startClocks; false if the sound driver should wait for the JACK
+ // driver to call back on startClocksApproved before starting.
+ bool startTransport();
+ bool relocateTransport();
+ void stopTransport();
+
+ RealTime getAudioPlayLatency() const;
+ RealTime getAudioRecordLatency() const;
+ RealTime getInstrumentPlayLatency(InstrumentId) const;
+ RealTime getMaximumPlayLatency() const;
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId id,
+ QString identifier,
+ int position);
+
+ virtual void removePluginInstance(InstrumentId id, int position);
+
+ // Remove all plugin instances
+ //
+ virtual void removePluginInstances();
+
+ virtual void setPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber,
+ float value);
+
+ virtual float getPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber);
+
+ virtual void setPluginInstanceBypass(InstrumentId id,
+ int position,
+ bool value);
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId id,
+ int position);
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position);
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position,
+ int bank,
+ int program);
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString name);
+
+ virtual void setPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString program);
+
+ virtual QString configurePlugin(InstrumentId id,
+ int position,
+ QString key, QString value);
+
+ virtual RunnablePluginInstance *getSynthPlugin(InstrumentId id);
+
+ virtual void clearSynthPluginEvents(); // when stopping
+
+ virtual unsigned int getSampleRate() const { return m_sampleRate; }
+ virtual unsigned int getBufferSize() const { return m_bufferSize; }
+
+ // A new audio file for storage of our recorded samples - the
+ // file stays open so we can append samples at will. We must
+ // explicitly close the file eventually though to make sure
+ // the integrity is correct (sample sizes must be written).
+ //
+ bool openRecordFile(InstrumentId id,
+ const std::string &fileName);
+ bool closeRecordFile(InstrumentId id,
+ AudioFileId &returnedId);
+
+ // Set or change the number of audio inputs and outputs.
+ // The first of these is slightly misnamed -- the submasters
+ // argument controls the number of busses, not ports (which
+ // may or may not exist depending on the setAudioPorts call).
+ //
+ void setAudioPorts(bool faderOuts, bool submasterOuts);
+
+ // Locks used by the disk thread and mix thread. The AlsaDriver
+ // should hold these locks whenever it wants to modify its audio
+ // play queue -- at least when adding or removing files or
+ // resetting status; it doesn't need to hold the locks when
+ // incrementing their statuses or simply reading them.
+ //
+ int getAudioQueueLocks();
+ int tryAudioQueueLocks();
+ int releaseAudioQueueLocks();
+
+ void prepareAudio(); // when repositioning etc
+ void prebufferAudio(); // when starting playback (incorporates prepareAudio)
+ void kickAudio(); // for paranoia only
+
+ // Because we don't want to do any lookups that might involve
+ // locking etc from within the JACK process thread, we instead
+ // call this regularly from the ALSA driver thread -- it looks up
+ // various bits of data such as the master fader and monitoring
+ // levels, number of inputs etc and either processes them or
+ // writes them into simple records in the JACK driver for process
+ // to read. Actually quite a lot of work.
+ //
+ void updateAudioData();
+
+ // Similarly, set data on the buss mixer to avoid the buss mixer
+ // having to call back on the mapped studio to discover it
+ //
+ void setAudioBussLevels(int bussNo, float dB, float pan);
+
+ // Likewise for instrument mixer
+ //
+ void setAudioInstrumentLevels(InstrumentId instrument, float dB, float pan);
+
+ // Called from AlsaDriver to indicate that an async MIDI event is
+ // being sent to a soft synth. JackDriver uses this to suggest
+ // that it needs to start processing soft synths, if it wasn't
+ // already. It will switch this off again itself when things
+ // fall silent.
+ //
+ void setHaveAsyncAudioEvent() { m_haveAsyncAudioEvent = true; }
+
+ RealTime getNextSliceStart(const RealTime &now) const;
+
+ // For audit purposes only.
+ size_t getFramesProcessed() const { return m_framesProcessed; }
+
+ // Reinitialise if we've been kicked off JACK -- if we can
+ //
+ void restoreIfRestorable();
+
+ // Report back to GUI via the AlsaDriver
+ //
+ void reportFailure(MappedEvent::FailureCode code);
+
+protected:
+
+ // static methods for JACK process thread:
+ static int jackProcessStatic(jack_nframes_t nframes, void *arg);
+ static int jackBufferSize(jack_nframes_t nframes, void *arg);
+ static int jackSampleRate(jack_nframes_t nframes, void *arg);
+ static void jackShutdown(void *arg);
+ static int jackXRun(void *);
+
+ // static JACK transport callbacks
+ static int jackSyncCallback(jack_transport_state_t,
+ jack_position_t *, void *);
+ static int jackTimebaseCallback(jack_transport_state_t,
+ jack_nframes_t,
+ jack_position_t *,
+ int,
+ void *);
+
+ // jackProcessStatic delegates to this
+ int jackProcess(jack_nframes_t nframes);
+ int jackProcessRecord(InstrumentId id,
+ jack_nframes_t nframes,
+ sample_t *, sample_t *, bool);
+ int jackProcessEmpty(jack_nframes_t nframes);
+
+ // other helper methods:
+
+ void initialise(bool reinitialise = false);
+
+ bool createMainOutputs();
+ bool createFaderOutputs(int audioPairs, int synthPairs);
+ bool createSubmasterOutputs(int pairs);
+ bool createRecordInputs(int pairs);
+
+ bool relocateTransportInternal(bool alsoStart);
+
+ // data members:
+
+ jack_client_t *m_client;
+
+ std::vector<jack_port_t *> m_inputPorts;
+ std::vector<jack_port_t *> m_outputInstruments;
+ std::vector<jack_port_t *> m_outputSubmasters;
+ std::vector<jack_port_t *> m_outputMonitors;
+ std::vector<jack_port_t *> m_outputMasters;
+
+ jack_nframes_t m_bufferSize;
+ jack_nframes_t m_sampleRate;
+
+ sample_t *m_tempOutBuffer;
+
+ bool m_jackTransportEnabled;
+ bool m_jackTransportMaster;
+
+ bool m_waiting;
+ jack_transport_state_t m_waitingState;
+ ExternalTransport::TransportToken m_waitingToken;
+ int m_ignoreProcessTransportCount;
+
+ AudioBussMixer *m_bussMixer;
+ AudioInstrumentMixer *m_instrumentMixer;
+ AudioFileReader *m_fileReader;
+ AudioFileWriter *m_fileWriter;
+ AlsaDriver *m_alsaDriver;
+
+ float m_masterLevel;
+ unsigned long m_directMasterAudioInstruments; // bitmap
+ unsigned long m_directMasterSynthInstruments;
+ std::map<InstrumentId, RealTime> m_instrumentLatencies;
+ RealTime m_maxInstrumentLatency;
+ bool m_haveAsyncAudioEvent;
+
+ struct RecordInputDesc {
+ int input;
+ int channel;
+ float level;
+ RecordInputDesc(int i = 1000, int c = -1, float l = 0.0f) :
+ input(i), channel(c), level(l) { }
+ };
+ typedef std::map<InstrumentId, RecordInputDesc> RecordInputMap;
+ RecordInputMap m_recordInputs;
+
+ time_t m_kickedOutAt;
+ size_t m_framesProcessed;
+ bool m_ok;
+};
+
+
+}
+
+#endif
+#endif
+
+#endif
+
diff --git a/src/sound/LADSPAPluginFactory.cpp b/src/sound/LADSPAPluginFactory.cpp
new file mode 100644
index 0000000..2a4a4ea
--- /dev/null
+++ b/src/sound/LADSPAPluginFactory.cpp
@@ -0,0 +1,841 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "LADSPAPluginFactory.h"
+#include <iostream>
+#include <cstdlib>
+
+#ifdef HAVE_LADSPA
+
+#include <dlfcn.h>
+#include <qdir.h>
+#include <cmath>
+
+#include "AudioPluginInstance.h"
+#include "LADSPAPluginInstance.h"
+#include "MappedStudio.h"
+#include "PluginIdentifier.h"
+
+#ifdef HAVE_LIBLRDF
+#include "lrdf.h"
+#endif // HAVE_LIBLRDF
+
+#include <kdebug.h>
+
+namespace Rosegarden
+{
+
+LADSPAPluginFactory::LADSPAPluginFactory()
+{}
+
+LADSPAPluginFactory::~LADSPAPluginFactory()
+{
+ for (std::set
+ <RunnablePluginInstance *>::iterator i = m_instances.begin();
+ i != m_instances.end(); ++i) {
+ (*i)->setFactory(0);
+ delete *i;
+ }
+ m_instances.clear();
+ unloadUnusedLibraries();
+}
+
+const std::vector<QString> &
+LADSPAPluginFactory::getPluginIdentifiers() const
+{
+ return m_identifiers;
+}
+
+void
+LADSPAPluginFactory::enumeratePlugins(MappedObjectPropertyList &list)
+{
+ for (std::vector<QString>::iterator i = m_identifiers.begin();
+ i != m_identifiers.end(); ++i) {
+
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(*i);
+
+ if (!descriptor) {
+ std::cerr << "WARNING: LADSPAPluginFactory::enumeratePlugins: couldn't get descriptor for identifier " << *i << std::endl;
+ continue;
+ }
+
+// std::cerr << "Enumerating plugin identifier " << *i << std::endl;
+
+ list.push_back(*i);
+ list.push_back(descriptor->Name);
+ list.push_back(QString("%1").arg(descriptor->UniqueID));
+ list.push_back(descriptor->Label);
+ list.push_back(descriptor->Maker);
+ list.push_back(descriptor->Copyright);
+ list.push_back("false"); // is synth
+ list.push_back("false"); // is grouped
+
+ if (m_taxonomy.find(descriptor->UniqueID) != m_taxonomy.end() &&
+ m_taxonomy[descriptor->UniqueID] != "") {
+// std::cerr << "LADSPAPluginFactory: cat for " << *i<< " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << std::endl;
+ list.push_back(m_taxonomy[descriptor->UniqueID]);
+
+ } else if (m_fallbackCategories.find(*i) !=
+ m_fallbackCategories.end()) {
+ list.push_back(m_fallbackCategories[*i]);
+// std::cerr << "LADSPAPluginFactory: cat for " << *i <<" found in fallbacks as " << m_fallbackCategories[*i] << std::endl;
+
+ } else {
+ list.push_back("");
+// std::cerr << "LADSPAPluginFactory: cat for " << *i << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << std::endl;
+
+ }
+
+ list.push_back(QString("%1").arg(descriptor->PortCount));
+
+ for (unsigned long p = 0; p < descriptor->PortCount; ++p) {
+
+ int type = 0;
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Control;
+ } else {
+ type |= PluginPort::Audio;
+ }
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Input;
+ } else {
+ type |= PluginPort::Output;
+ }
+
+ list.push_back(QString("%1").arg(p));
+ list.push_back(descriptor->PortNames[p]);
+ list.push_back(QString("%1").arg(type));
+ list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
+ }
+ }
+
+ unloadUnusedLibraries();
+}
+
+
+void
+LADSPAPluginFactory::populatePluginSlot(QString identifier, MappedPluginSlot &slot)
+{
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier);
+
+ if (descriptor) {
+
+ slot.setProperty(MappedPluginSlot::Label, descriptor->Label);
+ slot.setProperty(MappedPluginSlot::PluginName, descriptor->Name);
+ slot.setProperty(MappedPluginSlot::Author, descriptor->Maker);
+ slot.setProperty(MappedPluginSlot::Copyright, descriptor->Copyright);
+ slot.setProperty(MappedPluginSlot::PortCount, descriptor->PortCount);
+
+ if (m_taxonomy.find(descriptor->UniqueID) != m_taxonomy.end() &&
+ m_taxonomy[descriptor->UniqueID] != "") {
+ // std::cerr << "LADSPAPluginFactory: cat for " << identifier<< " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << std::endl;
+ slot.setProperty(MappedPluginSlot::Category,
+ m_taxonomy[descriptor->UniqueID]);
+
+ } else if (m_fallbackCategories.find(identifier) !=
+ m_fallbackCategories.end()) {
+ // std::cerr << "LADSPAPluginFactory: cat for " << identifier <<" found in fallbacks as " << m_fallbackCategories[identifier] << std::endl;
+ slot.setProperty(MappedPluginSlot::Category,
+ m_fallbackCategories[identifier]);
+
+ } else {
+ // std::cerr << "LADSPAPluginFactory: cat for " << identifier << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << std::endl;
+ slot.setProperty(MappedPluginSlot::Category, "");
+ }
+
+ slot.destroyChildren();
+
+ for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+ LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ MappedStudio *studio = dynamic_cast<MappedStudio *>(slot.getParent());
+ if (!studio) {
+ std::cerr << "WARNING: LADSPAPluginFactory::populatePluginSlot: can't find studio" << std::endl;
+ return ;
+ }
+
+ MappedPluginPort *port =
+ dynamic_cast<MappedPluginPort *>
+ (studio->createObject(MappedObject::PluginPort));
+
+ slot.addChild(port);
+ port->setParent(&slot);
+
+ port->setProperty(MappedPluginPort::PortNumber, i);
+ port->setProperty(MappedPluginPort::Name,
+ descriptor->PortNames[i]);
+ port->setProperty(MappedPluginPort::Maximum,
+ getPortMaximum(descriptor, i));
+ port->setProperty(MappedPluginPort::Minimum,
+ getPortMinimum(descriptor, i));
+ port->setProperty(MappedPluginPort::Default,
+ getPortDefault(descriptor, i));
+ port->setProperty(MappedPluginPort::DisplayHint,
+ getPortDisplayHint(descriptor, i));
+ }
+ }
+ }
+
+ //!!! leak here if the plugin is not instantiated too...?
+}
+
+MappedObjectValue
+LADSPAPluginFactory::getPortMinimum(const LADSPA_Descriptor *descriptor, int port)
+{
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+
+ MappedObjectValue minimum = 0.0;
+
+ if (LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
+ MappedObjectValue lb = descriptor->PortRangeHints[port].LowerBound;
+ minimum = lb;
+ } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ MappedObjectValue ub = descriptor->PortRangeHints[port].UpperBound;
+ minimum = std::min(0.f, ub - 1.f);
+ }
+
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+ minimum *= m_sampleRate;
+ }
+
+ if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
+ if (minimum == 0.f) minimum = 1.f;
+ }
+
+ return minimum;
+}
+
+MappedObjectValue
+LADSPAPluginFactory::getPortMaximum(const LADSPA_Descriptor *descriptor, int port)
+{
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+
+ MappedObjectValue maximum = 1.0;
+
+// std::cerr << "LADSPAPluginFactory::getPortMaximum(" << port << ")" << std::endl;
+// std::cerr << "bounded above: " << LADSPA_IS_HINT_BOUNDED_ABOVE(d) << std::endl;
+
+ if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ MappedObjectValue ub = descriptor->PortRangeHints[port].UpperBound;
+ maximum = ub;
+ } else {
+ MappedObjectValue lb = descriptor->PortRangeHints[port].LowerBound;
+ if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
+ if (lb == 0.f) lb = 1.f;
+ maximum = lb * 100.f;
+ } else {
+ if (lb == 1.f) maximum = 10.f;
+ else maximum = lb + 10;
+ }
+ }
+
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+// std::cerr << "note: port has sample rate hint" << std::endl;
+ maximum *= m_sampleRate;
+ }
+
+// std::cerr << "maximum: " << maximum << std::endl;
+// if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
+// std::cerr << "note: port is logarithmic" << std::endl;
+// }
+// std::cerr << "note: minimum is reported as " << getPortMinimum(descriptor, port) << " (from bounded = " << LADSPA_IS_HINT_BOUNDED_BELOW(d) << ", bound = " << descriptor->PortRangeHints[port].LowerBound << ")" << std::endl;
+
+ return maximum;
+}
+
+MappedObjectValue
+LADSPAPluginFactory::getPortDefault(const LADSPA_Descriptor *descriptor, int port)
+{
+ MappedObjectValue minimum = getPortMinimum(descriptor, port);
+ MappedObjectValue maximum = getPortMaximum(descriptor, port);
+ MappedObjectValue deft;
+
+ if (m_portDefaults.find(descriptor->UniqueID) !=
+ m_portDefaults.end()) {
+ if (m_portDefaults[descriptor->UniqueID].find(port) !=
+ m_portDefaults[descriptor->UniqueID].end()) {
+
+ deft = m_portDefaults[descriptor->UniqueID][port];
+ if (deft < minimum) deft = minimum;
+ if (deft > maximum) deft = maximum;
+// std::cerr << "port " << port << ": default " << deft << " from defaults" << std::endl;
+ return deft;
+ }
+ }
+
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+
+ bool logarithmic = LADSPA_IS_HINT_LOGARITHMIC(d);
+
+ float logmin = 0, logmax = 0;
+ if (logarithmic) {
+ float thresh = powf(10, -10);
+ if (minimum < thresh) logmin = -10;
+ else logmin = log10f(minimum);
+ if (maximum < thresh) logmax = -10;
+ else logmax = log10f(maximum);
+ }
+
+ if (!LADSPA_IS_HINT_HAS_DEFAULT(d)) {
+
+ deft = minimum;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_MINIMUM(d)) {
+
+ // See comment for DEFAULT_MAXIMUM below
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
+ deft = descriptor->PortRangeHints[port].LowerBound;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+ deft *= m_sampleRate;
+ }
+// std::cerr << "default-minimum: " << deft << std::endl;
+ if (deft < minimum || deft > maximum) deft = minimum;
+// std::cerr << "default-minimum: " << deft << std::endl;
+ } else {
+ deft = minimum;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_LOW(d)) {
+
+ if (logarithmic) {
+ deft = powf(10, logmin * 0.75 + logmax * 0.25);
+ } else {
+ deft = minimum * 0.75 + maximum * 0.25;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(d)) {
+
+ if (logarithmic) {
+ deft = powf(10, logmin * 0.5 + logmax * 0.5);
+ } else {
+ deft = minimum * 0.5 + maximum * 0.5;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_HIGH(d)) {
+
+ if (logarithmic) {
+ deft = powf(10, logmin * 0.25 + logmax * 0.75);
+ } else {
+ deft = minimum * 0.25 + maximum * 0.75;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(d)) {
+
+ // CMT plugins employ this grossness (setting DEFAULT_MAXIMUM
+ // without BOUNDED_ABOVE and then using the UPPER_BOUND as the
+ // port default)
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ deft = descriptor->PortRangeHints[port].UpperBound;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+ deft *= m_sampleRate;
+ }
+// std::cerr << "default-maximum: " << deft << std::endl;
+ if (deft < minimum || deft > maximum) deft = maximum;
+// std::cerr << "default-maximum: " << deft << std::endl;
+ } else {
+ deft = maximum;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_0(d)) {
+
+ deft = 0.0;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_1(d)) {
+
+ deft = 1.0;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_100(d)) {
+
+ deft = 100.0;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_440(d)) {
+
+ deft = 440.0;
+
+ } else {
+
+ deft = minimum;
+ }
+
+// std::cerr << "port " << port << " default = "<< deft << std::endl;
+
+ return deft;
+}
+
+int
+LADSPAPluginFactory::getPortDisplayHint(const LADSPA_Descriptor *descriptor, int port)
+{
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+ int hint = PluginPort::NoHint;
+
+ if (LADSPA_IS_HINT_TOGGLED(d))
+ hint |= PluginPort::Toggled;
+ if (LADSPA_IS_HINT_INTEGER(d))
+ hint |= PluginPort::Integer;
+ if (LADSPA_IS_HINT_LOGARITHMIC(d))
+ hint |= PluginPort::Logarithmic;
+
+ return hint;
+}
+
+
+RunnablePluginInstance *
+LADSPAPluginFactory::instantiatePlugin(QString identifier,
+ int instrument,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels)
+{
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier);
+
+ if (descriptor) {
+
+ LADSPAPluginInstance *instance =
+ new LADSPAPluginInstance
+ (this, instrument, identifier, position, sampleRate, blockSize, channels,
+ descriptor);
+
+ m_instances.insert(instance);
+
+ return instance;
+ }
+
+ return 0;
+}
+
+void
+LADSPAPluginFactory::releasePlugin(RunnablePluginInstance *instance,
+ QString identifier)
+{
+ if (m_instances.find(instance) == m_instances.end()) {
+ std::cerr << "WARNING: LADSPAPluginFactory::releasePlugin: Not one of mine!"
+ << std::endl;
+ return ;
+ }
+
+ QString type, soname, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+
+ m_instances.erase(m_instances.find(instance));
+
+ bool stillInUse = false;
+
+ for (std::set
+ <RunnablePluginInstance *>::iterator ii = m_instances.begin();
+ ii != m_instances.end(); ++ii) {
+ QString itype, isoname, ilabel;
+ PluginIdentifier::parseIdentifier((*ii)->getIdentifier(), itype, isoname, ilabel);
+ if (isoname == soname) {
+ // std::cerr << "LADSPAPluginFactory::releasePlugin: dll " << soname << " is still in use for plugin " << ilabel << std::endl;
+ stillInUse = true;
+ break;
+ }
+ }
+
+ if (!stillInUse) {
+ // std::cerr << "LADSPAPluginFactory::releasePlugin: dll " << soname << " no longer in use, unloading" << std::endl;
+ unloadLibrary(soname);
+ }
+}
+
+const LADSPA_Descriptor *
+LADSPAPluginFactory::getLADSPADescriptor(QString identifier)
+{
+ QString type, soname, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ loadLibrary(soname);
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: loadLibrary failed for " << soname << std::endl;
+ return 0;
+ }
+ }
+
+ void *libraryHandle = m_libraryHandles[soname];
+
+ LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function)
+ dlsym(libraryHandle, "ladspa_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No descriptor function in library " << soname << std::endl;
+ return 0;
+ }
+
+ const LADSPA_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+ if (descriptor->Label == label)
+ return descriptor;
+ ++index;
+ }
+
+ std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No such plugin as " << label << " in library " << soname << std::endl;
+
+ return 0;
+}
+
+void
+LADSPAPluginFactory::loadLibrary(QString soName)
+{
+ void *libraryHandle = dlopen(soName.data(), RTLD_NOW);
+ if (libraryHandle)
+ m_libraryHandles[soName] = libraryHandle;
+}
+
+void
+LADSPAPluginFactory::unloadLibrary(QString soName)
+{
+ LibraryHandleMap::iterator li = m_libraryHandles.find(soName);
+ if (li != m_libraryHandles.end()) {
+ // std::cerr << "unloading " << soName << std::endl;
+ dlclose(m_libraryHandles[soName]);
+ m_libraryHandles.erase(li);
+ }
+}
+
+void
+LADSPAPluginFactory::unloadUnusedLibraries()
+{
+ std::vector<QString> toUnload;
+
+ for (LibraryHandleMap::iterator i = m_libraryHandles.begin();
+ i != m_libraryHandles.end(); ++i) {
+
+ bool stillInUse = false;
+
+ for (std::set
+ <RunnablePluginInstance *>::iterator ii = m_instances.begin();
+ ii != m_instances.end(); ++ii) {
+
+ QString itype, isoname, ilabel;
+ PluginIdentifier::parseIdentifier((*ii)->getIdentifier(), itype, isoname, ilabel);
+ if (isoname == i->first) {
+ stillInUse = true;
+ break;
+ }
+ }
+
+ if (!stillInUse)
+ toUnload.push_back(i->first);
+ }
+
+ for (std::vector<QString>::iterator i = toUnload.begin();
+ i != toUnload.end(); ++i) {
+ unloadLibrary(*i);
+ }
+}
+
+
+// It is only later, after they've gone,
+// I realize they have delivered a letter.
+// It's a letter from my wife. "What are you doing
+// there?" my wife asks. "Are you drinking?"
+// I study the postmark for hours. Then it, too, begins to fade.
+// I hope someday to forget all this.
+
+
+std::vector<QString>
+LADSPAPluginFactory::getPluginPath()
+{
+ std::vector<QString> pathList;
+ std::string path;
+
+ char *cpath = getenv("LADSPA_PATH");
+ if (cpath)
+ path = cpath;
+
+ if (path == "") {
+ path = "/usr/local/lib/ladspa:/usr/lib/ladspa";
+ char *home = getenv("HOME");
+ if (home)
+ path = std::string(home) + "/.ladspa:" + path;
+ }
+
+ std::string::size_type index = 0, newindex = 0;
+
+ while ((newindex = path.find(':', index)) < path.size()) {
+ pathList.push_back(path.substr(index, newindex - index).c_str());
+ index = newindex + 1;
+ }
+
+ pathList.push_back(path.substr(index).c_str());
+
+ return pathList;
+}
+
+
+#ifdef HAVE_LIBLRDF
+std::vector<QString>
+LADSPAPluginFactory::getLRDFPath(QString &baseUri)
+{
+ std::vector<QString> pathList = getPluginPath();
+ std::vector<QString> lrdfPaths;
+
+ lrdfPaths.push_back("/usr/local/share/ladspa/rdf");
+ lrdfPaths.push_back("/usr/share/ladspa/rdf");
+
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+ lrdfPaths.push_back(*i + "/rdf");
+ }
+
+ baseUri = LADSPA_BASE;
+ return lrdfPaths;
+}
+#endif
+
+void
+LADSPAPluginFactory::discoverPlugins()
+{
+ std::vector<QString> pathList = getPluginPath();
+
+ std::cerr << "LADSPAPluginFactory::discoverPlugins - "
+ << "discovering plugins; path is ";
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+ std::cerr << "[" << *i << "] ";
+ }
+ std::cerr << std::endl;
+
+// std::cerr << "LADSPAPluginFactory::discoverPlugins - "
+// << "trace is ";
+// std::cerr << kdBacktrace() << std::endl;
+
+#ifdef HAVE_LIBLRDF
+ // Initialise liblrdf and read the description files
+ //
+ lrdf_init();
+
+ QString baseUri;
+ std::vector<QString> lrdfPaths = getLRDFPath(baseUri);
+
+ bool haveSomething = false;
+
+ for (size_t i = 0; i < lrdfPaths.size(); ++i) {
+ QDir dir(lrdfPaths[i], "*.rdf;*.rdfs");
+ for (unsigned int j = 0; j < dir.count(); ++j) {
+ if (!lrdf_read_file(QString("file:" + lrdfPaths[i] + "/" + dir[j]).data())) {
+ // std::cerr << "LADSPAPluginFactory: read RDF file " << (lrdfPaths[i] + "/" + dir[j]) << std::endl;
+ haveSomething = true;
+ }
+ }
+ }
+
+ if (haveSomething) {
+ generateTaxonomy(baseUri + "Plugin", "");
+ }
+#endif // HAVE_LIBLRDF
+
+ generateFallbackCategories();
+
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+
+ QDir pluginDir(*i, "*.so");
+
+ for (unsigned int j = 0; j < pluginDir.count(); ++j) {
+ discoverPlugins(QString("%1/%2").arg(*i).arg(pluginDir[j]));
+ }
+ }
+
+#ifdef HAVE_LIBLRDF
+ // Cleanup after the RDF library
+ //
+ lrdf_cleanup();
+#endif // HAVE_LIBLRDF
+
+ std::cerr << "LADSPAPluginFactory::discoverPlugins - done" << std::endl;
+}
+
+void
+LADSPAPluginFactory::discoverPlugins(QString soName)
+{
+ void *libraryHandle = dlopen(soName.data(), RTLD_LAZY);
+
+ if (!libraryHandle) {
+ std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: couldn't dlopen "
+ << soName << " - " << dlerror() << std::endl;
+ return ;
+ }
+
+ LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function)
+ dlsym(libraryHandle, "ladspa_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: No descriptor function in " << soName << std::endl;
+ return ;
+ }
+
+ const LADSPA_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+
+#ifdef HAVE_LIBLRDF
+ char * def_uri = 0;
+ lrdf_defaults *defs = 0;
+
+ QString category = m_taxonomy[descriptor->UniqueID];
+
+ if (category == "" && descriptor->Name != 0) {
+ std::string name = descriptor->Name;
+ if (name.length() > 4 &&
+ name.substr(name.length() - 4) == " VST") {
+ category = "VST effects";
+ m_taxonomy[descriptor->UniqueID] = category;
+ }
+ }
+
+// std::cerr << "Plugin id is " << descriptor->UniqueID
+// << ", category is \"" << (category ? category : QString("(none)"))
+// << "\", name is " << descriptor->Name
+// << ", label is " << descriptor->Label
+// << std::endl;
+
+ def_uri = lrdf_get_default_uri(descriptor->UniqueID);
+ if (def_uri) {
+ defs = lrdf_get_setting_values(def_uri);
+ }
+
+ int controlPortNumber = 1;
+
+ for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
+
+ if (def_uri && defs) {
+
+ for (int j = 0; j < defs->count; j++) {
+ if (defs->items[j].pid == controlPortNumber) {
+ // std::cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << descriptor->PortNames[i] << std::endl;
+ m_portDefaults[descriptor->UniqueID][i] =
+ defs->items[j].value;
+ }
+ }
+ }
+
+ ++controlPortNumber;
+ }
+ }
+#endif // HAVE_LIBLRDF
+
+ QString identifier = PluginIdentifier::createIdentifier
+ ("ladspa", soName, descriptor->Label);
+// std::cerr << "Added plugin identifier " << identifier << std::endl;
+ m_identifiers.push_back(identifier);
+
+ ++index;
+ }
+
+ if (dlclose(libraryHandle) != 0) {
+ std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins - can't unload " << libraryHandle << std::endl;
+ return ;
+ }
+}
+
+void
+LADSPAPluginFactory::generateFallbackCategories()
+{
+ std::vector<QString> pluginPath = getPluginPath();
+ std::vector<QString> path;
+
+ for (size_t i = 0; i < pluginPath.size(); ++i) {
+ if (pluginPath[i].contains("/lib/")) {
+ QString p(pluginPath[i]);
+ p.replace("/lib/", "/share/");
+ path.push_back(p);
+ // std::cerr << "LADSPAPluginFactory::generateFallbackCategories: path element " << p << std::endl;
+ }
+ path.push_back(pluginPath[i]);
+ // std::cerr << "LADSPAPluginFactory::generateFallbackCategories: path element " << pluginPath[i] << std::endl;
+ }
+
+ for (size_t i = 0; i < path.size(); ++i) {
+
+ QDir dir(path[i], "*.cat");
+
+// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << std::endl;
+ for (unsigned int j = 0; j < dir.count(); ++j) {
+
+ QFile file(path[i] + "/" + dir[j]);
+
+ // std::cerr << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i] + "/" + dir[j]) << std::endl;
+
+ if (file.open(IO_ReadOnly)) {
+ // std::cerr << "...opened" << std::endl;
+ QTextStream stream(&file);
+ QString line;
+
+ while (!stream.eof()) {
+ line = stream.readLine();
+ // std::cerr << "line is: \"" << line << "\"" << std::endl;
+ QString id = line.section("::", 0, 0);
+ QString cat = line.section("::", 1, 1);
+ m_fallbackCategories[id] = cat;
+// std::cerr << "set id \"" << id << "\" to cat \"" << cat << "\"" << std::endl;
+ }
+ }
+ }
+ }
+}
+
+void
+LADSPAPluginFactory::generateTaxonomy(QString uri, QString base)
+{
+#ifdef HAVE_LIBLRDF
+ lrdf_uris *uris = lrdf_get_instances(uri.data());
+
+ if (uris != NULL) {
+ for (int i = 0; i < uris->count; ++i) {
+ m_taxonomy[lrdf_get_uid(uris->items[i])] = base;
+ }
+ lrdf_free_uris(uris);
+ }
+
+ uris = lrdf_get_subclasses(uri.data());
+
+ if (uris != NULL) {
+ for (int i = 0; i < uris->count; ++i) {
+ char *label = lrdf_get_label(uris->items[i]);
+ generateTaxonomy(uris->items[i],
+ base + (base.length() > 0 ? " > " : "") + label);
+ }
+ lrdf_free_uris(uris);
+ }
+#endif
+}
+
+}
+
+#endif // HAVE_LADSPA
+
diff --git a/src/sound/LADSPAPluginFactory.h b/src/sound/LADSPAPluginFactory.h
new file mode 100644
index 0000000..a5ec368
--- /dev/null
+++ b/src/sound/LADSPAPluginFactory.h
@@ -0,0 +1,104 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _LADSPA_PLUGIN_FACTORY_H_
+#define _LADSPA_PLUGIN_FACTORY_H_
+
+#ifdef HAVE_LADSPA
+
+#include "PluginFactory.h"
+#include <ladspa.h>
+
+#include <vector>
+#include <map>
+#include <set>
+#include <qstring.h>
+
+namespace Rosegarden
+{
+
+class LADSPAPluginInstance;
+
+class LADSPAPluginFactory : public PluginFactory
+{
+public:
+ virtual ~LADSPAPluginFactory();
+
+ virtual void discoverPlugins();
+
+ virtual const std::vector<QString> &getPluginIdentifiers() const;
+
+ virtual void enumeratePlugins(MappedObjectPropertyList &list);
+
+ virtual void populatePluginSlot(QString identifier, MappedPluginSlot &slot);
+
+ virtual RunnablePluginInstance *instantiatePlugin(QString identifier,
+ int instrumentId,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels);
+
+ MappedObjectValue getPortMinimum(const LADSPA_Descriptor *, int port);
+ MappedObjectValue getPortMaximum(const LADSPA_Descriptor *, int port);
+ MappedObjectValue getPortDefault(const LADSPA_Descriptor *, int port);
+ int getPortDisplayHint(const LADSPA_Descriptor *, int port);
+
+protected:
+ LADSPAPluginFactory();
+ friend class PluginFactory;
+
+ virtual std::vector<QString> getPluginPath();
+
+#ifdef HAVE_LIBLRDF
+ virtual std::vector<QString> getLRDFPath(QString &baseUri);
+#endif
+
+ virtual void discoverPlugins(QString soName);
+ virtual void generateTaxonomy(QString uri, QString base);
+ virtual void generateFallbackCategories();
+
+ virtual void releasePlugin(RunnablePluginInstance *, QString);
+
+ virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier);
+
+ void loadLibrary(QString soName);
+ void unloadLibrary(QString soName);
+ void unloadUnusedLibraries();
+
+ std::vector<QString> m_identifiers;
+
+ std::map<unsigned long, QString> m_taxonomy;
+ std::map<QString, QString> m_fallbackCategories;
+ std::map<unsigned long, std::map<int, float> > m_portDefaults;
+
+ std::set<RunnablePluginInstance *> m_instances;
+
+ typedef std::map<QString, void *> LibraryHandleMap;
+ LibraryHandleMap m_libraryHandles;
+};
+
+}
+
+#endif
+
+#endif
+
diff --git a/src/sound/LADSPAPluginInstance.cpp b/src/sound/LADSPAPluginInstance.cpp
new file mode 100644
index 0000000..e2b8890
--- /dev/null
+++ b/src/sound/LADSPAPluginInstance.cpp
@@ -0,0 +1,435 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <iostream>
+#include <cassert>
+
+#include "LADSPAPluginInstance.h"
+#include "LADSPAPluginFactory.h"
+
+#ifdef HAVE_LADSPA
+
+//#define DEBUG_LADSPA 1
+
+namespace Rosegarden
+{
+
+
+LADSPAPluginInstance::LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const LADSPA_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_instanceCount(0),
+ m_descriptor(descriptor),
+ m_blockSize(blockSize),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_bypassed(false)
+{
+ init(idealChannelCount);
+
+ m_inputBuffers = new sample_t * [m_instanceCount * m_audioPortsIn.size()];
+ m_outputBuffers = new sample_t * [m_instanceCount * m_audioPortsOut.size()];
+
+ for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) {
+ m_inputBuffers[i] = new sample_t[blockSize];
+ }
+ for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) {
+ m_outputBuffers[i] = new sample_t[blockSize];
+ }
+
+ m_ownBuffers = true;
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ }
+}
+
+LADSPAPluginInstance::LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const LADSPA_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_instanceCount(0),
+ m_descriptor(descriptor),
+ m_blockSize(blockSize),
+ m_inputBuffers(inputBuffers),
+ m_outputBuffers(outputBuffers),
+ m_ownBuffers(false),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_bypassed(false)
+{
+ init();
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ }
+}
+
+
+void
+LADSPAPluginInstance::init(int idealChannelCount)
+{
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init(" << idealChannelCount << "): plugin has "
+ << m_descriptor->PortCount << " ports" << std::endl;
+#endif
+
+ // Discover ports numbers and identities
+ //
+ for (unsigned long i = 0; i < m_descriptor->PortCount; ++i) {
+ if (LADSPA_IS_PORT_AUDIO(m_descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is audio in" << std::endl;
+#endif
+
+ m_audioPortsIn.push_back(i);
+ } else {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is audio out" << std::endl;
+#endif
+
+ m_audioPortsOut.push_back(i);
+ }
+ } else
+ if (LADSPA_IS_PORT_CONTROL(m_descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is control in" << std::endl;
+#endif
+
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+ m_controlPortsIn.push_back(
+ std::pair<unsigned long, LADSPA_Data*>(i, data));
+ } else {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is control out" << std::endl;
+#endif
+
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+ m_controlPortsOut.push_back(
+ std::pair<unsigned long, LADSPA_Data*>(i, data));
+ if (!strcmp(m_descriptor->PortNames[i], "latency") ||
+ !strcmp(m_descriptor->PortNames[i], "_latency")) {
+#ifdef DEBUG_LADSPA
+ std::cerr << "Wooo! We have a latency port!" << std::endl;
+#endif
+
+ m_latencyPort = data;
+ }
+ }
+ }
+#ifdef DEBUG_LADSPA
+ else
+ std::cerr << "LADSPAPluginInstance::init - "
+ << "unrecognised port type" << std::endl;
+#endif
+
+ }
+
+ m_instanceCount = 1;
+
+ if (idealChannelCount > 0) {
+ if (m_audioPortsIn.size() == 1) {
+ // mono plugin: duplicate it if need be
+ m_instanceCount = idealChannelCount;
+ }
+ }
+}
+
+size_t
+LADSPAPluginInstance::getLatency()
+{
+ if (m_latencyPort) {
+ if (!m_run) {
+ for (int i = 0; i < getAudioInputCount(); ++i) {
+ for (int j = 0; j < m_blockSize; ++j) {
+ m_inputBuffers[i][j] = 0.f;
+ }
+ }
+ run(RealTime::zeroTime);
+ }
+ return *m_latencyPort;
+ }
+ return 0;
+}
+
+void
+LADSPAPluginInstance::silence()
+{
+ if (isOK()) {
+ deactivate();
+ activate();
+ }
+}
+
+void
+LADSPAPluginInstance::setIdealChannelCount(size_t channels)
+{
+ if (m_audioPortsIn.size() != 1 || channels == m_instanceCount) {
+ silence();
+ return ;
+ }
+
+ if (isOK()) {
+ deactivate();
+ }
+
+ //!!! don't we need to reallocate inputBuffers and outputBuffers?
+
+ cleanup();
+ m_instanceCount = channels;
+ instantiate(m_sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ }
+}
+
+
+LADSPAPluginInstance::~LADSPAPluginInstance()
+{
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::~LADSPAPluginInstance" << std::endl;
+#endif
+
+ if (m_instanceHandles.size() != 0) { // "isOK()"
+ deactivate();
+ }
+
+ cleanup();
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i)
+ delete m_controlPortsIn[i].second;
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i)
+ delete m_controlPortsOut[i].second;
+
+ m_controlPortsIn.clear();
+ m_controlPortsOut.clear();
+
+ if (m_ownBuffers) {
+ for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+ delete[] m_inputBuffers[i];
+ }
+ for (size_t i = 0; i < m_audioPortsOut.size(); ++i) {
+ delete[] m_outputBuffers[i];
+ }
+
+ delete[] m_inputBuffers;
+ delete[] m_outputBuffers;
+ }
+
+ m_audioPortsIn.clear();
+ m_audioPortsOut.clear();
+}
+
+
+void
+LADSPAPluginInstance::instantiate(unsigned long sampleRate)
+{
+#ifdef DEBUG_LADSPA
+ std::cout << "LADSPAPluginInstance::instantiate - plugin unique id = "
+ << m_descriptor->UniqueID << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ if (!m_descriptor->instantiate) {
+ std::cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
+ << ":" << m_descriptor->Label
+ << " has no instantiate method!" << std::endl;
+ return ;
+ }
+
+ for (int i = 0; i < m_instanceCount; ++i) {
+ m_instanceHandles.push_back
+ (m_descriptor->instantiate(m_descriptor, sampleRate));
+ }
+}
+
+void
+LADSPAPluginInstance::activate()
+{
+ if (!m_descriptor || !m_descriptor->activate)
+ return ;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->activate(*hi);
+ }
+}
+
+void
+LADSPAPluginInstance::connectPorts()
+{
+ if (!m_descriptor || !m_descriptor->connect_port)
+ return ;
+
+ assert(sizeof(LADSPA_Data) == sizeof(float));
+ assert(sizeof(sample_t) == sizeof(float));
+
+ int inbuf = 0, outbuf = 0;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+
+ for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_audioPortsIn[i],
+ (LADSPA_Data *)m_inputBuffers[inbuf]);
+ ++inbuf;
+ }
+
+ for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_audioPortsOut[i],
+ (LADSPA_Data *)m_outputBuffers[outbuf]);
+ ++outbuf;
+ }
+
+ // If there is more than one instance, they all share the same
+ // control port ins (and outs, for the moment, because we
+ // don't actually do anything with the outs anyway -- but they
+ // do have to be connected as the plugin can't know if they're
+ // not and will write to them anyway).
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_controlPortsIn[i].first,
+ m_controlPortsIn[i].second);
+ }
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_controlPortsOut[i].first,
+ m_controlPortsOut[i].second);
+ }
+ }
+}
+
+void
+LADSPAPluginInstance::setPortValue(unsigned int portNumber, float value)
+{
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
+ if (f) {
+ if (value < f->getPortMinimum(m_descriptor, portNumber)) {
+ value = f->getPortMinimum(m_descriptor, portNumber);
+ }
+ if (value > f->getPortMaximum(m_descriptor, portNumber)) {
+ value = f->getPortMaximum(m_descriptor, portNumber);
+ }
+ }
+ (*m_controlPortsIn[i].second) = value;
+ }
+ }
+}
+
+float
+LADSPAPluginInstance::getPortValue(unsigned int portNumber)
+{
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ return (*m_controlPortsIn[i].second);
+ }
+ }
+
+ return 0.0;
+}
+
+void
+LADSPAPluginInstance::run(const RealTime &)
+{
+ if (!m_descriptor || !m_descriptor->run)
+ return ;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->run(*hi, m_blockSize);
+ }
+
+ m_run = true;
+}
+
+void
+LADSPAPluginInstance::deactivate()
+{
+ if (!m_descriptor || !m_descriptor->deactivate)
+ return ;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->deactivate(*hi);
+ }
+}
+
+void
+LADSPAPluginInstance::cleanup()
+{
+ if (!m_descriptor)
+ return ;
+
+ if (!m_descriptor->cleanup) {
+ std::cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
+ << ":" << m_descriptor->Label
+ << " has no cleanup method!" << std::endl;
+ return ;
+ }
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->cleanup(*hi);
+ }
+
+ m_instanceHandles.clear();
+}
+
+
+
+}
+
+#endif // HAVE_LADSPA
+
+
diff --git a/src/sound/LADSPAPluginInstance.h b/src/sound/LADSPAPluginInstance.h
new file mode 100644
index 0000000..9654cfb
--- /dev/null
+++ b/src/sound/LADSPAPluginInstance.h
@@ -0,0 +1,137 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <vector>
+#include <set>
+#include <qstring.h>
+#include "Instrument.h"
+
+#ifndef _LADSPAPLUGININSTANCE_H_
+#define _LADSPAPLUGININSTANCE_H_
+
+#ifdef HAVE_LADSPA
+
+#include <ladspa.h>
+#include "RunnablePluginInstance.h"
+
+namespace Rosegarden
+{
+
+// LADSPA plugin instance. LADSPA is a variable block size API, but
+// for one reason and another it's more convenient to use a fixed
+// block size in this wrapper.
+//
+class LADSPAPluginInstance : public RunnablePluginInstance
+{
+public:
+ virtual ~LADSPAPluginInstance();
+
+ virtual bool isOK() const { return m_instanceHandles.size() != 0; }
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ virtual QString getIdentifier() const { return m_identifier; }
+ int getPosition() const { return m_position; }
+
+ virtual void run(const RealTime &rt);
+
+ virtual void setPortValue(unsigned int portNumber, float value);
+ virtual float getPortValue(unsigned int portNumber);
+
+ virtual size_t getBufferSize() { return m_blockSize; }
+ virtual size_t getAudioInputCount() { return m_instanceCount * m_audioPortsIn.size(); }
+ virtual size_t getAudioOutputCount() { return m_instanceCount * m_audioPortsOut.size(); }
+ virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; }
+ virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; }
+
+ virtual bool isBypassed() const { return m_bypassed; }
+ virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; }
+
+ virtual size_t getLatency();
+
+ virtual void silence();
+ virtual void setIdealChannelCount(size_t channels); // may re-instantiate
+
+protected:
+ // To be constructed only by LADSPAPluginFactory
+ friend class LADSPAPluginFactory;
+
+ // Constructor that creates the buffers internally
+ //
+ LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const LADSPA_Descriptor* descriptor);
+
+ // Constructor that uses shared buffers
+ //
+ LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const LADSPA_Descriptor* descriptor);
+
+ void init(int idealChannelCount = 0);
+ void instantiate(unsigned long sampleRate);
+ void cleanup();
+ void activate();
+ void deactivate();
+
+ // Connection of data (and behind the scenes control) ports
+ //
+ void connectPorts();
+
+ InstrumentId m_instrument;
+ int m_position;
+ std::vector<LADSPA_Handle> m_instanceHandles;
+ size_t m_instanceCount;
+ const LADSPA_Descriptor *m_descriptor;
+
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsIn;
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsOut;
+
+ std::vector<int> m_audioPortsIn;
+ std::vector<int> m_audioPortsOut;
+
+ size_t m_blockSize;
+ sample_t **m_inputBuffers;
+ sample_t **m_outputBuffers;
+ bool m_ownBuffers;
+ size_t m_sampleRate;
+ float *m_latencyPort;
+ bool m_run;
+
+ bool m_bypassed;
+};
+
+}
+
+#endif // HAVE_LADSPA
+
+#endif // _LADSPAPLUGININSTANCE_H_
+
diff --git a/src/sound/MP3AudioFile.cpp b/src/sound/MP3AudioFile.cpp
new file mode 100644
index 0000000..700169f
--- /dev/null
+++ b/src/sound/MP3AudioFile.cpp
@@ -0,0 +1,329 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MP3AudioFile.h"
+
+#ifdef HAVE_LIBMAD
+
+#include <mad.h>
+
+namespace Rosegarden
+{
+
+
+/*
+ * This is a private message structure. A generic pointer to this structure
+ * is passed to each of the callback functions. Put here any data you need
+ * to access from within the callbacks.
+ */
+
+struct player
+{
+ unsigned char const *start;
+ unsigned long length;
+ //int default_driver;
+ //ao_device *device;
+ //ao_sample_format format;
+ //class SoundTouch *touch;
+};
+
+
+
+MP3AudioFile::MP3AudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName):
+ AudioFile(id, name, fileName)
+{
+ m_type = MP3;
+}
+
+
+MP3AudioFile::MP3AudioFile(const std::string &fileName,
+ unsigned int /*channels*/,
+ unsigned int /*sampleRate*/,
+ unsigned int /*bytesPerSecond*/,
+ unsigned int /*bytesPerSample*/,
+ unsigned int /*bitsPerSample*/):
+ AudioFile(0, std::string(""), fileName)
+{
+ m_type = MP3;
+}
+
+
+MP3AudioFile::~MP3AudioFile()
+{}
+
+bool
+MP3AudioFile::open()
+{
+ // if already open
+ if (m_inFile && (*m_inFile))
+ return true;
+
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!(*m_inFile)) {
+ m_type = UNKNOWN;
+ return false;
+ }
+
+ // Get the file size and store it for comparison later
+ m_fileSize = m_fileInfo->size();
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException s) {
+ throw(s);
+ }
+
+ return true;
+}
+
+bool
+MP3AudioFile::write()
+{
+ return false;
+}
+
+void
+MP3AudioFile::close()
+{}
+
+void
+MP3AudioFile::parseHeader()
+{
+ const std::string MP3_TAG("TAG");
+ if (m_inFile == 0)
+ return ;
+
+ // store size conveniently
+ m_fileSize = m_fileInfo->size();
+
+ if (m_fileSize == 0) {
+ std::string mess = std::string("\"") + m_fileName +
+ std::string("\" is empty - invalid MP3 file");
+ throw(mess);
+ }
+
+ // seek to beginning
+ m_inFile->seekg(0, std::ios::beg);
+
+ // get some header information
+ //
+ const int bufferLength = 3096;
+ std::string hS = getBytes(bufferLength);
+ bool foundMP3 = false;
+
+ for (unsigned int i = 0; i < hS.length() - 1; ++i) {
+ if ((hS[i] & 0xff) == 0xff && (hS[i + 1] & 0xe0) == 0xe0) {
+ foundMP3 = true;
+ break;
+ }
+ }
+
+ if (foundMP3 == false || (int)hS.length() < bufferLength) {
+ std::string mess = std::string("\"") + m_fileName +
+ std::string("\" doesn't appear to be a valid MP3 file");
+ throw(mess);
+ }
+
+ // guess most likely values - these are reset during decoding
+ m_channels = 2;
+ m_sampleRate = 44100;
+
+ mad_synth synth;
+ mad_frame frame;
+ mad_stream stream;
+
+ mad_synth_init(&synth);
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+
+ /*
+ mad_stream_buffer(&stream, hS.data(), hS.length());
+
+ if (mad_header_decode(&frame.header, &stream) == -1)
+ {
+ throw("Can't decode header");
+ }
+
+ mad_frame_decode(&frame, &stream);
+
+ m_sampleRate = frame.header.samplerate;
+
+ mad_synth_frame(&synth, &frame);
+ struct mad_pcm *pcm = &synth.pcm;
+
+ m_channels = pcm->channels;
+ */
+
+ /*
+ struct player player;
+ struct mad_decoder decoder;
+ struct stat stat;
+ void *fdm;
+ int result;
+
+ if (fstat(fd, &stat) == -1 ||
+ stat.st_size == 0)
+ return 0;
+
+ fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (fdm == MAP_FAILED) {
+ fprintf(stderr, "mmap failed, aborting...\n");
+ return 0;
+ }
+
+ player.start = (unsigned char *)fdm;
+ player.length = stat.st_size;
+ player.default_driver = ao_default_driver_id();
+ player.device = NULL;
+ player.touch = new SoundTouch;
+ player.touch->setTempo(tempo);
+ player.touch->setPitch(pitch);
+ mad_decoder_init(&decoder, &player,
+ input, 0 , 0 , process_output,
+ decode_error, 0);
+
+ result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
+ mad_decoder_finish(&decoder);
+ delete player.touch;
+ ao_close(player.device);
+ if (munmap((void *)player.start, stat.st_size) == -1)
+ return 4;
+
+ return result;
+ */
+}
+
+std::streampos
+MP3AudioFile::getDataOffset()
+{
+ return 0;
+}
+
+bool
+MP3AudioFile::scanTo(const RealTime & /*time*/)
+{
+ return false;
+}
+
+bool
+MP3AudioFile::scanTo(std::ifstream * /*file*/, const RealTime & /*time*/)
+{
+ return false;
+}
+
+
+// Scan forward in a file by a certain amount of time
+//
+bool
+MP3AudioFile::scanForward(const RealTime & /*time*/)
+{
+ return false;
+}
+
+bool
+MP3AudioFile::scanForward(std::ifstream * /*file*/, const RealTime & /*time*/)
+{
+ return false;
+}
+
+
+// Return a number of samples - caller will have to
+// de-interleave n-channel samples themselves.
+//
+std::string
+MP3AudioFile::getSampleFrames(std::ifstream * /*file*/,
+ unsigned int /*frames*/)
+{
+ return "";
+}
+
+unsigned int
+MP3AudioFile::getSampleFrames(std::ifstream * /*file*/,
+ char * /* buf */,
+ unsigned int /*frames*/)
+{
+ return 0;
+}
+
+std::string
+MP3AudioFile::getSampleFrames(unsigned int /*frames*/)
+{
+ return "";
+}
+
+
+// Return a number of (possibly) interleaved samples
+// over a time slice from current file pointer position.
+//
+std::string
+MP3AudioFile::getSampleFrameSlice(std::ifstream * /*file*/,
+ const RealTime & /*time*/)
+{
+ return "";
+}
+
+std::string
+MP3AudioFile::getSampleFrameSlice(const RealTime & /*time*/)
+{
+ return "";
+}
+
+
+// Append a string of samples to an already open (for writing)
+// audio file.
+//
+bool
+MP3AudioFile::appendSamples(const std::string & /*buffer*/)
+{
+ return false;
+}
+
+bool
+MP3AudioFile::appendSamples(const char * /*buffer*/, unsigned int)
+{
+ return false;
+}
+
+
+// Get the length of the sample in Seconds/Microseconds
+//
+RealTime
+MP3AudioFile::getLength()
+{
+ return RealTime(0, 0);
+}
+
+void
+MP3AudioFile::printStats()
+{}
+
+
+
+
+
+}
+
+#endif // HAVE_LIBMAD
diff --git a/src/sound/MP3AudioFile.h b/src/sound/MP3AudioFile.h
new file mode 100644
index 0000000..2423cd9
--- /dev/null
+++ b/src/sound/MP3AudioFile.h
@@ -0,0 +1,128 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _MP3AUDIOFILE_H_
+#define _MP3AUDIOFILE_H_
+
+#ifdef HAVE_LIBMAD
+
+#include "AudioFile.h"
+
+namespace Rosegarden
+{
+
+class MP3AudioFile : public AudioFile
+{
+public:
+ MP3AudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName);
+
+ MP3AudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerSample,
+ unsigned int bitsPerSample);
+
+ ~MP3AudioFile();
+
+ // Override these methods for the WAV
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ // Show the information we have on this file
+ //
+ virtual void printStats();
+
+ // Get all header information
+ //
+ void parseHeader();
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset();
+
+ // Peak file name
+ //
+ virtual std::string getPeakFilename()
+ { return (m_fileName + std::string(".pk")); }
+
+ // scan time was valid and successful.
+ //
+ virtual bool scanTo(const RealTime &time);
+ virtual bool scanTo(std::ifstream *file, const RealTime &time);
+
+ // Scan forward in a file by a certain amount of time
+ //
+ virtual bool scanForward(const RealTime &time);
+ virtual bool scanForward(std::ifstream *file, const RealTime &time);
+
+ // 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);
+ virtual unsigned int getSampleFrames(std::ifstream *file,
+ char *buf,
+ unsigned int frames);
+ virtual std::string getSampleFrames(unsigned int frames);
+
+ // 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);
+ virtual std::string getSampleFrameSlice(const RealTime &time);
+
+ // Append a string of samples to an already open (for writing)
+ // audio file.
+ //
+ virtual bool appendSamples(const std::string &buffer);
+ virtual bool appendSamples(const char *buffer, unsigned int samples);
+
+ // Get the length of the sample in Seconds/Microseconds
+ //
+ virtual RealTime getLength();
+
+ virtual unsigned int getBytesPerFrame() { return 0; }
+
+
+ //!!! NOT IMPLEMENTED YET
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false) { return false; }
+
+};
+
+}
+
+#endif // HAVE_LIBMAD
+
+#endif // _MP3AUDIOFILE_H_
+
diff --git a/src/sound/MappedCommon.h b/src/sound/MappedCommon.h
new file mode 100644
index 0000000..5ef5487
--- /dev/null
+++ b/src/sound/MappedCommon.h
@@ -0,0 +1,68 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _MAPPEDCOMMON_H_
+#define _MAPPEDCOMMON_H_
+
+// Some Mapped types that gui and sound libraries use to communicate
+// plugin and Studio information. Putting them here so we can change
+// MappedStudio regularly without having to rebuild the gui.
+//
+#include <vector>
+
+#include <qstring.h>
+#include <qdatastream.h>
+
+namespace Rosegarden
+{
+
+typedef int MappedObjectId;
+typedef QString MappedObjectProperty;
+typedef float MappedObjectValue;
+
+// typedef QValueVector<MappedObjectProperty> MappedObjectPropertyList;
+// replaced with a std::vector<> for Qt2 compatibility
+
+typedef std::vector<MappedObjectId> MappedObjectIdList;
+typedef std::vector<MappedObjectProperty> MappedObjectPropertyList;
+typedef std::vector<MappedObjectValue> MappedObjectValueList;
+
+// The direction in which a port operates.
+//
+typedef enum
+{
+ ReadOnly, // input port
+ WriteOnly, // output port
+ Duplex
+} PortDirection;
+
+QDataStream& operator>>(QDataStream& s, MappedObjectIdList&);
+QDataStream& operator<<(QDataStream&, const MappedObjectIdList&);
+
+QDataStream& operator>>(QDataStream& s, MappedObjectPropertyList&);
+QDataStream& operator<<(QDataStream&, const MappedObjectPropertyList&);
+
+QDataStream& operator>>(QDataStream& s, MappedObjectValueList&);
+QDataStream& operator<<(QDataStream&, const MappedObjectValueList&);
+
+}
+
+#endif // _MAPPEDCOMMON_H_
diff --git a/src/sound/MappedComposition.cpp b/src/sound/MappedComposition.cpp
new file mode 100644
index 0000000..975fccf
--- /dev/null
+++ b/src/sound/MappedComposition.cpp
@@ -0,0 +1,216 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <qdatastream.h>
+#include "MappedComposition.h"
+#include "MappedEvent.h"
+#include "SegmentPerformanceHelper.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+
+using std::cerr;
+using std::cout;
+using std::endl;
+
+MappedComposition::~MappedComposition()
+{
+ clear();
+}
+
+// copy constructor
+MappedComposition::MappedComposition(const MappedComposition &mC):
+ std::multiset<MappedEvent *, MappedEvent::MappedEventCmp>()
+{
+ clear();
+
+ // deep copy
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); it++)
+ this->insert(new MappedEvent(**it));
+
+}
+
+// Turn a MappedComposition into a QDataStream - MappedEvents can
+// stream themselves.
+//
+QDataStream&
+operator<<(QDataStream &dS, MappedComposition *mC)
+{
+ dS << int(mC->size());
+
+ for (MappedCompositionIterator it = mC->begin(); it != mC->end(); ++it )
+ dS << (*it);
+
+ return dS;
+}
+
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedComposition &mC)
+{
+ dS << int(mC.size());
+
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); ++it )
+ dS << (*it);
+
+ return dS;
+}
+
+
+// Turn a QDataStream into a MappedComposition
+//
+QDataStream&
+operator>>(QDataStream &dS, MappedComposition *mC)
+{
+ int sliceSize;
+ MappedEvent *mE;
+
+ dS >> sliceSize;
+
+ while (!dS.atEnd() && sliceSize) {
+ mE = new MappedEvent();
+ dS >> mE;
+
+ try {
+ mC->insert(mE);
+ } catch (...) {
+ ;
+ }
+
+ sliceSize--;
+
+ }
+
+#ifdef DEBUG_MAPPEDCOMPOSITION
+ if (sliceSize) {
+ cerr << "operator>> - wrong number of events received" << endl;
+ }
+#endif
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedComposition &mC)
+{
+ int sliceSize;
+ MappedEvent *mE;
+
+ dS >> sliceSize;
+
+ while (!dS.atEnd() && sliceSize) {
+ mE = new MappedEvent();
+
+ dS >> mE;
+
+ try {
+ mC.insert(mE);
+ } catch (...) {
+ ;
+ }
+
+ sliceSize--;
+
+ }
+
+#ifdef DEBUG_MAPPEDCOMPOSITION
+ if (sliceSize) {
+ cerr << "operator>> - wrong number of events received" << endl;
+ }
+#endif
+
+
+ return dS;
+}
+
+// Move the start time of this MappedComposition and all its events.
+// Actually - we have a special case for audio events at the moment..
+//
+//
+void
+MappedComposition::moveStartTime(const RealTime &mT)
+{
+ MappedCompositionIterator it;
+
+ for (it = this->begin(); it != this->end(); ++it) {
+ // Reset start time and duration
+ //
+ (*it)->setEventTime((*it)->getEventTime() + mT);
+ (*it)->setDuration((*it)->getDuration() - mT);
+
+ // For audio adjust the start index
+ //
+ if ((*it)->getType() == MappedEvent::Audio)
+ (*it)->setAudioStartMarker((*it)->getAudioStartMarker() + mT);
+ }
+
+ m_startTime = m_startTime + mT;
+ m_endTime = m_endTime + mT;
+
+}
+
+
+// Concatenate MappedComposition
+//
+MappedComposition&
+MappedComposition::operator+(const MappedComposition &mC)
+{
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); it++)
+ this->insert(new MappedEvent(**it)); // deep copy
+
+ return *this;
+}
+
+// Assign (clear and deep copy)
+//
+MappedComposition&
+MappedComposition::operator=(const MappedComposition &mC)
+{
+ if (&mC == this)
+ return * this;
+
+ clear();
+
+ // deep copy
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); it++)
+ this->insert(new MappedEvent(**it));
+
+ return *this;
+}
+
+void
+MappedComposition::clear()
+{
+ // Only clear if the events aren't persistent
+ //
+ for (MappedCompositionIterator it = this->begin(); it != this->end(); it++)
+ if (!(*it)->isPersistent())
+ delete (*it);
+
+ this->erase(this->begin(), this->end());
+}
+
+
+
+}
+
+
diff --git a/src/sound/MappedComposition.h b/src/sound/MappedComposition.h
new file mode 100644
index 0000000..bfa7c05
--- /dev/null
+++ b/src/sound/MappedComposition.h
@@ -0,0 +1,93 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _MAPPEDCOMPOSITION_H_
+#define _MAPPEDCOMPOSITION_H_
+
+
+// MappedComposition is used with MappedEvent to create a sequence
+// of MIDI ready events ready for playing. The QDataStream operators
+// are a necessary part of the DCOP transmission process allowing
+// the whole class to be serialized. The core application is sent
+// a request specifying a time slice between given start and end
+// points which it fills with MappedEvents which are cut down
+// (sequencer suitable) versions of the core Events.
+//
+
+#include <Composition.h>
+#include "MappedEvent.h"
+#include <set>
+#include <qdatastream.h>
+
+namespace Rosegarden
+{
+
+class MappedComposition : public std::multiset<MappedEvent *,
+ MappedEvent::MappedEventCmp>
+{
+public:
+ MappedComposition():m_startTime(0, 0), m_endTime(0, 0) {;}
+
+ MappedComposition(const RealTime &sT,
+ const RealTime &eT):
+ m_startTime(sT), m_endTime(eT) {;}
+
+ MappedComposition(const MappedComposition &mC);
+
+ ~MappedComposition();
+
+ const RealTime getStartTime() const { return m_startTime; }
+ const RealTime getEndTime() const { return m_endTime; }
+ void setStartTime(const RealTime &sT) { m_startTime = sT; }
+ void setEndTime(const RealTime &eT) { m_endTime = eT; }
+
+ // When we're looping we want to be able to move the start
+ // time of MappedEvents around in this container
+ //
+ void moveStartTime(const RealTime &mT);
+
+ MappedComposition& operator+(const MappedComposition &mC);
+ MappedComposition& operator=(const MappedComposition &mC);
+
+ // This section is used for serialising this class over DCOP
+ //
+ //
+ friend QDataStream& operator>>(QDataStream &dS, MappedComposition *mC);
+ friend QDataStream& operator<<(QDataStream &dS, MappedComposition *mC);
+ friend QDataStream& operator>>(QDataStream &dS, MappedComposition &mC);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedComposition &mC);
+
+ // Clear out
+ void clear();
+
+private:
+ RealTime m_startTime;
+ RealTime m_endTime;
+
+};
+
+typedef std::multiset<MappedEvent *, MappedEvent::MappedEventCmp>::iterator MappedCompositionIterator;
+
+
+}
+
+#endif // _MAPPEDCOMPOSITION_H_
diff --git a/src/sound/MappedDevice.cpp b/src/sound/MappedDevice.cpp
new file mode 100644
index 0000000..619be2a
--- /dev/null
+++ b/src/sound/MappedDevice.cpp
@@ -0,0 +1,250 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "MappedDevice.h"
+#include "MappedInstrument.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+
+MappedDevice::MappedDevice():
+ std::vector<MappedInstrument*>(),
+ m_id(Device::NO_DEVICE),
+ m_type(Device::Midi),
+ m_name("Unconfigured device"),
+ m_connection(""),
+ m_direction(MidiDevice::Play),
+ m_recording(false)
+{}
+
+MappedDevice::MappedDevice(DeviceId id,
+ Device::DeviceType type,
+ std::string name,
+ std::string connection):
+ std::vector<MappedInstrument*>(),
+ m_id(id),
+ m_type(type),
+ m_name(name),
+ m_connection(connection),
+ m_direction(MidiDevice::Play),
+ m_recording(false)
+{}
+
+MappedDevice::~MappedDevice()
+{}
+
+MappedDevice::MappedDevice(const MappedDevice &mD):
+ std::vector<MappedInstrument*>()
+{
+ clear();
+
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ this->push_back(new MappedInstrument(**it));
+
+ m_id = mD.getId();
+ m_type = mD.getType();
+ m_name = mD.getName();
+ m_connection = mD.getConnection();
+ m_direction = mD.getDirection();
+ m_recording = mD.isRecording();
+}
+
+void
+MappedDevice::clear()
+{
+ MappedDeviceIterator it;
+
+ for (it = this->begin(); it != this->end(); it++)
+ delete (*it);
+
+ this->erase(this->begin(), this->end());
+}
+
+MappedDevice&
+MappedDevice::operator+(const MappedDevice &mD)
+{
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ this->push_back(new MappedInstrument(**it));
+
+ return *this;
+}
+
+MappedDevice&
+MappedDevice::operator=(const MappedDevice &mD)
+{
+ if (&mD == this)
+ return * this;
+
+ clear();
+
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ this->push_back(new MappedInstrument(**it));
+
+ m_id = mD.getId();
+ m_type = mD.getType();
+ m_name = mD.getName();
+ m_connection = mD.getConnection();
+ m_direction = mD.getDirection();
+ m_recording = mD.isRecording();
+
+ return *this;
+}
+
+
+QDataStream&
+operator>>(QDataStream &dS, MappedDevice *mD)
+{
+ int instruments = 0;
+ dS >> instruments;
+
+ MappedInstrument mI;
+ while (!dS.atEnd() && instruments) {
+ dS >> mI;
+ mD->push_back(new MappedInstrument(mI));
+ instruments--;
+ }
+
+ QString name;
+ unsigned int id, dType;
+ QString connection;
+ unsigned int direction;
+ unsigned int recording;
+
+ dS >> id;
+ dS >> dType;
+ dS >> name;
+ dS >> connection;
+ dS >> direction;
+ dS >> recording;
+ mD->setId(id);
+ mD->setType(Device::DeviceType(dType));
+ mD->setName(std::string(name.data()));
+ mD->setConnection(connection.data());
+ mD->setDirection(MidiDevice::DeviceDirection(direction));
+ mD->setRecording((bool)recording);
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ if (instruments) {
+ std::cerr << "MappedDevice::operator>> - "
+ << "wrong number of events received" << std::endl;
+ }
+#endif
+
+ return dS;
+}
+
+
+QDataStream&
+operator>>(QDataStream &dS, MappedDevice &mD)
+{
+ int instruments;
+ dS >> instruments;
+
+ MappedInstrument mI;
+
+ while (!dS.atEnd() && instruments) {
+ dS >> mI;
+ mD.push_back(new MappedInstrument(mI));
+ instruments--;
+ }
+
+ unsigned int id, dType;
+ QString name;
+ QString connection;
+ unsigned int direction;
+ unsigned int recording;
+
+ dS >> id;
+ dS >> dType;
+ dS >> name;
+ dS >> connection;
+ dS >> direction;
+ dS >> recording;
+ mD.setId(id);
+ mD.setType(Device::DeviceType(dType));
+ mD.setName(std::string(name.data()));
+ mD.setConnection(connection.data());
+ mD.setDirection(MidiDevice::DeviceDirection(direction));
+ mD.setRecording((bool)recording);
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ if (instruments) {
+ std::cerr << "MappedDevice::operator>> - "
+ << "wrong number of events received" << std::endl;
+ }
+#endif
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, MappedDevice *mD)
+{
+ dS << (int)mD->size();
+
+ for (MappedDeviceIterator it = mD->begin(); it != mD->end(); it++)
+ dS << (*it);
+
+ dS << (unsigned int)(mD->getId());
+ dS << (int)(mD->getType());
+ dS << QString(mD->getName().c_str());
+ dS << QString(mD->getConnection().c_str());
+ dS << mD->getDirection();
+ dS << (unsigned int)(mD->isRecording());
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ std::cerr << "MappedDevice::operator>> - wrote \"" << mD->getConnection() << "\""
+ << std::endl;
+#endif
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedDevice &mD)
+{
+ dS << (int)mD.size();
+
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ dS << (*it);
+
+ dS << (unsigned int)(mD.getId());
+ dS << (int)(mD.getType());
+ dS << QString(mD.getName().c_str());
+ dS << QString(mD.getConnection().c_str());
+ dS << mD.getDirection();
+ dS << (unsigned int)(mD.isRecording());
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ std::cerr << "MappedDevice::operator>> - wrote \"" << mD.getConnection() << "\""
+ << std::endl;
+#endif
+
+ return dS;
+}
+
+}
+
diff --git a/src/sound/MappedDevice.h b/src/sound/MappedDevice.h
new file mode 100644
index 0000000..f53bb37
--- /dev/null
+++ b/src/sound/MappedDevice.h
@@ -0,0 +1,103 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <string>
+#include <vector>
+
+#include <qdatastream.h>
+
+#include "Device.h"
+#include "MidiDevice.h"
+#include "MappedCommon.h"
+
+#ifndef _MAPPEDDEVICE_H_
+#define _MAPPEDDEVICE_H_
+
+// A DCOP wrapper to get MappedInstruments across to the GUI
+//
+
+namespace Rosegarden
+{
+
+class MappedInstrument;
+
+class MappedDevice : public std::vector<MappedInstrument*>
+{
+public:
+ MappedDevice();
+ MappedDevice(DeviceId id,
+ Device::DeviceType type,
+ std::string name,
+ std::string connection = "");
+
+ MappedDevice(const MappedDevice &mD);
+ ~MappedDevice();
+
+ // Clear down
+ //
+ void clear();
+
+ MappedDevice& operator+(const MappedDevice &mD);
+ MappedDevice& operator=(const MappedDevice &mD);
+
+ friend QDataStream& operator>>(QDataStream &dS, MappedDevice *mD);
+ friend QDataStream& operator<<(QDataStream &dS, MappedDevice *mD);
+ friend QDataStream& operator>>(QDataStream &dS, MappedDevice &mD);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedDevice &mD);
+
+ std::string getName() const { return m_name; }
+ void setName(const std::string &name) { m_name = name; }
+
+ DeviceId getId() const { return m_id; }
+ void setId(DeviceId id) { m_id = id; }
+
+ Device::DeviceType getType() const { return m_type; }
+ void setType(Device::DeviceType type) { m_type = type; }
+
+ std::string getConnection() const { return m_connection; }
+ void setConnection(std::string connection) { m_connection = connection; }
+
+ MidiDevice::DeviceDirection getDirection() const { return m_direction; }
+ void setDirection(MidiDevice::DeviceDirection direction) { m_direction = direction; }
+
+ bool isRecording() const { return m_recording; }
+ void setRecording(bool recording) { m_recording = recording; }
+
+protected:
+
+ DeviceId m_id;
+ Device::DeviceType m_type;
+ std::string m_name;
+ std::string m_connection;
+ MidiDevice::DeviceDirection m_direction;
+ bool m_recording;
+};
+
+typedef std::vector<MappedInstrument*>::const_iterator
+ MappedDeviceConstIterator;
+
+typedef std::vector<MappedInstrument*>::iterator
+ MappedDeviceIterator;
+
+}
+
+#endif // _MAPPEDDEVICE_H_
+
diff --git a/src/sound/MappedEvent.cpp b/src/sound/MappedEvent.cpp
new file mode 100644
index 0000000..9b4ccab
--- /dev/null
+++ b/src/sound/MappedEvent.cpp
@@ -0,0 +1,593 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+
+#include <kstddirs.h>
+
+#include "MappedEvent.h"
+#include "BaseProperties.h"
+#include "Midi.h"
+#include "MidiTypes.h"
+
+#define DEBUG_MAPPEDEVENT 1
+
+namespace Rosegarden
+{
+
+MappedEvent::MappedEvent(InstrumentId id,
+ const Event &e,
+ const RealTime &eventTime,
+ const RealTime &duration):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(MidiNote),
+ m_data1(0),
+ m_data2(0),
+ m_eventTime(eventTime),
+ m_duration(duration),
+ m_audioStartMarker(0, 0),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId( -1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0)
+
+{
+ try {
+
+ // For each event type, we set the properties in a particular
+ // order: first the type, then whichever of data1 and data2 fits
+ // less well with its default value. This way if one throws an
+ // exception for no data, we still have a good event with the
+ // defaults set.
+
+ if (e.isa(Note::EventType)) {
+ m_type = MidiNoteOneShot;
+ long v = MidiMaxValue;
+ e.get<Int>(BaseProperties::VELOCITY, v);
+ m_data2 = v;
+ m_data1 = e.get<Int>(BaseProperties::PITCH);
+ } else if (e.isa(PitchBend::EventType)) {
+ m_type = MidiPitchBend;
+ PitchBend pb(e);
+ m_data1 = pb.getMSB();
+ m_data2 = pb.getLSB();
+ } else if (e.isa(Controller::EventType)) {
+ m_type = MidiController;
+ Controller c(e);
+ m_data1 = c.getNumber();
+ m_data2 = c.getValue();
+ } else if (e.isa(ProgramChange::EventType)) {
+ m_type = MidiProgramChange;
+ ProgramChange pc(e);
+ m_data1 = pc.getProgram();
+ } else if (e.isa(KeyPressure::EventType)) {
+ m_type = MidiKeyPressure;
+ KeyPressure kp(e);
+ m_data1 = kp.getPitch();
+ m_data2 = kp.getPressure();
+ } else if (e.isa(ChannelPressure::EventType)) {
+ m_type = MidiChannelPressure;
+ ChannelPressure cp(e);
+ m_data1 = cp.getPressure();
+ } else if (e.isa(SystemExclusive::EventType)) {
+ m_type = MidiSystemMessage;
+ m_data1 = MIDI_SYSTEM_EXCLUSIVE;
+ SystemExclusive s(e);
+ std::string dataBlock = s.getRawData();
+ DataBlockRepository::getInstance()->registerDataBlockForEvent(dataBlock, this);
+ } else {
+ m_type = InvalidMappedEvent;
+ }
+ } catch (MIDIValueOutOfRange r) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "MIDI value out of range in MappedEvent ctor"
+ << std::endl;
+#else
+
+ ;
+#endif
+
+ } catch (Event::NoData d) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "Caught Event::NoData in MappedEvent ctor, message is:"
+ << std::endl << d.getMessage() << std::endl;
+#else
+
+ ;
+#endif
+
+ } catch (Event::BadType b) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "Caught Event::BadType in MappedEvent ctor, message is:"
+ << std::endl << b.getMessage() << std::endl;
+#else
+
+ ;
+#endif
+
+ } catch (SystemExclusive::BadEncoding e) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "Caught bad SysEx encoding in MappedEvent ctor"
+ << std::endl;
+#else
+
+ ;
+#endif
+
+ }
+}
+
+bool
+operator<(const MappedEvent &a, const MappedEvent &b)
+{
+ return a.getEventTime() < b.getEventTime();
+}
+
+MappedEvent&
+MappedEvent::operator=(const MappedEvent &mE)
+{
+ if (&mE == this)
+ return * this;
+
+ m_trackId = mE.getTrackId();
+ m_instrument = mE.getInstrument();
+ m_type = mE.getType();
+ m_data1 = mE.getData1();
+ m_data2 = mE.getData2();
+ m_eventTime = mE.getEventTime();
+ m_duration = mE.getDuration();
+ m_audioStartMarker = mE.getAudioStartMarker();
+ m_dataBlockId = mE.getDataBlockId();
+ m_runtimeSegmentId = mE.getRuntimeSegmentId();
+ m_autoFade = mE.isAutoFading();
+ m_fadeInTime = mE.getFadeInTime();
+ m_fadeOutTime = mE.getFadeOutTime();
+ m_recordedChannel = mE.getRecordedChannel();
+ m_recordedDevice = mE.getRecordedDevice();
+
+ return *this;
+}
+
+// Do we use this? It looks dangerous so just commenting it out - rwb
+//
+//const size_t MappedEvent::streamedSize = 12 * sizeof(unsigned int);
+
+QDataStream&
+operator<<(QDataStream &dS, MappedEvent *mE)
+{
+ dS << (unsigned int)mE->getTrackId();
+ dS << (unsigned int)mE->getInstrument();
+ dS << (unsigned int)mE->getType();
+ dS << (unsigned int)mE->getData1();
+ dS << (unsigned int)mE->getData2();
+ dS << (unsigned int)mE->getEventTime().sec;
+ dS << (unsigned int)mE->getEventTime().nsec;
+ dS << (unsigned int)mE->getDuration().sec;
+ dS << (unsigned int)mE->getDuration().nsec;
+ dS << (unsigned int)mE->getAudioStartMarker().sec;
+ dS << (unsigned int)mE->getAudioStartMarker().nsec;
+ dS << (unsigned long)mE->getDataBlockId();
+ dS << mE->getRuntimeSegmentId();
+ dS << (unsigned int)mE->isAutoFading();
+ dS << (unsigned int)mE->getFadeInTime().sec;
+ dS << (unsigned int)mE->getFadeInTime().nsec;
+ dS << (unsigned int)mE->getFadeOutTime().sec;
+ dS << (unsigned int)mE->getFadeOutTime().nsec;
+ dS << (unsigned int)mE->getRecordedChannel();
+ dS << (unsigned int)mE->getRecordedDevice();
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedEvent &mE)
+{
+ dS << (unsigned int)mE.getTrackId();
+ dS << (unsigned int)mE.getInstrument();
+ dS << (unsigned int)mE.getType();
+ dS << (unsigned int)mE.getData1();
+ dS << (unsigned int)mE.getData2();
+ dS << (unsigned int)mE.getEventTime().sec;
+ dS << (unsigned int)mE.getEventTime().nsec;
+ dS << (unsigned int)mE.getDuration().sec;
+ dS << (unsigned int)mE.getDuration().nsec;
+ dS << (unsigned int)mE.getAudioStartMarker().sec;
+ dS << (unsigned int)mE.getAudioStartMarker().nsec;
+ dS << (unsigned long)mE.getDataBlockId();
+ dS << mE.getRuntimeSegmentId();
+ dS << (unsigned int)mE.isAutoFading();
+ dS << (unsigned int)mE.getFadeInTime().sec;
+ dS << (unsigned int)mE.getFadeInTime().nsec;
+ dS << (unsigned int)mE.getFadeOutTime().sec;
+ dS << (unsigned int)mE.getFadeOutTime().nsec;
+ dS << (unsigned int)mE.getRecordedChannel();
+ dS << (unsigned int)mE.getRecordedDevice();
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedEvent *mE)
+{
+ unsigned int trackId = 0, instrument = 0, type = 0, data1 = 0, data2 = 0;
+ long eventTimeSec = 0, eventTimeNsec = 0, durationSec = 0, durationNsec = 0,
+ audioSec = 0, audioNsec = 0;
+ std::string dataBlock;
+ unsigned long dataBlockId = 0;
+ int runtimeSegmentId = -1;
+ unsigned int autoFade = 0,
+ fadeInSec = 0, fadeInNsec = 0, fadeOutSec = 0, fadeOutNsec = 0,
+ recordedChannel = 0, recordedDevice = 0;
+
+ dS >> trackId;
+ dS >> instrument;
+ dS >> type;
+ dS >> data1;
+ dS >> data2;
+ dS >> eventTimeSec;
+ dS >> eventTimeNsec;
+ dS >> durationSec;
+ dS >> durationNsec;
+ dS >> audioSec;
+ dS >> audioNsec;
+ dS >> dataBlockId;
+ dS >> runtimeSegmentId;
+ dS >> autoFade;
+ dS >> fadeInSec;
+ dS >> fadeInNsec;
+ dS >> fadeOutSec;
+ dS >> fadeOutNsec;
+ dS >> recordedChannel;
+ dS >> recordedDevice;
+
+ mE->setTrackId((TrackId)trackId);
+ mE->setInstrument((InstrumentId)instrument);
+ mE->setType((MappedEvent::MappedEventType)type);
+ mE->setData1((MidiByte)data1);
+ mE->setData2((MidiByte)data2);
+ mE->setEventTime(RealTime(eventTimeSec, eventTimeNsec));
+ mE->setDuration(RealTime(durationSec, durationNsec));
+ mE->setAudioStartMarker(RealTime(audioSec, audioNsec));
+ mE->setDataBlockId(dataBlockId);
+ mE->setRuntimeSegmentId(runtimeSegmentId);
+ mE->setAutoFade(autoFade);
+ mE->setFadeInTime(RealTime(fadeInSec, fadeInNsec));
+ mE->setFadeOutTime(RealTime(fadeOutSec, fadeOutNsec));
+ mE->setRecordedChannel(recordedChannel);
+ mE->setRecordedDevice(recordedDevice);
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedEvent &mE)
+{
+ unsigned int trackId = 0, instrument = 0, type = 0, data1 = 0, data2 = 0;
+ long eventTimeSec = 0, eventTimeNsec = 0, durationSec = 0, durationNsec = 0,
+ audioSec = 0, audioNsec = 0;
+ std::string dataBlock;
+ unsigned long dataBlockId = 0;
+ int runtimeSegmentId = -1;
+ unsigned int autoFade = 0,
+ fadeInSec = 0, fadeInNsec = 0, fadeOutSec = 0, fadeOutNsec = 0,
+ recordedChannel = 0, recordedDevice = 0;
+
+ dS >> trackId;
+ dS >> instrument;
+ dS >> type;
+ dS >> data1;
+ dS >> data2;
+ dS >> eventTimeSec;
+ dS >> eventTimeNsec;
+ dS >> durationSec;
+ dS >> durationNsec;
+ dS >> audioSec;
+ dS >> audioNsec;
+ dS >> dataBlockId;
+ dS >> runtimeSegmentId;
+ dS >> autoFade;
+ dS >> fadeInSec;
+ dS >> fadeInNsec;
+ dS >> fadeOutSec;
+ dS >> fadeOutNsec;
+ dS >> recordedChannel;
+ dS >> recordedDevice;
+
+ mE.setTrackId((TrackId)trackId);
+ mE.setInstrument((InstrumentId)instrument);
+ mE.setType((MappedEvent::MappedEventType)type);
+ mE.setData1((MidiByte)data1);
+ mE.setData2((MidiByte)data2);
+ mE.setEventTime(RealTime(eventTimeSec, eventTimeNsec));
+ mE.setDuration(RealTime(durationSec, durationNsec));
+ mE.setAudioStartMarker(RealTime(audioSec, audioNsec));
+ mE.setDataBlockId(dataBlockId);
+ mE.setRuntimeSegmentId(runtimeSegmentId);
+ mE.setAutoFade(autoFade);
+ mE.setFadeInTime(RealTime(fadeInSec, fadeInNsec));
+ mE.setFadeOutTime(RealTime(fadeOutSec, fadeOutNsec));
+ mE.setRecordedChannel(recordedChannel);
+ mE.setRecordedDevice(recordedDevice);
+
+ return dS;
+}
+
+void
+MappedEvent::addDataByte(MidiByte byte)
+{
+ DataBlockRepository::getInstance()->addDataByteForEvent(byte, this);
+}
+
+void
+MappedEvent::addDataString(const std::string& data)
+{
+ DataBlockRepository::getInstance()->addDataStringForEvent(data, this);
+}
+
+
+
+//--------------------------------------------------
+
+class DataBlockFile
+{
+public:
+ DataBlockFile(DataBlockRepository::blockid id);
+ ~DataBlockFile();
+
+ QString getFileName()
+ {
+ return m_fileName;
+ }
+
+ void addDataByte(MidiByte);
+ void addDataString(const std::string&);
+
+ void clear()
+ {
+ m_cleared = true;
+ }
+ bool exists();
+ void setData(const std::string&);
+ std::string getData();
+
+protected:
+ void prepareToWrite();
+ void prepareToRead();
+
+ //--------------- Data members ---------------------------------
+ QString m_fileName;
+ QFile m_file;
+ bool m_cleared;
+};
+
+DataBlockFile::DataBlockFile(DataBlockRepository::blockid id)
+ : m_fileName(KGlobal::dirs()->resourceDirs("tmp").first() + QString("/rosegarden_datablock_%1").arg(id)),
+ m_file(m_fileName),
+ m_cleared(false)
+{
+ // std::cerr << "DataBlockFile " << m_fileName.latin1() << std::endl;
+}
+
+DataBlockFile::~DataBlockFile()
+{
+ if (m_cleared) {
+// std::cerr << "~DataBlockFile : removing " << m_fileName.latin1() << std::endl;
+ QFile::remove
+ (m_fileName);
+ }
+
+}
+
+bool DataBlockFile::exists()
+{
+ return QFile::exists(m_fileName);
+}
+
+void DataBlockFile::setData(const std::string& s)
+{
+ // std::cerr << "DataBlockFile::setData() : setting data to " << m_fileName << std::endl;
+ prepareToWrite();
+
+ QDataStream stream(&m_file);
+ stream.writeRawBytes(s.data(), s.length());
+}
+
+std::string DataBlockFile::getData()
+{
+ if (!exists())
+ return std::string();
+
+ prepareToRead();
+
+ QDataStream stream(&m_file);
+ // std::cerr << "DataBlockFile::getData() : file size = " << m_file.size() << std::endl;
+ char* tmp = new char[m_file.size()];
+ stream.readRawBytes(tmp, m_file.size());
+ std::string res(tmp, m_file.size());
+ delete[] tmp;
+
+ return res;
+}
+
+void DataBlockFile::addDataByte(MidiByte byte)
+{
+ prepareToWrite();
+ m_file.putch(byte);
+}
+
+void DataBlockFile::addDataString(const std::string& s)
+{
+ prepareToWrite();
+ QDataStream stream(&m_file);
+ stream.writeRawBytes(s.data(), s.length());
+}
+
+void DataBlockFile::prepareToWrite()
+{
+ // std::cerr << "DataBlockFile[" << m_fileName << "]: prepareToWrite" << std::endl;
+ if (!m_file.isWritable()) {
+ m_file.close();
+ m_file.open(IO_WriteOnly | IO_Append);
+ assert(m_file.isWritable());
+ }
+}
+
+void DataBlockFile::prepareToRead()
+{
+// std::cerr << "DataBlockFile[" << m_fileName << "]: prepareToRead" << std::endl;
+ if (!m_file.isReadable()) {
+ m_file.close();
+ m_file.open(IO_ReadOnly);
+ assert(m_file.isReadable());
+ }
+}
+
+
+
+//--------------------------------------------------
+
+DataBlockRepository* DataBlockRepository::getInstance()
+{
+ if (!m_instance)
+ m_instance = new DataBlockRepository;
+ return m_instance;
+}
+
+std::string DataBlockRepository::getDataBlock(DataBlockRepository::blockid id)
+{
+ DataBlockFile dataBlockFile(id);
+
+ if (dataBlockFile.exists())
+ return dataBlockFile.getData();
+
+ return std::string();
+}
+
+
+std::string DataBlockRepository::getDataBlockForEvent(MappedEvent* e)
+{
+ blockid id = e->getDataBlockId();
+ if (id == 0) {
+ // std::cerr << "WARNING: DataBlockRepository::getDataBlockForEvent called on event with data block id 0" << std::endl;
+ return "";
+ }
+ return getInstance()->getDataBlock(id);
+}
+
+void DataBlockRepository::setDataBlockForEvent(MappedEvent* e, const std::string& s)
+{
+ blockid id = e->getDataBlockId();
+ if (id == 0) {
+ // std::cerr << "Creating new datablock for event" << std::endl;
+ getInstance()->registerDataBlockForEvent(s, e);
+ } else {
+ // std::cerr << "Writing " << s.length() << " chars to file for datablock " << id << std::endl;
+ DataBlockFile dataBlockFile(id);
+ dataBlockFile.setData(s);
+ }
+}
+
+bool DataBlockRepository::hasDataBlock(DataBlockRepository::blockid id)
+{
+ return DataBlockFile(id).exists();
+}
+
+DataBlockRepository::blockid DataBlockRepository::registerDataBlock(const std::string& s)
+{
+ blockid id = 0;
+ while (id == 0 || DataBlockFile(id).exists())
+ id = (blockid)random();
+
+ // std::cerr << "DataBlockRepository::registerDataBlock: " << s.length() << " chars, id is " << id << std::endl;
+
+ DataBlockFile dataBlockFile(id);
+ dataBlockFile.setData(s);
+
+ return id;
+}
+
+void DataBlockRepository::unregisterDataBlock(DataBlockRepository::blockid id)
+{
+ DataBlockFile dataBlockFile(id);
+
+ dataBlockFile.clear();
+}
+
+void DataBlockRepository::registerDataBlockForEvent(const std::string& s, MappedEvent* e)
+{
+ e->setDataBlockId(registerDataBlock(s));
+}
+
+void DataBlockRepository::unregisterDataBlockForEvent(MappedEvent* e)
+{
+ unregisterDataBlock(e->getDataBlockId());
+}
+
+
+DataBlockRepository::DataBlockRepository()
+{}
+
+void DataBlockRepository::clear()
+{
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "DataBlockRepository::clear()\n";
+#endif
+
+ // Erase all 'datablock_*' files
+ //
+ QString tmpPath = KGlobal::dirs()->resourceDirs("tmp").first();
+
+ QDir segmentsDir(tmpPath, "rosegarden_datablock_*");
+ for (unsigned int i = 0; i < segmentsDir.count(); ++i) {
+ QString segmentName = tmpPath + '/' + segmentsDir[i];
+ QFile::remove
+ (segmentName);
+ }
+}
+
+
+void DataBlockRepository::addDataByteForEvent(MidiByte byte, MappedEvent* e)
+{
+ DataBlockFile dataBlockFile(e->getDataBlockId());
+ dataBlockFile.addDataByte(byte);
+
+}
+
+void DataBlockRepository::addDataStringForEvent(const std::string& s, MappedEvent* e)
+{
+ DataBlockFile dataBlockFile(e->getDataBlockId());
+ dataBlockFile.addDataString(s);
+}
+
+DataBlockRepository* DataBlockRepository::m_instance = 0;
+
+}
diff --git a/src/sound/MappedEvent.h b/src/sound/MappedEvent.h
new file mode 100644
index 0000000..cc4e3f3
--- /dev/null
+++ b/src/sound/MappedEvent.h
@@ -0,0 +1,546 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <qdatastream.h>
+
+#include "Composition.h" // for RealTime
+#include "Event.h"
+
+
+#ifndef _MAPPEDEVENT_H_
+#define _MAPPEDEVENT_H_
+
+// Used as a transformation stage between Composition, Events and output
+// at the Sequencer this class and MidiComposition eliminate the notion
+// of the Segment and Track for ease of Event access. The MappedEvents
+// are ready for playing or routing through an Instrument or Effects
+// boxes.
+//
+// MappedEvents can also represent instructions for playback of audio
+// samples - if the m_type is Audio then the sequencer will attempt to
+// map the Pitch (m_data1) to the audio id. Note that this limits us
+// to 256 audio files in the Composition unless we use a different
+// parameter for storing these IDs.
+//
+// The MappedEvent/Instrument relationship is interesting - we don't
+// want to duplicate the entire Instrument at the Sequencer level as
+// it'd be messy and unnecessary. Instead we use a MappedInstrument
+// which is just a very cut down Sequencer-side version of an Instrument.
+//
+// Some of these Events are unidirectional, some are bidirectional -
+// that is they only have a meaning in one direction (they are still
+// legal at either end). They are broadcast in both directions using
+// the "getSequencerSlice" and "processAsync/Recorded" interfaces on
+// which the control messages can piggyback and eventually stripped out.
+//
+
+namespace Rosegarden
+{
+class MappedEvent;
+
+class DataBlockRepository
+{
+public:
+ friend class MappedEvent;
+ typedef unsigned long blockid;
+
+ static DataBlockRepository* getInstance();
+ static std::string getDataBlockForEvent(MappedEvent*);
+ static void setDataBlockForEvent(MappedEvent*, const std::string&);
+ /**
+ * Clear all block files
+ */
+ static void clear();
+ bool hasDataBlock(blockid);
+
+protected:
+ DataBlockRepository();
+
+ std::string getDataBlock(blockid);
+
+ void addDataByteForEvent(MidiByte byte, MappedEvent*);
+ void addDataStringForEvent(const std::string&, MappedEvent*);
+
+
+ blockid registerDataBlock(const std::string&);
+ void unregisterDataBlock(blockid);
+
+ void registerDataBlockForEvent(const std::string&, MappedEvent*);
+ void unregisterDataBlockForEvent(MappedEvent*);
+
+
+ //--------------- Data members ---------------------------------
+
+ static DataBlockRepository* m_instance;
+};
+
+
+class MappedEvent
+{
+public:
+ typedef enum
+ {
+ // INVALID
+ //
+ InvalidMappedEvent = 0,
+
+ // Keep the MidiNotes bit flaggable so that filtering works
+ //
+ MidiNote = 1 << 0,
+ MidiNoteOneShot = 1 << 1, // doesn't need NOTE OFFs
+ MidiProgramChange = 1 << 2,
+ MidiKeyPressure = 1 << 3,
+ MidiChannelPressure = 1 << 4,
+ MidiPitchBend = 1 << 5,
+ MidiController = 1 << 6,
+ MidiSystemMessage = 1 << 7,
+
+ // Sent from the gui to play an audio file
+ Audio = 1 << 8,
+ // Sent from gui to cancel playing an audio file
+ AudioCancel = 1 << 9,
+ // Sent to the gui with audio level on Instrument
+ AudioLevel = 1 << 10,
+ // Sent to the gui to inform an audio file stopped
+ AudioStopped = 1 << 11,
+ // The gui is clear to generate a preview for a new audio file
+ AudioGeneratePreview = 1 << 12,
+
+ // Update Instruments - new ALSA client detected
+ SystemUpdateInstruments = 1 << 13,
+ // Set RG as JACK master/slave
+ SystemJackTransport = 1 << 14,
+ // Set RG as MMC master/slave
+ SystemMMCTransport = 1 << 15,
+ // Set System Messages and MIDI Clock
+ SystemMIDIClock = 1 << 16,
+ // Set Record device
+ SystemRecordDevice = 1 << 17,
+ // Set Metronome device
+ SystemMetronomeDevice = 1 << 18,
+ // Set Audio inputs/outputs: data1 num inputs, data2 num submasters
+ SystemAudioPortCounts = 1 << 19,
+ // Set whether we create various Audio ports (data1 is an AudioOutMask)
+ SystemAudioPorts = 1 << 20,
+ // Some failure has occurred: data1 contains FailureCode
+ SystemFailure = 1 << 21,
+
+ // Time sig. event (from time sig. composition reference segment)
+ TimeSignature = 1 << 22,
+ // Tempo event (from tempo composition reference segment)
+ Tempo = 1 << 23,
+
+ // Panic function
+ Panic = 1 << 24,
+
+ // Set RG as MTC master/slave
+ SystemMTCTransport = 1 << 25,
+ // Auto-connect sync outputs
+ SystemMIDISyncAuto = 1 << 26,
+ // File format used for audio recording (data1 is 0=PCM,1=float)
+ SystemAudioFileFormat = 1 << 27
+
+ } MappedEventType;
+
+ typedef enum
+ {
+ // These values are OR'd to produce the data2 field in a
+ // SystemAudioPorts event.
+ FaderOuts = 1 << 0,
+ SubmasterOuts = 1 << 1
+
+ } MappedEventAudioOutMask;
+
+ typedef enum
+ {
+ // JACK is having some xruns - warn the user maybe
+ FailureXRuns = 0,
+ // JACK has died or kicked us out
+ FailureJackDied = 1,
+ // Audio subsystem failed to read from disc fast enough
+ FailureDiscUnderrun = 2,
+ // Audio subsystem failed to write to disc fast enough
+ FailureDiscOverrun = 3,
+ // Audio subsystem failed to mix busses fast enough
+ FailureBussMixUnderrun = 4,
+ // Audio subsystem failed to mix instruments fast enough
+ FailureMixUnderrun = 5,
+ // Using a timer that has too low a resolution (e.g. 100Hz system timer)
+ WarningImpreciseTimer = 6,
+ // Too much CPU time spent in audio processing -- risk of xruns and lockup
+ FailureCPUOverload = 7,
+ // JACK kicked us out, but we've reconnected
+ FailureJackRestart = 8,
+ // JACK kicked us out, and now the reconnection has failed
+ FailureJackRestartFailed = 9,
+ // A necessary ALSA call has returned an error code
+ FailureALSACallFailed = 10,
+ // Using a timer that has too low a resolution, but RTC might work
+ WarningImpreciseTimerTryRTC = 11,
+ } FailureCode;
+
+ MappedEvent(): m_trackId(0),
+ m_instrument(0),
+ m_type(MidiNote),
+ m_data1(0),
+ m_data2(0),
+ m_eventTime(0, 0),
+ m_duration(0, 0),
+ m_audioStartMarker(0, 0),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // Construct from Events to Internal (MIDI) type MappedEvent
+ //
+ MappedEvent(const Event &e);
+
+ // Another Internal constructor from Events
+ MappedEvent(InstrumentId id,
+ const Event &e,
+ const RealTime &eventTime,
+ const RealTime &duration);
+
+ // A general MappedEvent constructor for any MappedEvent type
+ //
+ MappedEvent(InstrumentId id,
+ MappedEventType type,
+ MidiByte pitch,
+ MidiByte velocity,
+ const RealTime &absTime,
+ const RealTime &duration,
+ const RealTime &audioStartMarker):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(pitch),
+ m_data2(velocity),
+ m_eventTime(absTime),
+ m_duration(duration),
+ m_audioStartMarker(audioStartMarker),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // Audio MappedEvent shortcut constructor
+ //
+ MappedEvent(InstrumentId id,
+ unsigned short audioID,
+ const RealTime &eventTime,
+ const RealTime &duration,
+ const RealTime &audioStartMarker):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(Audio),
+ m_data1(audioID % 256),
+ m_data2(audioID / 256),
+ m_eventTime(eventTime),
+ m_duration(duration),
+ m_audioStartMarker(audioStartMarker),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // More generalised MIDI event containers for
+ // large and small events (one param, two param)
+ //
+ MappedEvent(InstrumentId id,
+ MappedEventType type,
+ MidiByte data1,
+ MidiByte data2):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(data1),
+ m_data2(data2),
+ m_eventTime(RealTime(0, 0)),
+ m_duration(RealTime(0, 0)),
+ m_audioStartMarker(RealTime(0, 0)),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ MappedEvent(InstrumentId id,
+ MappedEventType type,
+ MidiByte data1):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(data1),
+ m_data2(0),
+ m_eventTime(RealTime(0, 0)),
+ m_duration(RealTime(0, 0)),
+ m_audioStartMarker(RealTime(0, 0)),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+
+ // Construct SysExs say
+ //
+ MappedEvent(InstrumentId id,
+ MappedEventType type):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(0),
+ m_data2(0),
+ m_eventTime(RealTime(0, 0)),
+ m_duration(RealTime(0, 0)),
+ m_audioStartMarker(RealTime(0, 0)),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // Copy constructor
+ //
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030531)
+ MappedEvent(const MappedEvent &mE):
+ m_trackId(mE.getTrackId()),
+ m_instrument(mE.getInstrument()),
+ m_type(mE.getType()),
+ m_data1(mE.getData1()),
+ m_data2(mE.getData2()),
+ m_eventTime(mE.getEventTime()),
+ m_duration(mE.getDuration()),
+ m_audioStartMarker(mE.getAudioStartMarker()),
+ m_dataBlockId(mE.getDataBlockId()),
+ m_isPersistent(false),
+ m_runtimeSegmentId(mE.getRuntimeSegmentId()),
+ m_autoFade(mE.isAutoFading()),
+ m_fadeInTime(mE.getFadeInTime()),
+ m_fadeOutTime(mE.getFadeOutTime()),
+ m_recordedChannel(mE.getRecordedChannel()),
+ m_recordedDevice(mE.getRecordedDevice()) {}
+
+ // Copy from pointer
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030531)
+ MappedEvent(MappedEvent *mE):
+ m_trackId(mE->getTrackId()),
+ m_instrument(mE->getInstrument()),
+ m_type(mE->getType()),
+ m_data1(mE->getData1()),
+ m_data2(mE->getData2()),
+ m_eventTime(mE->getEventTime()),
+ m_duration(mE->getDuration()),
+ m_audioStartMarker(mE->getAudioStartMarker()),
+ m_dataBlockId(mE->getDataBlockId()),
+ m_isPersistent(false),
+ m_runtimeSegmentId(mE->getRuntimeSegmentId()),
+ m_autoFade(mE->isAutoFading()),
+ m_fadeInTime(mE->getFadeInTime()),
+ m_fadeOutTime(mE->getFadeOutTime()),
+ m_recordedChannel(mE->getRecordedChannel()),
+ m_recordedDevice(mE->getRecordedDevice()) {}
+
+ // Construct perhaps without initialising, for placement new or equivalent
+ MappedEvent(bool initialise) {
+ if (initialise) *this = MappedEvent();
+ }
+
+ // Event time
+ //
+ void setEventTime(const RealTime &a) { m_eventTime = a; }
+ RealTime getEventTime() const { return m_eventTime; }
+
+ // Duration
+ //
+ void setDuration(const RealTime &d) { m_duration = d; }
+ RealTime getDuration() const { return m_duration; }
+
+ // Instrument
+ void setInstrument(InstrumentId id) { m_instrument = id; }
+ InstrumentId getInstrument() const { return m_instrument; }
+
+ // Track
+ void setTrackId(TrackId id) { m_trackId = id; }
+ TrackId getTrackId() const { return m_trackId; }
+
+ MidiByte getPitch() const { return m_data1; }
+
+ // Keep pitch within MIDI limits
+ //
+ void setPitch(MidiByte p)
+ {
+ m_data1 = p;
+ if (m_data1 > MidiMaxValue) m_data1 = MidiMaxValue;
+ }
+
+ void setVelocity(MidiByte v) { m_data2 = v; }
+ MidiByte getVelocity() const { return m_data2; }
+
+ // And the trendy names for them
+ //
+ MidiByte getData1() const { return m_data1; }
+ MidiByte getData2() const { return m_data2; }
+ void setData1(MidiByte d1) { m_data1 = d1; }
+ void setData2(MidiByte d2) { m_data2 = d2; }
+
+ void setAudioID(unsigned short id) { m_data1 = id % 256; m_data2 = id / 256; }
+ int getAudioID() const { return m_data1 + 256 * m_data2; }
+
+ // A sample doesn't have to be played from the beginning. When
+ // passing an Audio event this value may be set to indicate from
+ // where in the sample it should be played. Duration is measured
+ // against total sounding length (not absolute position).
+ //
+ void setAudioStartMarker(const RealTime &aS)
+ { m_audioStartMarker = aS; }
+ RealTime getAudioStartMarker() const
+ { return m_audioStartMarker; }
+
+ MappedEventType getType() const { return m_type; }
+ void setType(const MappedEventType &value) { m_type = value; }
+
+ // Data block id
+ //
+ DataBlockRepository::blockid getDataBlockId() const { return m_dataBlockId; }
+ void setDataBlockId(DataBlockRepository::blockid dataBlockId) { m_dataBlockId = dataBlockId; }
+
+ // How MappedEvents are ordered in the MappedComposition
+ //
+ struct MappedEventCmp
+ {
+ bool operator()(const MappedEvent *mE1, const MappedEvent *mE2) const
+ {
+ return *mE1 < *mE2;
+ }
+ };
+
+ friend bool operator<(const MappedEvent &a, const MappedEvent &b);
+
+ MappedEvent& operator=(const MappedEvent &mE);
+
+ friend QDataStream& operator>>(QDataStream &dS, MappedEvent *mE);
+ friend QDataStream& operator<<(QDataStream &dS, MappedEvent *mE);
+ friend QDataStream& operator>>(QDataStream &dS, MappedEvent &mE);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedEvent &mE);
+
+ /// Add a single byte to the event's datablock (for SysExs)
+ void addDataByte(MidiByte byte);
+ /// Add several bytes to the event's datablock
+ void addDataString(const std::string& data);
+
+ void setPersistent(bool value) { m_isPersistent = value; }
+ bool isPersistent() const { return m_isPersistent; }
+
+ /// Size of a MappedEvent in a stream
+ static const size_t streamedSize;
+
+ // The runtime segment id of an audio file
+ //
+ int getRuntimeSegmentId() const { return m_runtimeSegmentId; }
+ void setRuntimeSegmentId(int id) { m_runtimeSegmentId = id; }
+
+ bool isAutoFading() const { return m_autoFade; }
+ void setAutoFade(bool value) { m_autoFade = value; }
+
+ RealTime getFadeInTime() const { return m_fadeInTime; }
+ void setFadeInTime(const RealTime &time)
+ { m_fadeInTime = time; }
+
+ RealTime getFadeOutTime() const { return m_fadeOutTime; }
+ void setFadeOutTime(const RealTime &time)
+ { m_fadeOutTime = time; }
+
+ // Original event input channel as it was recorded
+ //
+ unsigned int getRecordedChannel() const { return m_recordedChannel; }
+ void setRecordedChannel(const unsigned int channel)
+ { m_recordedChannel = channel; }
+
+ // Original event record device as it was recorded
+ //
+ unsigned int getRecordedDevice() const { return m_recordedDevice; }
+ void setRecordedDevice(const unsigned int device) { m_recordedDevice = device; }
+
+private:
+ TrackId m_trackId;
+ InstrumentId m_instrument;
+ MappedEventType m_type;
+ MidiByte m_data1;
+ MidiByte m_data2;
+ RealTime m_eventTime;
+ RealTime m_duration;
+ RealTime m_audioStartMarker;
+
+ // Use this when we want to store something in addition to the
+ // other bytes in this type, e.g. System Exclusive.
+ //
+ DataBlockRepository::blockid m_dataBlockId;
+
+ // Should a MappedComposition try and delete this MappedEvent or
+ // if it persistent?
+ //
+ bool m_isPersistent;
+
+
+ // Id of the segment that this (audio) event is derived from
+ //
+ int m_runtimeSegmentId;
+
+ // Audio autofading
+ //
+ bool m_autoFade;
+ RealTime m_fadeInTime;
+ RealTime m_fadeOutTime;
+
+ // input event original data,
+ // stored as it was recorded
+ //
+ unsigned int m_recordedChannel;
+ unsigned int m_recordedDevice;
+};
+
+
+}
+
+#endif
diff --git a/src/sound/MappedInstrument.cpp b/src/sound/MappedInstrument.cpp
new file mode 100644
index 0000000..b353f78
--- /dev/null
+++ b/src/sound/MappedInstrument.cpp
@@ -0,0 +1,153 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "MappedInstrument.h"
+
+namespace Rosegarden
+{
+
+
+MappedInstrument::MappedInstrument():
+ m_type(Instrument::Midi),
+ m_channel(0),
+ m_id(0),
+ m_name(std::string("")),
+ m_audioChannels(0)
+{}
+
+MappedInstrument::MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id):
+ m_type(type),
+ m_channel(channel),
+ m_id(id),
+ m_name(std::string("")),
+ m_audioChannels(0)
+{}
+
+MappedInstrument::MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id,
+ const std::string &name,
+ DeviceId device):
+ m_type(type),
+ m_channel(channel),
+ m_id(id),
+ m_name(name),
+ m_device(device),
+ m_audioChannels(0)
+{}
+
+MappedInstrument::MappedInstrument(const Instrument &instr):
+ m_type(instr.getType()),
+ m_channel(instr.getMidiChannel()),
+ m_id(instr.getId()),
+ m_name(instr.getName()),
+ m_device((instr.getDevice())->getId()),
+ m_audioChannels(instr.getAudioChannels())
+{}
+
+MappedInstrument::MappedInstrument(Instrument *instr):
+ m_type(instr->getType()),
+ m_channel(instr->getMidiChannel()),
+ m_id(instr->getId()),
+ m_name(instr->getName()),
+ m_device(instr->getDevice()->getId()),
+ m_audioChannels(instr->getAudioChannels())
+{}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedInstrument *mI)
+{
+ unsigned int type, channel, id, device, audioChannels;
+ QString name;
+
+ dS >> type;
+ dS >> channel;
+ dS >> id;
+ dS >> name;
+ dS >> device;
+ dS >> audioChannels;
+
+ mI->setType(Instrument::InstrumentType(type));
+ mI->setChannel(MidiByte(channel));
+ mI->setId(InstrumentId(id));
+ mI->setName(std::string(name.data()));
+ mI->setDevice(DeviceId(device));
+ mI->setAudioChannels(audioChannels);
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedInstrument &mI)
+{
+ unsigned int type, channel, id, device, audioChannels;
+ QString name;
+
+ dS >> type;
+ dS >> channel;
+ dS >> id;
+ dS >> name;
+ dS >> device;
+ dS >> audioChannels;
+
+ mI.setType(Instrument::InstrumentType(type));
+ mI.setChannel(MidiByte(channel));
+ mI.setId(InstrumentId(id));
+ mI.setName(std::string(name.data()));
+ mI.setDevice(DeviceId(device));
+ mI.setAudioChannels(audioChannels);
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, MappedInstrument *mI)
+{
+ dS << (unsigned int)mI->getType();
+ dS << (unsigned int)mI->getChannel();
+ dS << (unsigned int)mI->getId();
+ ;
+ dS << QString(mI->getName().c_str());
+ dS << (unsigned int)mI->getDevice();
+ dS << (unsigned int)mI->getAudioChannels();
+
+ return dS;
+}
+
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedInstrument &mI)
+{
+ dS << (unsigned int)mI.getType();
+ dS << (unsigned int)mI.getChannel();
+ dS << (unsigned int)mI.getId();
+ ;
+ dS << QString(mI.getName().c_str());
+ dS << (unsigned int)mI.getDevice();
+ dS << (unsigned int)mI.getAudioChannels();
+
+ return dS;
+}
+
+}
+
diff --git a/src/sound/MappedInstrument.h b/src/sound/MappedInstrument.h
new file mode 100644
index 0000000..49f0167
--- /dev/null
+++ b/src/sound/MappedInstrument.h
@@ -0,0 +1,106 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "Instrument.h"
+#include "MappedDevice.h"
+#include "MappedCommon.h"
+
+#ifndef _MAPPEDINSTRUMENT_H_
+#define _MAPPEDINSTRUMENT_H_
+
+// A scaled-down version of an Instrument that we keep Sequencer
+// side. IDs match with those on the GUI.
+//
+//
+
+namespace Rosegarden
+{
+
+class MappedInstrument
+{
+public:
+
+ MappedInstrument();
+
+ // GUI uses this constructor because it already knows
+ // the name of the Instrument
+ //
+ MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id);
+
+ // Driver uses this constructor (because the gui will want
+ // to know the name)
+ //
+ MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id,
+ const std::string &name,
+ DeviceId device);
+
+ // from instrument
+ MappedInstrument(const Instrument &instrument);
+ MappedInstrument(Instrument *instrument);
+
+ ~MappedInstrument() { ;}
+
+ void setId(InstrumentId id) { m_id = id; }
+ InstrumentId getId() const { return m_id; }
+
+ void setChannel(MidiByte channel) { m_channel = channel; }
+ MidiByte getChannel() const { return m_channel; }
+
+ void setType(Instrument::InstrumentType type) { m_type = type; }
+ Instrument::InstrumentType getType() const { return m_type; }
+
+ void setName(const std::string &name) { m_name = name; }
+ const std::string& getName() const { return m_name; }
+
+ void setDevice(DeviceId device) { m_device = device; }
+ DeviceId getDevice() const { return m_device; }
+
+ // How many audio channels we've got on this audio MappedInstrument
+ //
+ unsigned int getAudioChannels() const { return m_audioChannels; }
+ void setAudioChannels(unsigned int channels) { m_audioChannels = channels; }
+
+ friend QDataStream& operator>>(QDataStream &dS, MappedInstrument *mI);
+ friend QDataStream& operator<<(QDataStream &dS, MappedInstrument *mI);
+ friend QDataStream& operator>>(QDataStream &dS, MappedInstrument &mI);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedInstrument &mI);
+
+private:
+
+ Instrument::InstrumentType m_type;
+ MidiByte m_channel;
+ InstrumentId m_id;
+ std::string m_name;
+ DeviceId m_device;
+
+ // If this is an audio MappedInstrument then how many channels
+ // are associated with it?
+ //
+ unsigned int m_audioChannels;
+};
+
+}
+
+#endif // _MAPPEDINSTRUMENT_H_
diff --git a/src/sound/MappedRealTime.cpp b/src/sound/MappedRealTime.cpp
new file mode 100644
index 0000000..ba596fd
--- /dev/null
+++ b/src/sound/MappedRealTime.cpp
@@ -0,0 +1,62 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "MappedRealTime.h"
+
+namespace Rosegarden
+{
+
+QDataStream&
+operator>>(QDataStream &dS, MappedRealTime *mRT)
+{
+ dS >> mRT->sec;
+ dS >> mRT->nsec;
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, MappedRealTime *mRT)
+{
+ dS << mRT->sec;
+ dS << mRT->nsec;
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedRealTime &mRT)
+{
+ dS >> mRT.sec;
+ dS >> mRT.nsec;
+ return dS;
+}
+
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedRealTime &mRT)
+{
+ dS << mRT.sec;
+ dS << mRT.nsec;
+ return dS;
+}
+
+
+}
+
diff --git a/src/sound/MappedRealTime.h b/src/sound/MappedRealTime.h
new file mode 100644
index 0000000..ca2afd0
--- /dev/null
+++ b/src/sound/MappedRealTime.h
@@ -0,0 +1,56 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <qdatastream.h>
+#include "RealTime.h"
+
+// Just a DCOP wrapper to RealTime
+//
+
+#ifndef _MAPPEDREALTIME_H_
+#define _MAPPEDREALTIME_H_
+
+namespace Rosegarden
+{
+
+class MappedRealTime : public RealTime
+{
+public:
+ MappedRealTime() : RealTime(0, 0) {;}
+ MappedRealTime(const RealTime &t) : RealTime(t.sec, t.nsec) {;}
+
+ // Return as RealTime
+ RealTime getRealTime() { return RealTime(sec, nsec); }
+
+ // DCOP datastream
+ //
+ friend QDataStream& operator>>(QDataStream &dS, MappedRealTime *mRT);
+ friend QDataStream& operator<<(QDataStream &dS, MappedRealTime *mRT);
+ friend QDataStream& operator>>(QDataStream &dS, MappedRealTime &mRT);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedRealTime &mRT);
+
+
+};
+
+}
+
+#endif // _MAPPEDREALTIME_H_
+
diff --git a/src/sound/MappedStudio.cpp b/src/sound/MappedStudio.cpp
new file mode 100644
index 0000000..4b35122
--- /dev/null
+++ b/src/sound/MappedStudio.cpp
@@ -0,0 +1,1719 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <iostream>
+
+#include "MappedStudio.h"
+#include "SoundDriver.h"
+#include "PluginFactory.h"
+
+#include <pthread.h> // for mutex
+
+//#define DEBUG_MAPPEDSTUDIO 1
+
+namespace Rosegarden
+{
+
+static pthread_mutex_t _mappedObjectContainerLock;
+
+#ifdef DEBUG_MAPPEDSTUDIO
+static int _approxLockCount = 0;
+#endif
+
+static inline void getLock(const char *file, int line)
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "Acquiring MappedStudio container lock at " << file << ":" << line << ": count " << _approxLockCount++ << std::endl;
+#endif
+
+ pthread_mutex_lock(&_mappedObjectContainerLock);
+}
+
+static inline void releaseLock(const char *file, int line)
+{
+ pthread_mutex_unlock(&_mappedObjectContainerLock);
+#ifdef DEBUG_MAPPEDSTUDIO
+
+ std::cerr << "Released container lock at " << file << ":" << line << ": count " << --_approxLockCount << std::endl;
+#endif
+}
+
+#define GET_LOCK getLock(__FILE__,__LINE__)
+#define RELEASE_LOCK releaseLock(__FILE__,__LINE__)
+
+// These stream functions are stolen and adapted from Qt3 qvaluevector.h
+//
+// ** Copyright (C) 1992-2000 Trolltech AS. All rights reserved.
+//
+QDataStream& operator>>(QDataStream& s, MappedObjectIdList& v)
+{
+ v.clear();
+ Q_UINT32 c;
+ s >> c;
+ v.resize(c);
+ for (Q_UINT32 i = 0; i < c; ++i) {
+ MappedObjectId t;
+ s >> t;
+ v[i] = t;
+ }
+ return s;
+}
+
+QDataStream& operator<<(QDataStream& s, const MappedObjectIdList& v)
+{
+ s << (Q_UINT32)v.size();
+ MappedObjectIdList::const_iterator it = v.begin();
+ for ( ; it != v.end(); ++it )
+ s << *it;
+ return s;
+}
+
+QDataStream& operator>>(QDataStream& s, MappedObjectPropertyList& v)
+{
+ v.clear();
+ Q_UINT32 c;
+ s >> c;
+ v.resize(c);
+ for (Q_UINT32 i = 0; i < c; ++i) {
+ MappedObjectProperty t;
+ s >> t;
+ v[i] = t;
+ }
+ return s;
+}
+
+QDataStream& operator<<(QDataStream& s, const MappedObjectPropertyList& v)
+{
+ s << (Q_UINT32)v.size();
+ MappedObjectPropertyList::const_iterator it = v.begin();
+ for ( ; it != v.end(); ++it )
+ s << *it;
+ return s;
+}
+
+QDataStream& operator>>(QDataStream& s, MappedObjectValueList& v)
+{
+ v.clear();
+ Q_UINT32 c;
+ s >> c;
+ v.resize(c);
+ for (Q_UINT32 i = 0; i < c; ++i) {
+ MappedObjectValue t;
+ s >> t;
+ v[i] = t;
+ }
+ return s;
+}
+
+QDataStream& operator<<(QDataStream& s, const MappedObjectValueList& v)
+{
+ s << (Q_UINT32)v.size();
+ MappedObjectValueList::const_iterator it = v.begin();
+ for ( ; it != v.end(); ++it )
+ s << *it;
+ return s;
+}
+
+// Define our object properties - these can be queried and set.
+//
+
+// General things
+//
+const MappedObjectProperty MappedObject::Name = "name";
+const MappedObjectProperty MappedObject::Instrument = "instrument";
+const MappedObjectProperty MappedObject::Position = "position";
+
+const MappedObjectProperty MappedConnectableObject::ConnectionsIn = "connectionsIn";
+const MappedObjectProperty MappedConnectableObject::ConnectionsOut = "connectionsOut";
+
+const MappedObjectProperty MappedAudioFader::Channels = "channels";
+const MappedObjectProperty MappedAudioFader::FaderLevel = "faderLevel";
+const MappedObjectProperty MappedAudioFader::FaderRecordLevel = "faderRecordLevel";
+const MappedObjectProperty MappedAudioFader::Pan = "pan";
+const MappedObjectProperty MappedAudioFader::InputChannel = "inputChannel";
+
+const MappedObjectProperty MappedAudioBuss::BussId = "bussId";
+const MappedObjectProperty MappedAudioBuss::Level = "level";
+const MappedObjectProperty MappedAudioBuss::Pan = "pan";
+
+const MappedObjectProperty MappedAudioInput::InputNumber = "inputNumber";
+
+const MappedObjectProperty MappedPluginSlot::Identifier = "identifier";
+const MappedObjectProperty MappedPluginSlot::PluginName = "pluginname";
+const MappedObjectProperty MappedPluginSlot::Label = "label";
+const MappedObjectProperty MappedPluginSlot::Author = "author";
+const MappedObjectProperty MappedPluginSlot::Copyright = "copyright";
+const MappedObjectProperty MappedPluginSlot::Category = "category";
+const MappedObjectProperty MappedPluginSlot::PortCount = "portcount";
+const MappedObjectProperty MappedPluginSlot::Ports = "ports";
+const MappedObjectProperty MappedPluginSlot::Instrument = "instrument";
+const MappedObjectProperty MappedPluginSlot::Position = "position";
+const MappedObjectProperty MappedPluginSlot::Bypassed = "bypassed";
+const MappedObjectProperty MappedPluginSlot::Programs = "programs";
+const MappedObjectProperty MappedPluginSlot::Program = "program";
+const MappedObjectProperty MappedPluginSlot::Configuration = "configuration";
+
+const MappedObjectProperty MappedPluginPort::PortNumber = "portnumber";
+const MappedObjectProperty MappedPluginPort::Name = "name";
+const MappedObjectProperty MappedPluginPort::Minimum = "minimum";
+const MappedObjectProperty MappedPluginPort::Maximum = "maximum";
+const MappedObjectProperty MappedPluginPort::Default = "default";
+const MappedObjectProperty MappedPluginPort::DisplayHint = "displayhint";
+const MappedObjectProperty MappedPluginPort::Value = "value";
+
+// --------- MappedObject ---------
+//
+
+void
+MappedObject::addChild(MappedObject *object)
+{
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++)
+ if ((*it) == object)
+ return ;
+
+ m_children.push_back(object);
+}
+
+void
+MappedObject::removeChild(MappedObject *object)
+{
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++) {
+ if ((*it) == object) {
+ m_children.erase(it);
+ return ;
+ }
+ }
+}
+
+// Return all child ids
+//
+MappedObjectPropertyList
+MappedObject::getChildren()
+{
+ MappedObjectPropertyList list;
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++)
+ list.push_back(QString("%1").arg((*it)->getId()));
+
+ return list;
+}
+
+
+// Return all child ids of a certain type
+//
+MappedObjectPropertyList
+MappedObject::getChildren(MappedObjectType type)
+{
+ MappedObjectPropertyList list;
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++) {
+ if ((*it)->getType() == type)
+ list.push_back(QString("%1").arg((*it)->getId()));
+ }
+
+ return list;
+}
+
+void
+MappedObject::destroyChildren()
+{
+ // remove references from the studio as well as from the object
+ MappedObject *studioObject = getParent();
+ while (!dynamic_cast<MappedStudio*>(studioObject))
+ studioObject = studioObject->getParent();
+
+ // see note in destroy() below
+
+ std::vector<MappedObject *> children = m_children;
+ m_children.clear();
+
+ std::vector<MappedObject *>::iterator it = children.begin();
+ for (; it != children.end(); it++)
+ (*it)->destroy(); // remove from studio and destroy
+}
+
+// Destroy this object and remove it from the studio and
+// do the same for all its children.
+//
+void
+MappedObject::destroy()
+{
+ MappedObject *studioObject = getParent();
+ while (!dynamic_cast<MappedStudio*>(studioObject))
+ studioObject = studioObject->getParent();
+
+ MappedStudio *studio = dynamic_cast<MappedStudio*>(studioObject);
+
+ // The destroy method on each child calls studio->clearObject,
+ // which calls back on the parent (in this case us) to remove the
+ // child. (That's necessary for the case of destroying a plugin,
+ // where we need to remove it from its plugin manager -- etc.) So
+ // we don't want to be iterating over m_children here, as it will
+ // change from under us.
+
+ std::vector<MappedObject *> children = m_children;
+ m_children.clear();
+
+ std::vector<MappedObject *>::iterator it = children.begin();
+ for (; it != children.end(); it++) {
+ (*it)->destroy();
+ }
+
+ (void)studio->clearObject(m_id);
+ delete this;
+}
+
+
+// ------- MappedStudio -------
+//
+
+MappedStudio::MappedStudio() :
+ MappedObject(0,
+ "MappedStudio",
+ Studio,
+ 0),
+ m_runningObjectId(1)
+{
+ 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(&_mappedObjectContainerLock, &attr);
+}
+
+MappedStudio::~MappedStudio()
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cout << "MappedStudio::~MappedStudio" << std::endl;
+#endif
+
+ clear();
+}
+
+
+// Object factory
+//
+MappedObject*
+MappedStudio::createObject(MappedObjectType type)
+{
+ GET_LOCK;
+
+ MappedObject *mO = 0;
+
+ // Ensure we've got an empty slot
+ //
+ while (getObjectById(m_runningObjectId))
+ m_runningObjectId++;
+
+ mO = createObject(type, m_runningObjectId);
+
+ // If we've got a new object increase the running id
+ //
+ if (mO)
+ m_runningObjectId++;
+
+ RELEASE_LOCK;
+ return mO;
+}
+
+MappedObject*
+MappedStudio::createObject(MappedObjectType type,
+ MappedObjectId id)
+{
+ GET_LOCK;
+
+ // fail if the object already exists and it's not zero
+ if (id != 0 && getObjectById(id)) {
+ RELEASE_LOCK;
+ return 0;
+ }
+
+ MappedObject *mO = 0;
+
+ if (type == MappedObject::AudioFader) {
+ mO = new MappedAudioFader(this,
+ id,
+ 2); // channels
+
+ // push to the studio's child stack
+ addChild(mO);
+ } else if (type == MappedObject::AudioBuss) {
+ mO = new MappedAudioBuss(this,
+ id);
+
+ // push to the studio's child stack
+ addChild(mO);
+ } else if (type == MappedObject::AudioInput) {
+ mO = new MappedAudioInput(this,
+ id);
+
+ // push to the studio's child stack
+ addChild(mO);
+ } else if (type == MappedObject::PluginSlot) {
+ mO = new MappedPluginSlot(this,
+ id);
+ addChild(mO);
+ } else if (type == MappedObject::PluginPort) {
+ mO = new MappedPluginPort(this,
+ id);
+ // reset the port's parent after creation outside this method
+ }
+
+ // Insert
+ if (mO) {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "Adding object " << id << " to category " << type << std::endl;
+#endif
+
+ m_objects[type][id] = mO;
+ }
+
+ RELEASE_LOCK;
+
+ return mO;
+}
+
+MappedObject*
+MappedStudio::getObjectOfType(MappedObjectType type)
+{
+ MappedObject *rv = 0;
+
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[type];
+ if (!category.empty())
+ rv = category.begin()->second;
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+std::vector<MappedObject *>
+MappedStudio::getObjectsOfType(MappedObjectType type)
+{
+ std::vector<MappedObject *> rv;
+
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[type];
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ rv.push_back(i->second);
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+unsigned int
+MappedStudio::getObjectCount(MappedObjectType type)
+{
+ unsigned int count = 0;
+
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[type];
+ count = category.size();
+
+ RELEASE_LOCK;
+
+ return count;
+}
+
+
+bool
+MappedStudio::destroyObject(MappedObjectId id)
+{
+ GET_LOCK;
+
+ MappedObject *obj = getObjectById(id);
+
+ bool rv = false;
+
+ if (obj) {
+ obj->destroy();
+ rv = true;
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+bool
+MappedStudio::connectObjects(MappedObjectId mId1, MappedObjectId mId2)
+{
+ GET_LOCK;
+
+ bool rv = false;
+
+ // objects must exist and be of connectable types
+ MappedConnectableObject *obj1 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId1));
+ MappedConnectableObject *obj2 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId2));
+
+ if (obj1 && obj2) {
+ obj1->addConnection(MappedConnectableObject::Out, mId2);
+ obj2->addConnection(MappedConnectableObject::In, mId1);
+ rv = true;
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+bool
+MappedStudio::disconnectObjects(MappedObjectId mId1, MappedObjectId mId2)
+{
+ GET_LOCK;
+
+ bool rv = false;
+
+ // objects must exist and be of connectable types
+ MappedConnectableObject *obj1 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId1));
+ MappedConnectableObject *obj2 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId2));
+
+ if (obj1 && obj2) {
+ obj1->removeConnection(MappedConnectableObject::Out, mId2);
+ obj2->removeConnection(MappedConnectableObject::In, mId1);
+ rv = true;
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+bool
+MappedStudio::disconnectObject(MappedObjectId mId)
+{
+ GET_LOCK;
+
+ bool rv = false;
+
+ MappedConnectableObject *obj =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId));
+
+ if (obj) {
+ while (1) {
+ MappedObjectValueList list =
+ obj->getConnections(MappedConnectableObject::In);
+ if (list.empty())
+ break;
+ MappedObjectId otherId = MappedObjectId(*list.begin());
+ disconnectObjects(otherId, mId);
+ }
+ while (1) {
+ MappedObjectValueList list =
+ obj->getConnections(MappedConnectableObject::Out);
+ if (list.empty())
+ break;
+ MappedObjectId otherId = MappedObjectId(*list.begin());
+ disconnectObjects(mId, otherId);
+ }
+ }
+
+ rv = true;
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+
+
+// Clear down the whole studio
+//
+void
+MappedStudio::clear()
+{
+ GET_LOCK;
+
+ for (MappedObjectMap::iterator i = m_objects.begin();
+ i != m_objects.end(); ++i) {
+
+ for (MappedObjectCategory::iterator j = i->second.begin();
+ j != i->second.end(); ++j) {
+
+ delete j->second;
+ }
+ }
+
+ m_objects.clear();
+
+ // reset running object id
+ m_runningObjectId = 1;
+
+ RELEASE_LOCK;
+}
+
+bool
+MappedStudio::clearObject(MappedObjectId id)
+{
+ bool rv = false;
+
+ GET_LOCK;
+
+ for (MappedObjectMap::iterator i = m_objects.begin();
+ i != m_objects.end(); ++i) {
+
+ MappedObjectCategory::iterator j = i->second.find(id);
+ if (j != i->second.end()) {
+ // if the object has a parent other than the studio,
+ // persuade that parent to abandon it
+ MappedObject *parent = j->second->getParent();
+ if (parent && !dynamic_cast<MappedStudio *>(parent)) {
+ parent->removeChild(j->second);
+ }
+
+ i->second.erase(j);
+ rv = true;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+
+MappedObjectPropertyList
+MappedStudio::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ // something
+ }
+
+ return list;
+}
+
+bool
+MappedStudio::getProperty(const MappedObjectProperty &,
+ MappedObjectValue &)
+{
+ return false;
+}
+
+MappedObject*
+MappedStudio::getObjectById(MappedObjectId id)
+{
+ GET_LOCK;
+ MappedObject *rv = 0;
+
+ for (MappedObjectMap::iterator i = m_objects.begin();
+ i != m_objects.end(); ++i) {
+
+ MappedObjectCategory::iterator j = i->second.find(id);
+ if (j != i->second.end()) {
+ rv = j->second;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedObject*
+MappedStudio::getObjectByIdAndType(MappedObjectId id, MappedObjectType type)
+{
+ GET_LOCK;
+ MappedObject *rv = 0;
+
+ MappedObjectCategory &category = m_objects[type];
+ MappedObjectCategory::iterator i = category.find(id);
+ if (i != category.end()) {
+ rv = i->second;
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedObject*
+MappedStudio::getFirst(MappedObjectType type)
+{
+ return getObjectOfType(type);
+}
+
+MappedObject*
+MappedStudio::getNext(MappedObject *object)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[object->getType()];
+
+ bool next = false;
+ MappedObject *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ if (i->second->getId() == object->getId())
+ next = true;
+ else if (next) {
+ rv = i->second;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+void
+MappedStudio::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue /*value*/)
+{
+ if (property == "") {}
+
+}
+
+MappedAudioFader *
+MappedStudio::getAudioFader(InstrumentId id)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[AudioFader];
+ MappedAudioFader *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ MappedAudioFader *fader = dynamic_cast<MappedAudioFader *>(i->second);
+ if (fader && (fader->getInstrument() == id)) {
+ rv = fader;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedAudioBuss *
+MappedStudio::getAudioBuss(int bussNumber)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[AudioBuss];
+ MappedAudioBuss *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ MappedAudioBuss *buss = dynamic_cast<MappedAudioBuss *>(i->second);
+ if (buss && (buss->getBussId() == bussNumber)) {
+ rv = buss;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedAudioInput *
+MappedStudio::getAudioInput(int inputNumber)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[AudioInput];
+ MappedAudioInput *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ MappedAudioInput *input = dynamic_cast<MappedAudioInput *>(i->second);
+ if (input && (input->getInputNumber() == inputNumber)) {
+ rv = input;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+
+// -------------- MappedConnectableObject -----------------
+//
+//
+MappedConnectableObject::MappedConnectableObject(MappedObject *parent,
+ const std::string &name,
+ MappedObjectType type,
+ MappedObjectId id):
+ MappedObject(parent,
+ name,
+ type,
+ id)
+{}
+
+MappedConnectableObject::~MappedConnectableObject()
+{}
+
+void
+MappedConnectableObject::setConnections(ConnectionDirection dir,
+ MappedObjectValueList conns)
+{
+ if (dir == In)
+ m_connectionsIn = conns;
+ else
+ m_connectionsOut = conns;
+}
+
+void
+MappedConnectableObject::addConnection(ConnectionDirection dir,
+ MappedObjectId id)
+{
+ MappedObjectValueList &list =
+ (dir == In ? m_connectionsIn : m_connectionsOut);
+
+ for (MappedObjectValueList::iterator i = list.begin(); i != list.end(); ++i) {
+ if (*i == id) {
+ return ;
+ }
+ }
+
+ list.push_back(MappedObjectValue(id));
+}
+
+void
+MappedConnectableObject::removeConnection(ConnectionDirection dir,
+ MappedObjectId id)
+{
+ MappedObjectValueList &list =
+ (dir == In ? m_connectionsIn : m_connectionsOut);
+
+ for (MappedObjectValueList::iterator i = list.begin(); i != list.end(); ++i) {
+ if (*i == id) {
+ list.erase(i);
+ return ;
+ }
+ }
+}
+
+MappedObjectValueList
+MappedConnectableObject::getConnections(ConnectionDirection dir)
+{
+ if (dir == In)
+ return m_connectionsIn;
+ else
+ return m_connectionsOut;
+}
+
+
+// ------------ MappedAudioFader ----------------
+//
+MappedAudioFader::MappedAudioFader(MappedObject *parent,
+ MappedObjectId id,
+ MappedObjectValue channels):
+ MappedConnectableObject(parent,
+ "MappedAudioFader",
+ AudioFader,
+ id),
+ m_level(0.0), // dB
+ m_recordLevel(0.0),
+ m_instrumentId(0),
+ m_pan(0),
+ m_channels(channels),
+ m_inputChannel(0)
+{}
+
+MappedAudioFader::~MappedAudioFader()
+{}
+
+
+MappedObjectPropertyList
+MappedAudioFader::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(MappedAudioFader::FaderLevel);
+ list.push_back(MappedAudioFader::FaderRecordLevel);
+ list.push_back(MappedObject::Instrument);
+ list.push_back(MappedAudioFader::Pan);
+ list.push_back(MappedAudioFader::Channels);
+ list.push_back(MappedConnectableObject::ConnectionsIn);
+ list.push_back(MappedConnectableObject::ConnectionsOut);
+ } else if (property == MappedObject::Instrument) {
+ list.push_back(MappedObjectProperty("%1").arg(m_instrumentId));
+ } else if (property == MappedAudioFader::FaderLevel) {
+ list.push_back(MappedObjectProperty("%1").arg(m_level));
+ } else if (property == MappedAudioFader::FaderRecordLevel) {
+ list.push_back(MappedObjectProperty("%1").arg(m_recordLevel));
+ } else if (property == MappedAudioFader::Channels) {
+ list.push_back(MappedObjectProperty("%1").arg(m_channels));
+ } else if (property == MappedAudioFader::InputChannel) {
+ list.push_back(MappedObjectProperty("%1").arg(m_inputChannel));
+ } else if (property == MappedAudioFader::Pan) {
+ list.push_back(MappedObjectProperty("%1").arg(m_pan));
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsIn.begin();
+
+ for ( ; it != m_connectionsIn.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsOut.begin();
+
+ for ( ; it != m_connectionsOut.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ }
+
+ return list;
+}
+
+bool
+MappedAudioFader::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == FaderLevel) {
+ value = m_level;
+ } else if (property == Instrument) {
+ value = m_instrumentId;
+ } else if (property == FaderRecordLevel) {
+ value = m_recordLevel;
+ } else if (property == Channels) {
+ value = m_channels;
+ } else if (property == InputChannel) {
+ value = m_inputChannel;
+ } else if (property == Pan) {
+ value = m_pan;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioFader::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+void
+MappedAudioFader::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ bool updateLevels = false;
+
+ if (property == MappedAudioFader::FaderLevel) {
+ m_level = value;
+ updateLevels = true;
+ } else if (property == MappedObject::Instrument) {
+ m_instrumentId = InstrumentId(value);
+ updateLevels = true;
+ } else if (property == MappedAudioFader::FaderRecordLevel) {
+ m_recordLevel = value;
+ } else if (property == MappedAudioFader::Channels) {
+ m_channels = value;
+ } else if (property == MappedAudioFader::InputChannel) {
+ m_inputChannel = value;
+ } else if (property == MappedAudioFader::Pan) {
+ m_pan = value;
+ updateLevels = true;
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ m_connectionsIn.clear();
+ m_connectionsIn.push_back(value);
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ m_connectionsOut.clear();
+ m_connectionsOut.push_back(value);
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioFader::setProperty - "
+ << "unsupported property" << std::endl;
+#endif
+
+ return ;
+ }
+
+ /*
+ std::cout << "MappedAudioFader::setProperty - "
+ << property << " = " << value << std::endl;
+ */
+
+ if (updateLevels) {
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setAudioInstrumentLevels
+ (m_instrumentId, m_level, m_pan);
+ }
+ }
+}
+
+// ---------------- MappedAudioBuss -------------------
+//
+//
+MappedAudioBuss::MappedAudioBuss(MappedObject *parent,
+ MappedObjectId id) :
+ MappedConnectableObject(parent,
+ "MappedAudioBuss",
+ AudioBuss,
+ id),
+ m_bussId(0),
+ m_level(0),
+ m_pan(0)
+{}
+
+MappedAudioBuss::~MappedAudioBuss()
+{}
+
+MappedObjectPropertyList
+MappedAudioBuss::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(MappedAudioBuss::BussId);
+ list.push_back(MappedAudioBuss::Level);
+ list.push_back(MappedAudioBuss::Pan);
+ list.push_back(MappedConnectableObject::ConnectionsIn);
+ list.push_back(MappedConnectableObject::ConnectionsOut);
+ } else if (property == BussId) {
+ list.push_back(MappedObjectProperty("%1").arg(m_bussId));
+ } else if (property == Level) {
+ list.push_back(MappedObjectProperty("%1").arg(m_level));
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsIn.begin();
+
+ for ( ; it != m_connectionsIn.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsOut.begin();
+
+ for ( ; it != m_connectionsOut.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ }
+
+ return list;
+}
+
+bool
+MappedAudioBuss::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == BussId) {
+ value = m_bussId;
+ } else if (property == Level) {
+ value = m_level;
+ } else if (property == Pan) {
+ value = m_pan;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioBuss::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+void
+MappedAudioBuss::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ bool updateLevels = false;
+
+ if (property == MappedAudioBuss::BussId) {
+ m_bussId = (int)value;
+ updateLevels = true;
+ } else if (property == MappedAudioBuss::Level) {
+ m_level = value;
+ updateLevels = true;
+ } else if (property == MappedAudioBuss::Pan) {
+ m_pan = value;
+ updateLevels = true;
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ m_connectionsIn.clear();
+ m_connectionsIn.push_back(value);
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ m_connectionsOut.clear();
+ m_connectionsOut.push_back(value);
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioBuss::setProperty - "
+ << "unsupported property" << std::endl;
+#endif
+
+ return ;
+ }
+
+ if (updateLevels) {
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setAudioBussLevels
+ (m_bussId, m_level, m_pan);
+ }
+ }
+}
+
+std::vector<InstrumentId>
+MappedAudioBuss::getInstruments()
+{
+ std::vector<InstrumentId> rv;
+
+ GET_LOCK;
+
+ MappedObject *studioObject = getParent();
+ while (!dynamic_cast<MappedStudio *>(studioObject))
+ studioObject = studioObject->getParent();
+
+ std::vector<MappedObject *> objects =
+ static_cast<MappedStudio *>(studioObject)->
+ getObjectsOfType(MappedObject::AudioFader);
+
+ for (std::vector<MappedObject *>::iterator i = objects.begin();
+ i != objects.end(); ++i) {
+ MappedAudioFader *fader = dynamic_cast<MappedAudioFader *>(*i);
+ if (fader) {
+ MappedObjectValueList connections = fader->getConnections
+ (MappedConnectableObject::Out);
+ if (!connections.empty() && (*connections.begin() == getId())) {
+ rv.push_back(fader->getInstrument());
+ }
+ }
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+
+// ---------------- MappedAudioInput -------------------
+//
+//
+MappedAudioInput::MappedAudioInput(MappedObject *parent,
+ MappedObjectId id) :
+ MappedConnectableObject(parent,
+ "MappedAudioInput",
+ AudioInput,
+ id)
+{}
+
+MappedAudioInput::~MappedAudioInput()
+{}
+
+MappedObjectPropertyList
+MappedAudioInput::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(MappedAudioInput::InputNumber);
+ } else if (property == InputNumber) {
+ list.push_back(MappedObjectProperty("%1").arg(m_inputNumber));
+ }
+
+ return list;
+}
+
+bool
+MappedAudioInput::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == InputNumber) {
+ value = m_inputNumber;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioInput::getProperty - "
+ << "no properties available" << std::endl;
+#endif
+
+ }
+ return false;
+}
+
+void
+MappedAudioInput::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ if (property == InputNumber) {
+ m_inputNumber = value;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioInput::setProperty - "
+ << "no properties available" << std::endl;
+#endif
+
+ }
+ return ;
+}
+
+
+MappedPluginSlot::MappedPluginSlot(MappedObject *parent, MappedObjectId id) :
+ MappedObject(parent, "MappedPluginSlot", PluginSlot, id)
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::MappedPluginSlot: id = " << id << std::endl;
+#endif
+}
+
+MappedPluginSlot::~MappedPluginSlot()
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::~MappedPluginSlot: id = " << getId() << ", identifier = " << m_identifier << std::endl;
+#endif
+
+ if (m_identifier != "") {
+
+ // shut down and remove the plugin instance we have running
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ SoundDriver *drv = studio->getSoundDriver();
+
+ if (drv) {
+ drv->removePluginInstance(m_instrument, m_position);
+ }
+ }
+ }
+}
+
+MappedObjectPropertyList
+MappedPluginSlot::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(PortCount);
+ list.push_back(Instrument);
+ list.push_back(Bypassed);
+ list.push_back(PluginName);
+ list.push_back(Label);
+ list.push_back(Author);
+ list.push_back(Copyright);
+ list.push_back(Category);
+ } else if (property == Programs) {
+
+ // The set of available programs is dynamic -- it can change
+ // while a plugin is instantiated. So we query it on demand
+ // each time.
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ QStringList programs =
+ studio->getSoundDriver()->getPluginInstancePrograms(m_instrument,
+ m_position);
+
+ for (int i = 0; i < int(programs.count()); ++i) {
+ list.push_back(programs[i]);
+ }
+ }
+
+ } else {
+ std::cerr << "MappedPluginSlot::getPropertyList: not a list property"
+ << std::endl;
+ }
+
+ return list;
+}
+
+bool
+MappedPluginSlot::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == PortCount) {
+ value = m_portCount;
+ } else if (property == Instrument) {
+ value = m_instrument;
+ } else if (property == Position) {
+ value = m_position;
+ } else if (property == Bypassed) {
+ value = m_bypassed;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+bool
+MappedPluginSlot::getProperty(const MappedObjectProperty &property,
+ QString &value)
+{
+ if (property == Identifier) {
+ value = m_identifier;
+ } else if (property == PluginName) {
+ value = m_name;
+ } else if (property == Label) {
+ value = m_label;
+ } else if (property == Author) {
+ value = m_author;
+ } else if (property == Copyright) {
+ value = m_copyright;
+ } else if (property == Category) {
+ value = m_category;
+ } else if (property == Program) {
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ value = studio->getSoundDriver()->getPluginInstanceProgram(m_instrument,
+ m_position);
+ }
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+QString
+MappedPluginSlot::getProgram(int bank, int program)
+{
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ return
+ studio->getSoundDriver()->getPluginInstanceProgram(m_instrument,
+ m_position,
+ bank,
+ program);
+ }
+
+ return QString();
+}
+
+unsigned long
+MappedPluginSlot::getProgram(QString name)
+{
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ return
+ studio->getSoundDriver()->getPluginInstanceProgram(m_instrument,
+ m_position,
+ name);
+ }
+
+ return 0;
+}
+
+void
+MappedPluginSlot::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ if (property == Instrument) {
+ m_instrument = InstrumentId(value);
+ } else if (property == PortCount) {
+ m_portCount = int(value);
+ } else if (property == Position) {
+ m_position = int(value);
+ } else if (property == Bypassed) {
+ m_bypassed = bool(value);
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setPluginInstanceBypass(m_instrument,
+ m_position,
+ m_bypassed);
+ }
+ }
+}
+
+void
+MappedPluginSlot::setProperty(const MappedObjectProperty &property,
+ QString value)
+{
+ if (property == Identifier) {
+
+ if (m_identifier == value)
+ return ;
+
+ // shut down and remove the plugin instance we have running
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ SoundDriver *drv = studio->getSoundDriver();
+
+ if (drv) {
+
+ // We don't call drv->removePluginInstance at this
+ // point: the sequencer will deal with that when we
+ // call setPluginInstance below. If we removed the
+ // instance here, we might cause the library we want
+ // for the new plugin instance to be unloaded and then
+ // loaded again, which is hardly the most efficient.
+
+ m_identifier = value;
+
+ // populate myself and my ports
+ PluginFactory *factory = PluginFactory::instanceFor(m_identifier);
+ if (!factory) {
+ std::cerr << "WARNING: MappedPluginSlot::setProperty(identifier): No plugin factory for identifier " << m_identifier << "!" << std::endl;
+ m_identifier = "";
+ return ;
+ }
+
+ factory->populatePluginSlot(m_identifier, *this);
+
+ // now create the new instance
+ drv->setPluginInstance(m_instrument,
+ m_identifier,
+ m_position);
+ }
+ }
+
+ m_configuration.clear();
+
+ } else if (property == PluginName) {
+ m_name = value;
+ } else if (property == Label) {
+ m_label = value;
+ } else if (property == Author) {
+ m_author = value;
+ } else if (property == Copyright) {
+ m_copyright = value;
+ } else if (property == Category) {
+ m_category = value;
+ } else if (property == Program) {
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setPluginInstanceProgram(m_instrument,
+ m_position,
+ value);
+ }
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::setProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ }
+}
+
+void
+MappedPluginSlot::setPropertyList(const MappedObjectProperty &property,
+ const MappedObjectPropertyList &values)
+{
+ if (property == Configuration) {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::setPropertyList(configuration): configuration is:" << std::endl;
+#endif
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ for (MappedObjectPropertyList::const_iterator i = values.begin();
+ i != values.end(); ++i) {
+
+ QString key = *i;
+ QString value = *++i;
+
+#ifdef DEBUG_MAPPEDSTUDIO
+
+ std::cerr << key << " = " << value << std::endl;
+#endif
+
+ if (m_configuration.find(key) != m_configuration.end() &&
+ m_configuration[key] == value)
+ continue;
+
+ if (studio) {
+ QString rv =
+ studio->getSoundDriver()->configurePlugin(m_instrument,
+ m_position,
+ key, value);
+ if (rv && rv != "") {
+ throw(rv);
+ }
+ }
+ }
+
+ m_configuration.clear();
+
+ for (MappedObjectPropertyList::const_iterator i = values.begin();
+ i != values.end(); ++i) {
+
+ QString key = *i;
+ QString value = *++i;
+
+ m_configuration[key] = value;
+ }
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::setPropertyList - "
+ << "not a list property" << std::endl;
+#endif
+
+ }
+}
+
+void
+MappedPluginSlot::setPort(unsigned long portNumber, float value)
+{
+ std::vector<MappedObject*> ports = getChildObjects();
+ std::vector<MappedObject*>::iterator it = ports.begin();
+ MappedPluginPort *port = 0;
+
+ for (; it != ports.end(); it++) {
+ port = dynamic_cast<MappedPluginPort *>(*it);
+ if (port && (unsigned long)port->getPortNumber() == portNumber) {
+ port->setValue(value);
+ }
+ }
+}
+
+float
+MappedPluginSlot::getPort(unsigned long portNumber)
+{
+ std::vector<MappedObject*> ports = getChildObjects();
+ std::vector<MappedObject*>::iterator it = ports.begin();
+ MappedPluginPort *port = 0;
+
+ for (; it != ports.end(); it++) {
+ port = dynamic_cast<MappedPluginPort *>(*it);
+ if (port && (unsigned long)port->getPortNumber() == portNumber) {
+ return port->getValue();
+ }
+ }
+
+ return 0;
+}
+
+
+MappedPluginPort::MappedPluginPort(MappedObject *parent, MappedObjectId id) :
+ MappedObject(parent, "MappedPluginPort", PluginPort, id)
+{}
+
+MappedPluginPort::~MappedPluginPort()
+{}
+
+MappedObjectPropertyList
+MappedPluginPort::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(PortNumber);
+ list.push_back(Minimum);
+ list.push_back(Maximum);
+ list.push_back(Default);
+ list.push_back(DisplayHint);
+ list.push_back(Value);
+ list.push_back(Name);
+ } else {
+ std::cerr << "MappedPluginSlot::getPropertyList: not a list property"
+ << std::endl;
+ }
+
+ return list;
+}
+
+bool
+MappedPluginPort::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == PortNumber) {
+ value = m_portNumber;
+ } else if (property == Minimum) {
+ value = m_minimum;
+ } else if (property == Maximum) {
+ value = m_maximum;
+ } else if (property == Default) {
+ value = m_default;
+ } else if (property == DisplayHint) {
+ value = m_displayHint;
+ } else if (property == Value) {
+ return getValue();
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+bool
+MappedPluginPort::getProperty(const MappedObjectProperty &property,
+ QString &value)
+{
+ if (property == Name) {
+ value = m_name;
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+void
+MappedPluginPort::setValue(MappedObjectValue value)
+{
+ MappedPluginSlot *slot =
+ dynamic_cast<MappedPluginSlot *>(getParent());
+
+ if (slot) {
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio *>(slot->getParent());
+
+ if (studio) {
+ SoundDriver *drv = studio->getSoundDriver();
+
+ if (drv) {
+ drv->setPluginInstancePortValue(slot->getInstrument(),
+ slot->getPosition(),
+ m_portNumber, value);
+ }
+ }
+ }
+}
+
+float
+MappedPluginPort::getValue() const
+{
+ const MappedPluginSlot *slot =
+ dynamic_cast<const MappedPluginSlot *>(getParent());
+
+ if (slot) {
+
+ const MappedStudio *studio =
+ dynamic_cast<const MappedStudio *>(slot->getParent());
+
+ if (studio) {
+ SoundDriver *drv =
+ const_cast<SoundDriver *>(studio->getSoundDriver());
+
+ if (drv) {
+ return drv->getPluginInstancePortValue(slot->getInstrument(),
+ slot->getPosition(),
+ m_portNumber);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void
+MappedPluginPort::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ if (property == PortNumber) {
+ m_portNumber = int(value);
+ } else if (property == Minimum) {
+ m_minimum = value;
+ } else if (property == Maximum) {
+ m_maximum = value;
+ } else if (property == Default) {
+ m_default = value;
+ } else if (property == DisplayHint) {
+ m_displayHint = PluginPort::PortDisplayHint(value);
+ } else if (property == Value) {
+ setValue(value);
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::setProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ }
+}
+
+
+void
+MappedPluginPort::setProperty(const MappedObjectProperty &property,
+ QString value)
+{
+ if (property == Name) {
+ m_name = value;
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::setProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ }
+}
+
+
+}
+
+
+
diff --git a/src/sound/MappedStudio.h b/src/sound/MappedStudio.h
new file mode 100644
index 0000000..0896e6b
--- /dev/null
+++ b/src/sound/MappedStudio.h
@@ -0,0 +1,552 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <map>
+#include <string>
+#include <vector>
+#include <qdatastream.h>
+#include <qstring.h>
+
+#include "MappedCommon.h"
+#include "Instrument.h"
+#include "Device.h"
+
+#include "AudioPluginInstance.h" // for PluginPort::PortDisplayHint //!!!???
+
+#ifndef _MAPPEDSTUDIO_H_
+#define _MAPPEDSTUDIO_H_
+
+
+// A sequencer-side representation of certain elements in the
+// gui that enables us to control outgoing or incoming audio
+// and MIDI with run-time only persistence. Placeholders for
+// our Studio elements on the sequencer.
+
+namespace Rosegarden
+{
+
+class SoundDriver;
+
+
+// Types are in MappedCommon.h
+//
+class MappedObject
+{
+public:
+
+ // Some common properties
+ //
+ static const MappedObjectProperty Name;
+ static const MappedObjectProperty Instrument;
+ static const MappedObjectProperty Position;
+
+ // The object we can create
+ //
+ typedef enum
+ {
+ Studio,
+ AudioFader, // connectable fader - interfaces with devices
+ AudioBuss, // connectable buss - inferfaces with faders
+ AudioInput, // connectable record input
+ PluginSlot,
+ PluginPort
+
+ } MappedObjectType;
+
+ MappedObject(MappedObject *parent,
+ const std::string &name,
+ MappedObjectType type,
+ MappedObjectId id):
+ m_type(type),
+ m_id(id),
+ m_name(name),
+ m_parent(parent) {;}
+
+ virtual ~MappedObject() {;}
+
+ MappedObjectId getId() { return m_id; }
+ MappedObjectType getType() { return m_type; }
+
+ std::string getName() { return m_name; }
+ void setName(const std::string &name) { m_name= name; }
+
+ // Get and set properties
+ //
+ virtual MappedObjectPropertyList
+ getPropertyList(const MappedObjectProperty &property) = 0;
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value) = 0;
+
+ // Only relevant to objects that have string properties
+ //
+ virtual bool getProperty(const MappedObjectProperty &/* property */,
+ QString &/* value */) { return false; }
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value) = 0;
+
+ // Only relevant to objects that have string properties
+ //
+ virtual void setProperty(const MappedObjectProperty &/* property */,
+ QString /* value */) { }
+
+ // Only relevant to objects that have list properties
+ //
+ virtual void setPropertyList(const MappedObjectProperty &/* property */,
+ const MappedObjectPropertyList &/* values */) { }
+
+ // Ownership
+ //
+ MappedObject* getParent() { return m_parent; }
+ const MappedObject* getParent() const { return m_parent; }
+ void setParent(MappedObject *parent) { m_parent = parent; }
+
+ // Get a list of child ids - get a list of a certain type
+ //
+ MappedObjectPropertyList getChildren();
+ MappedObjectPropertyList getChildren(MappedObjectType type);
+
+ // Child management
+ //
+ void addChild(MappedObject *mO);
+ void removeChild(MappedObject *mO);
+
+ // Destruction
+ //
+ void destroy();
+ void destroyChildren();
+
+ std::vector<MappedObject*> getChildObjects() { return m_children; }
+
+protected:
+
+ MappedObjectType m_type;
+ MappedObjectId m_id;
+ std::string m_name;
+
+ MappedObject *m_parent;
+ std::vector<MappedObject*> m_children;
+};
+
+
+class MappedAudioFader;
+class MappedAudioBuss;
+class MappedAudioInput;
+
+// Works as a factory and virtual plug-board for all our other
+// objects whether they be MIDI or audio.
+//
+//
+//
+class MappedStudio : public MappedObject
+{
+public:
+ MappedStudio();
+ ~MappedStudio();
+
+ // Create a new slider of a certain type for a certain
+ // type of device.
+ //
+ MappedObject* createObject(MappedObjectType type);
+
+ // And create an object with a specified id
+ //
+ MappedObject* createObject(MappedObjectType type,
+ MappedObjectId id);
+
+ bool connectObjects(MappedObjectId mId1, MappedObjectId mId2);
+ bool disconnectObjects(MappedObjectId mId1, MappedObjectId mId2);
+ bool disconnectObject(MappedObjectId mId);
+
+ // Destroy a MappedObject by ID
+ //
+ bool destroyObject(MappedObjectId id);
+
+ // Get an object by ID only
+ //
+ MappedObject* getObjectById(MappedObjectId);
+
+ // Get an object by ID and type. (Returns 0 if the ID does not
+ // exist or exists but is not of the correct type.) This is
+ // faster than getObjectById if you know the type already.
+ //
+ MappedObject* getObjectByIdAndType(MappedObjectId, MappedObjectType);
+
+ // Get an arbitrary object of a given type - to see if any exist
+ //
+ MappedObject* getObjectOfType(MappedObjectType type);
+
+ // Find out how many objects there are of a certain type
+ //
+ unsigned int getObjectCount(MappedObjectType type);
+
+ // iterators
+ MappedObject* getFirst(MappedObjectType type);
+ MappedObject* getNext(MappedObject *object);
+
+ std::vector<MappedObject *> getObjectsOfType(MappedObjectType type);
+
+ // Empty the studio of everything
+ //
+ void clear();
+
+ // Clear a MappedObject reference from the Studio
+ //
+ bool clearObject(MappedObjectId id);
+
+ // Property list
+ //
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ // Get an audio fader for an InstrumentId. Convenience function.
+ //
+ MappedAudioFader *getAudioFader(InstrumentId id);
+ MappedAudioBuss *getAudioBuss(int bussNumber); // not buss no., not object id
+ MappedAudioInput *getAudioInput(int inputNumber); // likewise
+
+ // Return the object vector
+ //
+ //std::vector<MappedObject*>* getObjects() const { return &m_objects; }
+
+ // DCOP streaming
+ //
+ /* dunno if we need this
+ friend QDataStream& operator>>(QDataStream &dS, MappedStudio *mS);
+ friend QDataStream& operator<<(QDataStream &dS, MappedStudio *mS);
+ friend QDataStream& operator>>(QDataStream &dS, MappedStudio &mS);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedStudio &mS);
+ */
+
+
+ // Set the driver object so that we can do things like
+ // initialise plugins etc.
+ //
+ SoundDriver* getSoundDriver() { return m_soundDriver; }
+ const SoundDriver* getSoundDriver() const { return m_soundDriver; }
+ void setSoundDriver(SoundDriver *driver) { m_soundDriver = driver; }
+
+protected:
+
+private:
+
+ // We give everything we create a unique MappedObjectId for
+ // this session. So store the running total in here.
+ //
+ MappedObjectId m_runningObjectId;
+
+ // All of our mapped (virtual) studio resides in this container as
+ // well as having all their parent/child relationships. Because
+ // some things are just blobs with no connections we need to
+ // maintain both - don't forget about this.
+ //
+ // Note that object IDs are globally unique, not just unique within
+ // a category.
+ //
+ typedef std::map<MappedObjectId, MappedObject *> MappedObjectCategory;
+ typedef std::map<MappedObjectType, MappedObjectCategory> MappedObjectMap;
+ MappedObjectMap m_objects;
+
+ // Driver object
+ //
+ SoundDriver *m_soundDriver;
+};
+
+
+// A connectable AudioObject that provides a connection framework
+// for MappedAudioFader and MappedAudioBuss (for example). An
+// abstract base class.
+//
+// n input connections and m output connections - subclasses
+// can do the cleverness if n != m
+//
+
+class MappedConnectableObject : public MappedObject
+{
+public:
+ static const MappedObjectProperty ConnectionsIn;
+ static const MappedObjectProperty ConnectionsOut;
+
+ typedef enum
+ {
+ In,
+ Out
+ } ConnectionDirection;
+
+ MappedConnectableObject(MappedObject *parent,
+ const std::string &name,
+ MappedObjectType type,
+ MappedObjectId id);
+
+ ~MappedConnectableObject();
+
+ void setConnections(ConnectionDirection dir,
+ MappedObjectValueList conns);
+
+ void addConnection(ConnectionDirection dir, MappedObjectId id);
+ void removeConnection(ConnectionDirection dir, MappedObjectId id);
+
+ MappedObjectValueList getConnections (ConnectionDirection dir);
+
+protected:
+
+ // Which audio connections we have
+ //
+ MappedObjectValueList m_connectionsIn;
+ MappedObjectValueList m_connectionsOut;
+};
+
+// Audio fader
+//
+class MappedAudioFader : public MappedConnectableObject
+{
+public:
+ static const MappedObjectProperty Channels;
+
+ // properties
+ //
+ static const MappedObjectProperty FaderLevel;
+ static const MappedObjectProperty FaderRecordLevel;
+ static const MappedObjectProperty Pan;
+ static const MappedObjectProperty InputChannel;
+
+ MappedAudioFader(MappedObject *parent,
+ MappedObjectId id,
+ MappedObjectValue channels = 2); // stereo default
+ ~MappedAudioFader();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ InstrumentId getInstrument() const { return m_instrumentId; }
+
+protected:
+
+ MappedObjectValue m_level;
+ MappedObjectValue m_recordLevel;
+ InstrumentId m_instrumentId;
+
+ // Stereo pan (-1.0 to +1.0)
+ //
+ MappedObjectValue m_pan;
+
+ // How many channels we carry
+ //
+ MappedObjectValue m_channels;
+
+ // If we have an input, which channel we take from it (if we are
+ // a mono fader at least)
+ //
+ MappedObjectValue m_inputChannel;
+};
+
+class MappedAudioBuss : public MappedConnectableObject
+{
+public:
+ // A buss is much simpler than an instrument fader. It's always
+ // stereo, and just has a level and pan associated with it. The
+ // level may be a submaster fader level or a send mix level, it
+ // depends on what the purpose of the buss is. At the moment we
+ // just have a 1-1 relationship between busses and submasters, and
+ // no send channels.
+
+ static const MappedObjectProperty BussId;
+ static const MappedObjectProperty Pan;
+ static const MappedObjectProperty Level;
+
+ MappedAudioBuss(MappedObject *parent,
+ MappedObjectId id);
+ ~MappedAudioBuss();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ MappedObjectValue getBussId() { return m_bussId; }
+
+ // super-convenience function: retrieve the ids of the instruments
+ // connected to this buss
+ std::vector<InstrumentId> getInstruments();
+
+protected:
+ int m_bussId;
+ MappedObjectValue m_level;
+ MappedObjectValue m_pan;
+};
+
+class MappedAudioInput : public MappedConnectableObject
+{
+public:
+ // An input is simpler still -- no properties at all, apart from
+ // the input number, otherwise just the connections
+
+ static const MappedObjectProperty InputNumber;
+
+ MappedAudioInput(MappedObject *parent,
+ MappedObjectId id);
+ ~MappedAudioInput();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ MappedObjectValue getInputNumber() { return m_inputNumber; }
+
+protected:
+ MappedObjectValue m_inputNumber;
+};
+
+class MappedPluginSlot : public MappedObject
+{
+public:
+ static const MappedObjectProperty Identifier;
+ static const MappedObjectProperty PluginName;
+ static const MappedObjectProperty Label;
+ static const MappedObjectProperty Author;
+ static const MappedObjectProperty Copyright;
+ static const MappedObjectProperty Category;
+ static const MappedObjectProperty PortCount;
+ static const MappedObjectProperty Ports;
+ static const MappedObjectProperty Program;
+ static const MappedObjectProperty Programs; // list property
+ static const MappedObjectProperty Instrument;
+ static const MappedObjectProperty Position;
+ static const MappedObjectProperty Bypassed;
+ static const MappedObjectProperty Configuration; // list property
+
+ MappedPluginSlot(MappedObject *parent, MappedObjectId id);
+ ~MappedPluginSlot();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ QString &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ QString value);
+
+ virtual void setPropertyList(const MappedObjectProperty &,
+ const MappedObjectPropertyList &);
+
+ void setPort(unsigned long portNumber, float value);
+ float getPort(unsigned long portNumber);
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ int getPosition() const { return m_position; }
+
+ QString getProgram(int bank, int program);
+ unsigned long getProgram(QString name); // rv is bank << 16 + program
+
+protected:
+ QString m_identifier;
+
+ QString m_name;
+ QString m_label;
+ QString m_author;
+ QString m_copyright;
+ QString m_category;
+ unsigned long m_portCount;
+
+ InstrumentId m_instrument;
+ int m_position;
+ bool m_bypassed;
+
+ std::map<QString, QString> m_configuration;
+};
+
+class MappedPluginPort : public MappedObject
+{
+public:
+ static const MappedObjectProperty PortNumber;
+ static const MappedObjectProperty Name;
+ static const MappedObjectProperty Minimum;
+ static const MappedObjectProperty Maximum;
+ static const MappedObjectProperty Default;
+ static const MappedObjectProperty DisplayHint;
+ static const MappedObjectProperty Value;
+
+ MappedPluginPort(MappedObject *parent, MappedObjectId id);
+ ~MappedPluginPort();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ QString &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ QString value);
+
+ void setValue(MappedObjectValue value);
+ MappedObjectValue getValue() const;
+
+ int getPortNumber() const { return m_portNumber; }
+
+protected:
+ int m_portNumber;
+ QString m_name;
+ MappedObjectValue m_minimum;
+ MappedObjectValue m_maximum;
+ MappedObjectValue m_default;
+ PluginPort::PortDisplayHint m_displayHint;
+
+};
+
+
+}
+
+#endif // _MAPPEDSTUDIO_H_
diff --git a/src/sound/Midi.h b/src/sound/Midi.h
new file mode 100644
index 0000000..65bfe93
--- /dev/null
+++ b/src/sound/Midi.h
@@ -0,0 +1,184 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _ROSEGARDEN_MIDI_H_
+#define _ROSEGARDEN_MIDI_H_
+
+#include "Instrument.h" // for MidiByte
+#include <string>
+
+// Yes we use the STL here. Don't worry, it's fine.
+//
+//
+
+namespace Rosegarden
+{
+// Within the namespace we define our static MIDI messages
+// that'll help us create and understand MIDI files.
+//
+// CreateMessageByte(MSG, CHANNEL) = (MSG) | (CHANNEL)
+//
+//
+
+const std::string MIDI_FILE_HEADER = "MThd";
+const std::string MIDI_TRACK_HEADER = "MTrk";
+
+const MidiByte MIDI_STATUS_BYTE_MASK = 0x80;
+const MidiByte MIDI_MESSAGE_TYPE_MASK = 0xF0;
+const MidiByte MIDI_CHANNEL_NUM_MASK = 0x0F;
+
+// our basic MIDI messages
+//
+const MidiByte MIDI_NOTE_OFF = 0x80;
+const MidiByte MIDI_NOTE_ON = 0x90;
+const MidiByte MIDI_POLY_AFTERTOUCH = 0xA0;
+const MidiByte MIDI_CTRL_CHANGE = 0xB0;
+const MidiByte MIDI_PROG_CHANGE = 0xC0;
+const MidiByte MIDI_CHNL_AFTERTOUCH = 0xD0;
+const MidiByte MIDI_PITCH_BEND = 0xE0;
+
+// channel mode
+//
+const MidiByte MIDI_SELECT_CHNL_MODE = 0xB0;
+
+// system messages
+const MidiByte MIDI_SYSTEM_EXCLUSIVE = 0xF0;
+const MidiByte MIDI_TC_QUARTER_FRAME = 0xF1;
+const MidiByte MIDI_SONG_POSITION_PTR = 0xF2;
+const MidiByte MIDI_SONG_SELECT = 0xF3;
+const MidiByte MIDI_TUNE_REQUEST = 0xF6;
+const MidiByte MIDI_END_OF_EXCLUSIVE = 0xF7;
+
+const MidiByte MIDI_TIMING_CLOCK = 0xF8;
+const MidiByte MIDI_START = 0xFA;
+const MidiByte MIDI_CONTINUE = 0xFB;
+const MidiByte MIDI_STOP = 0xFC;
+const MidiByte MIDI_ACTIVE_SENSING = 0xFE;
+const MidiByte MIDI_SYSTEM_RESET = 0xFF;
+
+// System Exclusive Extensions
+//
+
+// Non-commercial use
+//
+const MidiByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D;
+
+// Universal non-real time use
+// Format:
+//
+// 0xF0 0x7E <device id> <sub id #1> <sub id #2> <data> 0xF7
+//
+const MidiByte MIDI_SYSEX_NON_RT = 0x7E;
+
+// RealTime e.g Midi Machine Control (MMC)
+//
+// 0xF0 0x7F <device id> <sub id #1> <sub id #2> <data> 0xF7
+//
+const MidiByte MIDI_SYSEX_RT = 0x7F;
+
+// Sub IDs for RealTime SysExs
+//
+const MidiByte MIDI_SYSEX_RT_COMMAND = 0x06;
+const MidiByte MIDI_SYSEX_RT_RESPONSE = 0x07;
+
+// MMC commands
+//
+const MidiByte MIDI_MMC_STOP = 0x01;
+const MidiByte MIDI_MMC_PLAY = 0x02;
+const MidiByte MIDI_MMC_DEFERRED_PLAY = 0x03;
+const MidiByte MIDI_MMC_FAST_FORWARD = 0x04;
+const MidiByte MIDI_MMC_REWIND = 0x05;
+const MidiByte MIDI_MMC_RECORD_STROBE = 0x06; // punch in
+const MidiByte MIDI_MMC_RECORD_EXIT = 0x07; // punch out
+const MidiByte MIDI_MMC_RECORD_PAUSE = 0x08;
+const MidiByte MIDI_MMC_PAUSE = 0x08;
+const MidiByte MIDI_MMC_EJECT = 0x0A;
+const MidiByte MIDI_MMC_LOCATE = 0x44; // jump to
+
+
+// Midi Event Code for META Event
+//
+const MidiByte MIDI_FILE_META_EVENT = 0xFF;
+
+// META Event Codes
+//
+const MidiByte MIDI_SEQUENCE_NUMBER = 0x00;
+const MidiByte MIDI_TEXT_EVENT = 0x01;
+const MidiByte MIDI_COPYRIGHT_NOTICE = 0x02;
+const MidiByte MIDI_TRACK_NAME = 0x03;
+const MidiByte MIDI_INSTRUMENT_NAME = 0x04;
+const MidiByte MIDI_LYRIC = 0x05;
+const MidiByte MIDI_TEXT_MARKER = 0x06;
+const MidiByte MIDI_CUE_POINT = 0x07;
+const MidiByte MIDI_CHANNEL_PREFIX = 0x20;
+
+// There is contention over what 0x21 really means.
+// It's either a miswritten CHANNEL PREFIX or it's
+// a non-standard PORT MAPPING used by a sequencer.
+// Either way we include it (and generally ignore it)
+// as it's a part of many MIDI files that already
+// exist.
+const MidiByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21;
+
+const MidiByte MIDI_END_OF_TRACK = 0x2F;
+const MidiByte MIDI_SET_TEMPO = 0x51;
+const MidiByte MIDI_SMPTE_OFFSET = 0x54;
+const MidiByte MIDI_TIME_SIGNATURE = 0x58;
+const MidiByte MIDI_KEY_SIGNATURE = 0x59;
+const MidiByte MIDI_SEQUENCER_SPECIFIC = 0x7F;
+
+// Some controllers
+//
+const MidiByte MIDI_CONTROLLER_BANK_MSB = 0x00;
+const MidiByte MIDI_CONTROLLER_VOLUME = 0x07;
+const MidiByte MIDI_CONTROLLER_BANK_LSB = 0x20;
+const MidiByte MIDI_CONTROLLER_MODULATION = 0x01;
+const MidiByte MIDI_CONTROLLER_PAN = 0x0A;
+const MidiByte MIDI_CONTROLLER_SUSTAIN = 0x40;
+const MidiByte MIDI_CONTROLLER_RESONANCE = 0x47;
+const MidiByte MIDI_CONTROLLER_RELEASE = 0x48;
+const MidiByte MIDI_CONTROLLER_ATTACK = 0x49;
+const MidiByte MIDI_CONTROLLER_FILTER = 0x4A;
+const MidiByte MIDI_CONTROLLER_REVERB = 0x5B;
+const MidiByte MIDI_CONTROLLER_CHORUS = 0x5D;
+
+// Registered and Non-Registered Parameter Controllers
+//
+const MidiByte MIDI_CONTROLLER_NRPN_1 = 0x62;
+const MidiByte MIDI_CONTROLLER_NRPN_2 = 0x63;
+const MidiByte MIDI_CONTROLLER_RPN_1 = 0x64;
+const MidiByte MIDI_CONTROLLER_RPN_2 = 0x65;
+
+const MidiByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78;
+const MidiByte MIDI_CONTROLLER_RESET = 0x79; // reset all controllers
+const MidiByte MIDI_CONTROLLER_LOCAL = 0x7A; // 0 = off, 127 = on
+const MidiByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
+
+
+// MIDI percussion channel
+const MidiByte MIDI_PERCUSSION_CHANNEL = 9;
+
+
+
+}
+
+
+#endif // _ROSEGARDEN_MIDI_H_
diff --git a/src/sound/MidiEvent.cpp b/src/sound/MidiEvent.cpp
new file mode 100644
index 0000000..975b7aa
--- /dev/null
+++ b/src/sound/MidiEvent.cpp
@@ -0,0 +1,289 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "Midi.h"
+#include "MidiEvent.h"
+#include <iostream>
+
+// MidiEvent is a representation of MIDI which we use
+// for the import/export of MidiFiles. It uses std::strings for
+// meta event messages which makes them nice and easy to handle.
+//
+//
+namespace Rosegarden
+{
+
+using std::string;
+using std::cout;
+using std::endl;
+
+MidiEvent::MidiEvent()
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(0),
+ m_data2(0),
+ m_metaEventCode(0),
+ m_metaMessage("")
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(data1),
+ m_data2(0),
+ m_metaEventCode(0),
+ m_metaMessage("")
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1,
+ MidiByte data2):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(data1),
+ m_data2(data2),
+ m_metaEventCode(0),
+ m_metaMessage("")
+
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte metaEventCode,
+ const string &metaMessage):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(0),
+ m_data2(0),
+ m_metaEventCode(metaEventCode),
+ m_metaMessage(metaMessage)
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ const string &sysEx):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(0),
+ m_data2(0),
+ m_metaEventCode(0),
+ m_metaMessage(sysEx)
+{}
+
+MidiEvent::~MidiEvent()
+{}
+
+// Show a representation of our MidiEvent purely for information
+// purposes (also demos how we decode them)
+//
+//
+void
+MidiEvent::print()
+{
+ timeT tempo;
+ int tonality;
+ string sharpflat;
+
+ if (m_metaEventCode) {
+ switch (m_metaEventCode) {
+ case MIDI_SEQUENCE_NUMBER:
+ cout << "MIDI SEQUENCE NUMBER" << endl;
+ break;
+
+ case MIDI_TEXT_EVENT:
+ cout << "MIDI TEXT:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_COPYRIGHT_NOTICE:
+ cout << "COPYRIGHT:\t\"" << m_metaMessage << "\"" << endl;
+
+ case MIDI_TRACK_NAME:
+ cout << "TRACK NAME:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_INSTRUMENT_NAME:
+ cout << "INSTRUMENT NAME:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_LYRIC:
+ cout << "LYRIC:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_TEXT_MARKER:
+ cout << "MARKER:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_CUE_POINT:
+ cout << "CUE POINT:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ // Sets a Channel number for a TRACK before it starts
+ case MIDI_CHANNEL_PREFIX:
+ cout << "CHANNEL PREFIX:\t"
+ << (timeT)m_metaMessage[0]
+ << endl;
+ break;
+
+ // These are actually the same case but this is not an
+ // official META event - it just crops up a lot. We
+ // assume it's a MIDI_CHANNEL_PREFIX though
+ //
+ case MIDI_CHANNEL_PREFIX_OR_PORT:
+ cout << "FIXED CHANNEL PREFIX:\t"
+ << (timeT)m_metaMessage[0] << endl;
+ break;
+
+ case MIDI_END_OF_TRACK:
+ cout << "END OF TRACK" << endl;
+ break;
+
+ case MIDI_SET_TEMPO:
+ tempo =
+ ((timeT)(((MidiByte)m_metaMessage[0]) << 16)) +
+ ((timeT)(((MidiByte)m_metaMessage[1]) << 8)) +
+ (short)(MidiByte)m_metaMessage[2];
+
+ tempo = 60000000 / tempo;
+ cout << "SET TEMPO:\t" << tempo << endl;
+ break;
+
+ case MIDI_SMPTE_OFFSET:
+ cout << "SMPTE TIME CODE:\t"
+ << (timeT)m_metaMessage[0]
+ << ":" << (timeT)m_metaMessage[1]
+ << ":" << (timeT)m_metaMessage[2]
+ << " - fps = " << (timeT)m_metaMessage[3]
+ << " - subdivsperframe = "
+ << (timeT)m_metaMessage[4]
+ << endl;
+ break;
+
+ case MIDI_TIME_SIGNATURE:
+ cout << "TIME SIGNATURE:\t"
+ << (timeT)m_metaMessage[0]
+ << "/"
+ << (1 << (timeT)m_metaMessage[1]) << endl;
+ break;
+
+ case MIDI_KEY_SIGNATURE:
+ tonality = (int)m_metaMessage[0];
+
+ if (tonality < 0) {
+ sharpflat = -tonality + " flat";
+ } else {
+ sharpflat = tonality;
+ sharpflat += " sharp";
+ }
+
+ cout << "KEY SIGNATURE:\t" << sharpflat << " "
+ << (((int)m_metaMessage[1]) == 0 ? "major" : "minor")
+ << endl;
+
+ break;
+
+ case MIDI_SEQUENCER_SPECIFIC:
+ cout << "SEQUENCER SPECIFIC:\t\"" << m_metaMessage << endl;
+ break;
+
+
+ default:
+ cout << "Undefined MIDI META event - "
+ << (timeT)m_metaEventCode << endl;
+ break;
+ }
+ } else {
+ switch (m_eventCode & MIDI_MESSAGE_TYPE_MASK) {
+ case MIDI_NOTE_ON:
+ cout << "NOTE ON:\t" << (int)m_data1 << " - "
+ << (int)m_data2 << endl;
+ break;
+
+ case MIDI_NOTE_OFF:
+ cout << "NOTE OFF:\t" << (int)m_data1 << " - "
+ << (int)m_data2 << endl;
+ break;
+
+ case MIDI_POLY_AFTERTOUCH:
+ cout << "POLY AFTERTOUCH:\t" << (int)m_data1
+ << " - " << (int)m_data2 << endl;
+ break;
+
+ case MIDI_CTRL_CHANGE:
+ cout << "CTRL CHANGE:\t" << (int)m_data1
+ << " - " << (int)m_data2 << endl;
+ break;
+
+ case MIDI_PITCH_BEND:
+ cout << "PITCH BEND:\t" << (int)m_data1
+ << " - " << (int)m_data2 << endl;
+ break;
+
+ case MIDI_PROG_CHANGE:
+ cout << "PROG CHANGE:\t" << (int)m_data1 << endl;
+ break;
+
+ case MIDI_CHNL_AFTERTOUCH:
+ cout << "CHNL AFTERTOUCH\t" << (int)m_data1 << endl;
+ break;
+
+ default:
+ cout << "Undefined MIDI event" << endl;
+ break;
+ }
+ }
+
+
+ return ;
+}
+
+// Adds the argument to _deltaTime and returns the result
+// thus aggregating the times as we go aint
+timeT
+MidiEvent::addTime(const timeT &time)
+{
+ m_deltaTime += time;
+ return m_deltaTime;
+}
+
+
+// Compare based on time
+//
+bool
+operator<(const MidiEvent &a, const MidiEvent &b)
+{
+ return a.getTime() < b.getTime();
+}
+
+
+}
+
+
diff --git a/src/sound/MidiEvent.h b/src/sound/MidiEvent.h
new file mode 100644
index 0000000..b2192b4
--- /dev/null
+++ b/src/sound/MidiEvent.h
@@ -0,0 +1,141 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _ROSEGARDEN_MIDI_EVENT_H_
+#define _ROSEGARDEN_MIDI_EVENT_H_
+
+#include "Midi.h"
+#include "Event.h"
+
+// MidiEvent holds MIDI and Event data during MIDI file I/O.
+// We don't use this class at all for playback or recording of MIDI -
+// for that look at MappedEvent and MappedComposition.
+//
+// Rosegarden doesn't have any internal concept of MIDI events, only
+// Events which are a superset of MIDI functionality.
+//
+// Check out Event in base/ for more information.
+//
+//
+//
+
+namespace Rosegarden
+{
+class MidiEvent
+{
+
+public:
+ MidiEvent();
+
+ // No data event
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode);
+
+ // single data byte case
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1);
+
+ // double data byte
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1,
+ MidiByte data2);
+
+ // Meta event
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte metaEventCode,
+ const std::string &metaMessage);
+
+ // Sysex style constructor
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ const std::string &sysEx);
+
+
+ ~MidiEvent();
+
+ // View our event as text
+ //
+ void print();
+
+
+ void setTime(const timeT &time) { m_deltaTime = time; }
+ void setDuration(const timeT& duration) {m_duration = duration;}
+ timeT addTime(const timeT &time);
+
+ MidiByte getMessageType() const
+ { return ( m_eventCode & MIDI_MESSAGE_TYPE_MASK ); }
+
+ MidiByte getChannelNumber() const
+ { return ( m_eventCode & MIDI_CHANNEL_NUM_MASK ); }
+
+ timeT getTime() const { return m_deltaTime; }
+ timeT getDuration() const { return m_duration; }
+
+ MidiByte getPitch() const { return m_data1; }
+ MidiByte getVelocity() const { return m_data2; }
+ MidiByte getData1() const { return m_data1; }
+ MidiByte getData2() const { return m_data2; }
+ MidiByte getEventCode() const { return m_eventCode; }
+
+ bool isMeta() const { return(m_eventCode == MIDI_FILE_META_EVENT); }
+
+ MidiByte getMetaEventCode() const { return m_metaEventCode; }
+ std::string getMetaMessage() const { return m_metaMessage; }
+ void setMetaMessage(const std::string &meta) { m_metaMessage = meta; }
+
+ friend bool operator<(const MidiEvent &a, const MidiEvent &b);
+
+private:
+
+ MidiEvent& operator=(const MidiEvent);
+
+ timeT m_deltaTime;
+ timeT m_duration;
+ MidiByte m_eventCode;
+ MidiByte m_data1; // or Note
+ MidiByte m_data2; // or Velocity
+
+ MidiByte m_metaEventCode;
+ std::string m_metaMessage;
+
+};
+
+// Comparator for sorting
+//
+struct MidiEventCmp
+{
+ bool operator()(const MidiEvent &mE1, const MidiEvent &mE2) const
+ { return mE1.getTime() < mE2.getTime(); }
+ bool operator()(const MidiEvent *mE1, const MidiEvent *mE2) const
+ { return mE1->getTime() < mE2->getTime(); }
+};
+
+}
+
+#endif // _ROSEGARDEN_MIDI_EVENT_H_
diff --git a/src/sound/MidiFile.cpp b/src/sound/MidiFile.cpp
new file mode 100644
index 0000000..76d5c85
--- /dev/null
+++ b/src/sound/MidiFile.cpp
@@ -0,0 +1,2261 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include <iostream>
+#include "misc/Debug.h"
+#include <kapplication.h>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <algorithm>
+
+#include "Midi.h"
+#include "MidiFile.h"
+#include "Segment.h"
+#include "NotationTypes.h"
+#include "BaseProperties.h"
+#include "SegmentNotationHelper.h"
+#include "SegmentPerformanceHelper.h"
+#include "CompositionTimeSliceAdapter.h"
+#include "AnalysisTypes.h"
+#include "Track.h"
+#include "Instrument.h"
+#include "Quantizer.h"
+#include "Studio.h"
+#include "MidiTypes.h"
+#include "Profiler.h"
+
+//#define MIDI_DEBUG 1
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include <kapp.h>
+
+namespace Rosegarden
+{
+
+using std::string;
+using std::ifstream;
+using std::stringstream;
+using std::cerr;
+using std::endl;
+using std::ends;
+using std::ios;
+
+MidiFile::MidiFile(Studio *studio):
+ SoundFile(std::string("unnamed.mid")),
+ m_timingDivision(0),
+ m_format(MIDI_FILE_NOT_LOADED),
+ m_numberOfTracks(0),
+ m_containsTimeChanges(false),
+ m_trackByteCount(0),
+ m_decrementCount(false),
+ m_studio(studio)
+{}
+
+MidiFile::MidiFile(const std::string &fn,
+ Studio *studio):
+ SoundFile(fn),
+ m_timingDivision(0),
+ m_format(MIDI_FILE_NOT_LOADED),
+ m_numberOfTracks(0),
+ m_containsTimeChanges(false),
+ m_trackByteCount(0),
+ m_decrementCount(false),
+ m_studio(studio)
+{}
+
+// Make sure we clear away the m_midiComposition
+//
+MidiFile::~MidiFile()
+{
+ clearMidiComposition();
+}
+
+
+// A couple of convenience functions. Watch the byte conversions out
+// of the STL strings.
+//
+//
+long
+MidiFile::midiBytesToLong(const string& bytes)
+{
+ if (bytes.length() != 4) {
+#ifdef MIDI_DEBUG
+ std::cerr << "WARNING: Wrong length for long data (" << bytes.length()
+ << ", should be 4)" << endl;
+#endif
+
+ throw (Exception("Wrong length for long data in MIDI stream"));
+ }
+
+ long longRet = ((long)(((MidiByte)bytes[0]) << 24)) |
+ ((long)(((MidiByte)bytes[1]) << 16)) |
+ ((long)(((MidiByte)bytes[2]) << 8)) |
+ ((long)((MidiByte)(bytes[3])));
+
+ std::cerr << "midiBytesToLong(" << int((MidiByte)bytes[0]) << "," << int((MidiByte)bytes[1]) << "," << int((MidiByte)bytes[2]) << "," << int((MidiByte)bytes[3]) << ") -> " << longRet << std::endl;
+
+ return longRet;
+}
+
+int
+MidiFile::midiBytesToInt(const string& bytes)
+{
+ if (bytes.length() != 2) {
+#ifdef MIDI_DEBUG
+ std::cerr << "WARNING: Wrong length for int data (" << bytes.length()
+ << ", should be 2)" << endl;
+#endif
+
+ throw (Exception("Wrong length for int data in MIDI stream"));
+ }
+
+ int intRet = ((int)(((MidiByte)bytes[0]) << 8)) |
+ ((int)(((MidiByte)bytes[1])));
+ return (intRet);
+}
+
+
+
+// Gets a single byte from the MIDI byte stream. For each track
+// section we can read only a specified number of bytes held in
+// m_trackByteCount.
+//
+MidiByte
+MidiFile::getMidiByte(ifstream* midiFile)
+{
+ static int bytesGot = 0; // purely for progress reporting purposes
+
+ if (midiFile->eof()) {
+ throw(Exception("End of MIDI file encountered while reading"));
+ }
+
+ if (m_decrementCount && m_trackByteCount <= 0) {
+ throw(Exception("Attempt to get more bytes than expected on Track"));
+ }
+
+ char byte;
+ if (midiFile->read(&byte, 1)) {
+
+ --m_trackByteCount;
+
+ // update a progress dialog if we have one
+ //
+ ++bytesGot;
+ if (bytesGot % 2000 == 0) {
+
+ emit setProgress((int)(double(midiFile->tellg()) /
+ double(m_fileSize) * 20.0));
+ kapp->processEvents(50);
+ }
+
+ return (MidiByte)byte;
+ }
+
+ throw(Exception("Attempt to read past MIDI file end"));
+}
+
+
+// Gets a specified number of bytes from the MIDI byte stream. For
+// each track section we can read only a specified number of bytes
+// held in m_trackByteCount.
+//
+string
+MidiFile::getMidiBytes(ifstream* midiFile, unsigned long numberOfBytes)
+{
+ string stringRet;
+ char fileMidiByte;
+ static int bytesGot = 0; // purely for progress reporting purposes
+
+ if (midiFile->eof()) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MIDI file EOF - got "
+ << stringRet.length() << " bytes out of "
+ << numberOfBytes << endl;
+#endif
+
+ throw(Exception("End of MIDI file encountered while reading"));
+
+ }
+
+ if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Attempt to get more bytes than allowed on Track ("
+ << numberOfBytes
+ << " > "
+ << m_trackByteCount << endl;
+#endif
+
+ //!!! Investigate -- I'm seeing this on new-notation-quantization
+ // branch: load glazunov.rg, run Interpret on first segment, export
+ // and attempt to import again
+
+ throw(Exception("Attempt to get more bytes than expected on Track"));
+ }
+
+ while (stringRet.length() < numberOfBytes &&
+ midiFile->read(&fileMidiByte, 1)) {
+ stringRet += fileMidiByte;
+ }
+
+ // if we've reached the end of file without fulfilling the
+ // quota then panic as our parsing has performed incorrectly
+ //
+ if (stringRet.length() < numberOfBytes) {
+ stringRet = "";
+#ifdef MIDI_DEBUG
+
+ cerr << "Attempt to read past file end - got "
+ << stringRet.length() << " bytes out of "
+ << numberOfBytes << endl;
+#endif
+
+ throw(Exception("Attempt to read past MIDI file end"));
+
+ }
+
+ // decrement the byte count
+ if (m_decrementCount)
+ m_trackByteCount -= stringRet.length();
+
+ // update a progress dialog if we have one
+ //
+ bytesGot += numberOfBytes;
+ if (bytesGot % 2000 == 0) {
+ emit setProgress((int)(double(midiFile->tellg()) /
+ double(m_fileSize) * 20.0));
+ kapp->processEvents(50);
+ }
+
+ return stringRet;
+}
+
+
+// Get a long number of variable length from the MIDI byte stream.
+//
+//
+long
+MidiFile::getNumberFromMidiBytes(ifstream* midiFile, int firstByte)
+{
+ long longRet = 0;
+ MidiByte midiByte;
+
+ if (firstByte >= 0) {
+ midiByte = (MidiByte)firstByte;
+ } else if (midiFile->eof()) {
+ return longRet;
+ } else {
+ midiByte = getMidiByte(midiFile);
+ }
+
+ longRet = midiByte;
+ if (midiByte & 0x80 ) {
+ longRet &= 0x7F;
+ do {
+ midiByte = getMidiByte(midiFile);
+ longRet = (longRet << 7) + (midiByte & 0x7F);
+ } while (!midiFile->eof() && (midiByte & 0x80));
+ }
+
+ return longRet;
+}
+
+
+
+// Seeks to the next track in the midi file and sets the number
+// of bytes to be read in the counter m_trackByteCount.
+//
+bool
+MidiFile::skipToNextTrack(ifstream *midiFile)
+{
+ string buffer, buffer2;
+ m_trackByteCount = -1;
+ m_decrementCount = false;
+
+ while (!midiFile->eof() && (m_decrementCount == false )) {
+ buffer = getMidiBytes(midiFile, 4);
+
+#if (__GNUC__ < 3)
+
+ if (buffer.compare(MIDI_TRACK_HEADER, 0, 4) == 0)
+#else
+
+ if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0)
+#endif
+
+ {
+ m_trackByteCount = midiBytesToLong(getMidiBytes(midiFile, 4));
+ m_decrementCount = true;
+ }
+
+ }
+
+ if ( m_trackByteCount == -1 ) // we haven't found a track
+ return (false);
+ else
+ return (true);
+}
+
+
+// Read in a MIDI file. The parsing process throws string
+// exceptions back up here if we run into trouble which we
+// can then pass back out to whoever called us using a nice
+// bool.
+//
+//
+bool
+MidiFile::open()
+{
+ bool retOK = true;
+ m_error = "";
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::open() : fileName = " << m_fileName.c_str() << endl;
+#endif
+
+ // Open the file
+ ifstream *midiFile = new ifstream(m_fileName.c_str(), ios::in | ios::binary);
+
+ try {
+ if (*midiFile) {
+
+ // Set file size so we can count it off
+ //
+ midiFile->seekg(0, std::ios::end);
+ m_fileSize = midiFile->tellg();
+ midiFile->seekg(0, std::ios::beg);
+
+ // Parse the MIDI header first. The first 14 bytes of the file.
+ if (!parseHeader(getMidiBytes(midiFile, 14))) {
+ m_format = MIDI_FILE_NOT_LOADED;
+ m_error = "Not a MIDI file.";
+ return (false);
+ }
+
+ m_containsTimeChanges = false;
+
+ TrackId i = 0;
+
+ for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
+
+//#ifdef MIDI_DEBUG
+ std::cerr << "Parsing Track " << j << endl;
+//#endif
+
+ if (!skipToNextTrack(midiFile)) {
+#ifdef MIDI_DEBUG
+ cerr << "Couldn't find Track " << j << endl;
+#endif
+
+ m_error = "File corrupted or in non-standard format?";
+ m_format = MIDI_FILE_NOT_LOADED;
+ return (false);
+ }
+
+#ifdef MIDI_DEBUG
+ std::cerr << "Track has " << m_trackByteCount << " bytes" << std::endl;
+#endif
+
+ // Run through the events taking them into our internal
+ // representation.
+ if (!parseTrack(midiFile, i)) {
+//#ifdef MIDI_DEBUG
+ std::cerr << "Track " << j << " parsing failed" << endl;
+//#endif
+
+ m_error = "File corrupted or in non-standard format?";
+ m_format = MIDI_FILE_NOT_LOADED;
+ return (false);
+ }
+
+ ++i; // j is the source track number, i the destination
+ }
+
+ m_numberOfTracks = i;
+ } else {
+ m_error = "File not found or not readable.";
+ m_format = MIDI_FILE_NOT_LOADED;
+ return (false);
+ }
+
+ // Close the file now
+ midiFile->close();
+ } catch (Exception e) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::open() - caught exception - "
+ << e.getMessage() << endl;
+#endif
+
+ m_error = e.getMessage();
+ retOK = false;
+ }
+
+ return (retOK);
+}
+
+// Parse and ensure the MIDI Header is legitimate
+//
+//
+bool
+MidiFile::parseHeader(const string &midiHeader)
+{
+ if (midiHeader.size() < 14) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader() - file header undersized" << endl;
+#endif
+
+ return (false);
+ }
+
+#if (__GNUC__ < 3)
+ if (midiHeader.compare(MIDI_FILE_HEADER, 0, 4) != 0)
+#else
+
+ if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0)
+#endif
+
+ {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader()"
+ << "- file header not found or malformed"
+ << endl;
+#endif
+
+ return (false);
+ }
+
+ if (midiBytesToLong(midiHeader.substr(4, 4)) != 6L) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader()"
+ << " - header length incorrect"
+ << endl;
+#endif
+
+ return (false);
+ }
+
+ m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8, 2));
+ m_numberOfTracks = midiBytesToInt(midiHeader.substr(10, 2));
+ m_timingDivision = midiBytesToInt(midiHeader.substr(12, 2));
+
+ if ( m_format == MIDI_SEQUENTIAL_TRACK_FILE ) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader()"
+ << "- can't load sequential track file"
+ << endl;
+#endif
+
+ return (false);
+ }
+
+
+#ifdef MIDI_DEBUG
+ if ( m_timingDivision < 0 ) {
+ std::cerr << "MidiFile::parseHeader()"
+ << " - file uses SMPTE timing"
+ << endl;
+ }
+#endif
+
+ return (true);
+}
+
+
+
+// Extract the contents from a MIDI file track and places it into
+// our local map of MIDI events.
+//
+//
+bool
+MidiFile::parseTrack(ifstream* midiFile, TrackId &lastTrackNum)
+{
+ MidiByte midiByte, metaEventCode, data1, data2;
+ MidiByte eventCode = 0x80;
+ std::string metaMessage;
+ unsigned int messageLength;
+ unsigned long deltaTime;
+ unsigned long accumulatedTime = 0;
+
+ // The trackNum passed in to this method is the default track for
+ // all events provided they're all on the same channel. If we find
+ // events on more than one channel, we increment trackNum and record
+ // the mapping from channel to trackNum in this channelTrackMap.
+ // We then return the new trackNum by reference so the calling
+ // method knows we've got more tracks than expected.
+
+ // This would be a vector<TrackId> but TrackId is unsigned
+ // and we need -1 to indicate "not yet used"
+ std::vector<int> channelTrackMap(16, -1);
+
+ // This is used to store the last absolute time found on each track,
+ // allowing us to modify delta-times correctly when separating events
+ // out from one to multiple tracks
+ //
+ std::map<int, unsigned long> trackTimeMap;
+
+ // Meta-events don't have a channel, so we place them in a fixed
+ // track number instead
+ TrackId metaTrack = lastTrackNum;
+
+ // Remember the last non-meta status byte (-1 if we haven't seen one)
+ int runningStatus = -1;
+
+ bool firstTrack = true;
+
+ std::cerr << "Parse track: last track number is " << lastTrackNum << std::endl;
+
+ while (!midiFile->eof() && ( m_trackByteCount > 0 ) ) {
+ if (eventCode < 0x80) {
+#ifdef MIDI_DEBUG
+ cerr << "WARNING: Invalid event code " << eventCode
+ << " in MIDI file" << endl;
+#endif
+
+ throw (Exception("Invalid event code found"));
+ }
+
+ deltaTime = getNumberFromMidiBytes(midiFile);
+
+#ifdef MIDI_DEBUG
+ cerr << "read delta time " << deltaTime << endl;
+#endif
+
+ // Get a single byte
+ midiByte = getMidiByte(midiFile);
+
+ if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
+ if (runningStatus < 0) {
+ throw (Exception("Running status used for first event in track"));
+ }
+
+ eventCode = (MidiByte)runningStatus;
+ data1 = midiByte;
+
+#ifdef MIDI_DEBUG
+ std::cerr << "using running status (byte " << int(midiByte) << " found)" << std::endl;
+#endif
+
+ } else {
+#ifdef MIDI_DEBUG
+ std::cerr << "have new event code " << int(midiByte) << std::endl;
+#endif
+
+ eventCode = midiByte;
+ data1 = getMidiByte(midiFile);
+ }
+
+ if (eventCode == MIDI_FILE_META_EVENT) // meta events
+ {
+ // metaEventCode = getMidiByte(midiFile);
+ metaEventCode = data1;
+ messageLength = getNumberFromMidiBytes(midiFile);
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found" << std::endl;
+#endif
+
+ metaMessage = getMidiBytes(midiFile, messageLength);
+
+ if (metaEventCode == MIDI_TIME_SIGNATURE ||
+ metaEventCode == MIDI_SET_TEMPO)
+ {
+ m_containsTimeChanges = true;
+ }
+
+ long gap = accumulatedTime - trackTimeMap[metaTrack];
+ accumulatedTime += deltaTime;
+ deltaTime += gap;
+ trackTimeMap[metaTrack] = accumulatedTime;
+
+ MidiEvent *e = new MidiEvent(deltaTime,
+ MIDI_FILE_META_EVENT,
+ metaEventCode,
+ metaMessage);
+
+ m_midiComposition[metaTrack].push_back(e);
+
+ } else // the rest
+ {
+ runningStatus = eventCode;
+
+ MidiEvent *midiEvent;
+
+ int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
+ if (channelTrackMap[channel] == -1) {
+ if (!firstTrack) {
+ ++lastTrackNum;
+ } else {
+ firstTrack = false;
+ }
+ std::cerr << "MidiFile: new channel map entry: channel " << channel << " -> track " << lastTrackNum << std::endl;
+ channelTrackMap[channel] = lastTrackNum;
+ m_trackChannelMap[lastTrackNum] = channel;
+ }
+
+ TrackId trackNum = channelTrackMap[channel];
+
+ {
+ static int prevTrackNum = -1, prevChannel = -1;
+ if (prevTrackNum != (int) trackNum ||
+ prevChannel != (int) channel) {
+ std::cerr << "MidiFile: track number for channel " << channel << " is " << trackNum << std::endl;
+ prevTrackNum = trackNum;
+ prevChannel = channel;
+ }
+ }
+
+ // accumulatedTime is abs time of last event on any track;
+ // trackTimeMap[trackNum] is that of last event on this track
+
+ long gap = accumulatedTime - trackTimeMap[trackNum];
+ accumulatedTime += deltaTime;
+ deltaTime += gap;
+ trackTimeMap[trackNum] = accumulatedTime;
+
+ switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
+ case MIDI_NOTE_ON:
+ case MIDI_NOTE_OFF:
+ case MIDI_POLY_AFTERTOUCH:
+ case MIDI_CTRL_CHANGE:
+ data2 = getMidiByte(midiFile);
+
+ // create and store our event
+ midiEvent = new MidiEvent(deltaTime, eventCode, data1, data2);
+
+ /*
+ std::cerr << "MIDI event for channel " << channel << " (track "
+ << trackNum << ")" << std::endl;
+ midiEvent->print();
+ */
+
+
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_PITCH_BEND:
+ data2 = getMidiByte(midiFile);
+
+ // create and store our event
+ midiEvent = new MidiEvent(deltaTime, eventCode, data1, data2);
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_PROG_CHANGE:
+ case MIDI_CHNL_AFTERTOUCH:
+ // create and store our event
+ std::cerr << "Program change or channel aftertouch: time " << deltaTime << ", code " << (int)eventCode << ", data " << (int) data1 << " going to track " << trackNum << std::endl;
+ midiEvent = new MidiEvent(deltaTime, eventCode, data1);
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_SYSTEM_EXCLUSIVE:
+ messageLength = getNumberFromMidiBytes(midiFile, data1);
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "SysEx of " << messageLength << " bytes found" << std::endl;
+#endif
+
+ metaMessage = getMidiBytes(midiFile, messageLength);
+
+ if (MidiByte(metaMessage[metaMessage.length() - 1]) !=
+ MIDI_END_OF_EXCLUSIVE) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseTrack() - "
+ << "malformed or unsupported SysEx type"
+ << std::endl;
+#endif
+
+ continue;
+ }
+
+ // chop off the EOX
+ // length fixed by Pedro Lopez-Cabanillas (20030523)
+ //
+ metaMessage = metaMessage.substr(0, metaMessage.length() - 1);
+
+ midiEvent = new MidiEvent(deltaTime,
+ MIDI_SYSTEM_EXCLUSIVE,
+ metaMessage);
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_END_OF_EXCLUSIVE:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::parseTrack() - "
+ << "Found a stray MIDI_END_OF_EXCLUSIVE" << std::endl;
+#endif
+
+ break;
+
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::parseTrack()"
+ << " - Unsupported MIDI Event Code: "
+ << (int)eventCode << endl;
+#endif
+
+ break;
+ }
+ }
+ }
+
+ return (true);
+}
+
+// borrowed from ALSA pcm_timer.c
+//
+static unsigned long gcd(unsigned long a, unsigned long b)
+{
+ unsigned long r;
+ if (a < b) {
+ r = a;
+ a = b;
+ b = r;
+ }
+ while ((r = a % b) != 0) {
+ a = b;
+ b = r;
+ }
+ return b;
+}
+
+// If we wanted to abstract the MidiFile class to make it more useful to
+// other applications (and formats) we'd make this method and its twin
+// pure virtual.
+//
+bool
+MidiFile::convertToRosegarden(Composition &composition, ConversionType type)
+{
+ Profiler profiler("MidiFile::convertToRosegarden");
+
+ MidiTrack::iterator midiEvent;
+ Segment *rosegardenSegment;
+ Segment *conductorSegment = 0;
+ Event *rosegardenEvent;
+ string trackName;
+
+ // Time conversions
+ //
+ timeT rosegardenTime = 0;
+ timeT rosegardenDuration = 0;
+ timeT maxTime = 0;
+
+ // To create rests
+ //
+ timeT endOfLastNote;
+
+ // Event specific vars
+ //
+ int numerator = 4;
+ int denominator = 4;
+ timeT segmentTime;
+
+ // keys
+ int accidentals;
+ bool isMinor;
+ bool isSharp;
+
+ if (type == CONVERT_REPLACE)
+ composition.clear();
+
+ timeT origin = 0;
+ if (type == CONVERT_APPEND && composition.getDuration() > 0) {
+ origin = composition.getBarEndForTime(composition.getDuration());
+ }
+
+ TrackId compTrack = 0;
+ for (Composition::iterator ci = composition.begin();
+ ci != composition.end(); ++ci) {
+ if ((*ci)->getTrack() >= compTrack)
+ compTrack = (*ci)->getTrack() + 1;
+ }
+
+ Track *track = 0;
+
+ // precalculate the timing factor
+ //
+ // [cc] -- attempt to avoid floating-point rounding errors
+ timeT crotchetTime = Note(Note::Crotchet).getDuration();
+ int divisor = m_timingDivision ? m_timingDivision : 96;
+
+ unsigned long multiplier = crotchetTime;
+ int g = (int)gcd(crotchetTime, divisor);
+ multiplier /= g;
+ divisor /= g;
+
+ timeT maxRawTime = LONG_MAX;
+ if (multiplier > divisor)
+ maxRawTime = (maxRawTime / multiplier) * divisor;
+
+ bool haveTimeSignatures = false;
+ InstrumentId compInstrument = MidiInstrumentBase;
+
+ // Clear down the assigned Instruments we already have
+ //
+ if (type == CONVERT_REPLACE) {
+ m_studio->unassignAllInstruments();
+ }
+
+ std::vector<Segment *> addedSegments;
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "NUMBER OF TRACKS = " << m_numberOfTracks << endl;
+ std::cerr << "MIDI COMP SIZE = " << m_midiComposition.size() << endl;
+#endif
+
+ for (TrackId i = 0; i < m_numberOfTracks; i++ ) {
+ segmentTime = 0;
+ trackName = string("Imported MIDI");
+
+ // progress - 20% total in file import itself and then 80%
+ // split over these tracks
+ emit setProgress(20 +
+ (int)((80.0 * double(i) / double(m_numberOfTracks))));
+ kapp->processEvents(50);
+
+ // Convert the deltaTime to an absolute time since
+ // the start of the segment. The addTime method
+ // returns the sum of the current Midi Event delta
+ // time plus the argument.
+ //
+ for (midiEvent = m_midiComposition[i].begin();
+ midiEvent != m_midiComposition[i].end();
+ ++midiEvent) {
+ segmentTime = (*midiEvent)->addTime(segmentTime);
+ }
+
+ // Consolidate NOTE ON and NOTE OFF events into a NOTE ON with
+ // a duration.
+ //
+ consolidateNoteOffEvents(i);
+
+ if (m_trackChannelMap.find(i) != m_trackChannelMap.end()) {
+ compInstrument = MidiInstrumentBase + m_trackChannelMap[i];
+ } else {
+ compInstrument = MidiInstrumentBase;
+ }
+
+ rosegardenSegment = new Segment;
+ rosegardenSegment->setTrack(compTrack);
+ rosegardenSegment->setStartTime(0);
+
+ track = new Track(compTrack, // id
+ compInstrument, // instrument
+ compTrack, // position
+ trackName, // name
+ false); // muted
+
+ std::cerr << "New Rosegarden track: id = " << compTrack << ", instrument = " << compInstrument << ", name = " << trackName << std::endl;
+
+ // rest creation token needs to be reset here
+ //
+ endOfLastNote = 0;
+
+ int msb = -1, lsb = -1; // for bank selects
+ Instrument *instrument = 0;
+
+ for (midiEvent = m_midiComposition[i].begin();
+ midiEvent != m_midiComposition[i].end();
+ midiEvent++) {
+ rosegardenEvent = 0;
+
+ // [cc] -- avoid floating-point where possible
+
+ timeT rawTime = (*midiEvent)->getTime();
+
+ if (rawTime < maxRawTime) {
+ rosegardenTime = origin +
+ timeT((rawTime * multiplier) / divisor);
+ } else {
+ rosegardenTime = origin +
+ timeT((double(rawTime) * multiplier) / double(divisor) + 0.01);
+ }
+
+ rosegardenDuration =
+ timeT(((*midiEvent)->getDuration() * multiplier) / divisor);
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MIDI file import: origin " << origin
+ << ", event time " << rosegardenTime
+ << ", duration " << rosegardenDuration
+ << ", event type " << (int)(*midiEvent)->getMessageType()
+ << ", previous max time " << maxTime
+ << ", potential max time " << (rosegardenTime + rosegardenDuration)
+ << ", ev raw time " << (*midiEvent)->getTime()
+ << ", crotchet " << crotchetTime
+ << ", multiplier " << multiplier
+ << ", divisor " << divisor
+ << std::endl;
+#endif
+
+ if (rosegardenTime + rosegardenDuration > maxTime) {
+ maxTime = rosegardenTime + rosegardenDuration;
+ }
+
+ // timeT fillFromTime = rosegardenTime;
+ if (rosegardenSegment->empty()) {
+ // fillFromTime = composition.getBarStartForTime(rosegardenTime);
+ endOfLastNote = composition.getBarStartForTime(rosegardenTime);
+ }
+
+ if ((*midiEvent)->isMeta()) {
+
+ switch ((*midiEvent)->getMetaEventCode()) {
+
+ case MIDI_TEXT_EVENT: {
+ std::string text = (*midiEvent)->getMetaMessage();
+ rosegardenEvent =
+ Text(text).getAsEvent(rosegardenTime);
+ }
+ break;
+
+ case MIDI_LYRIC: {
+ std::string text = (*midiEvent)->getMetaMessage();
+// std::cerr << "lyric event: text=\""
+// << text << "\", time=" << rosegardenTime << std::endl;
+ rosegardenEvent =
+ Text(text, Text::Lyric).
+ getAsEvent(rosegardenTime);
+ }
+ break;
+
+ case MIDI_TEXT_MARKER: {
+ std::string text = (*midiEvent)->getMetaMessage();
+ composition.addMarker(new Marker
+ (rosegardenTime, text, ""));
+ }
+ break;
+
+ case MIDI_COPYRIGHT_NOTICE:
+ if (type == CONVERT_REPLACE) {
+ composition.setCopyrightNote((*midiEvent)->
+ getMetaMessage());
+ }
+ break;
+
+ case MIDI_TRACK_NAME:
+ track->setLabel((*midiEvent)->getMetaMessage());
+ break;
+
+ case MIDI_INSTRUMENT_NAME:
+ rosegardenSegment->setLabel((*midiEvent)->getMetaMessage());
+ break;
+
+ case MIDI_END_OF_TRACK: {
+ timeT trackEndTime = rosegardenTime;
+ if (trackEndTime <= 0) {
+ trackEndTime = crotchetTime * 4 * numerator / denominator;
+ }
+ if (endOfLastNote < trackEndTime) {
+ //If there's nothing in the segment yet, then we
+ //shouldn't fill with rests because we don't want
+ //to cause the otherwise empty segment to be created
+ if (rosegardenSegment->size() > 0) {
+ rosegardenSegment->fillWithRests(trackEndTime);
+ }
+ }
+ }
+ break;
+
+ case MIDI_SET_TEMPO: {
+ MidiByte m0 = (*midiEvent)->getMetaMessage()[0];
+ MidiByte m1 = (*midiEvent)->getMetaMessage()[1];
+ MidiByte m2 = (*midiEvent)->getMetaMessage()[2];
+
+ long tempo = (((m0 << 8) + m1) << 8) + m2;
+
+ if (tempo != 0) {
+ double qpm = 60000000.0 / double(tempo);
+ tempoT rgt(Composition::getTempoForQpm(qpm));
+ std::cout << "MidiFile: converted MIDI tempo " << tempo << " to Rosegarden tempo " << rgt << std::endl;
+ composition.addTempoAtTime(rosegardenTime, rgt);
+ }
+ }
+ break;
+
+ case MIDI_TIME_SIGNATURE:
+ numerator = (int) (*midiEvent)->getMetaMessage()[0];
+ denominator = 1 << ((int)(*midiEvent)->getMetaMessage()[1]);
+
+ // NB. a MIDI time signature also has
+ // metamessage[2] and [3], containing some timing data
+
+ if (numerator == 0)
+ numerator = 4;
+ if (denominator == 0)
+ denominator = 4;
+
+ composition.addTimeSignature
+ (rosegardenTime,
+ TimeSignature(numerator, denominator));
+ haveTimeSignatures = true;
+ break;
+
+ case MIDI_KEY_SIGNATURE:
+ // get the details
+ accidentals = (int) (*midiEvent)->getMetaMessage()[0];
+ isMinor = (int) (*midiEvent)->getMetaMessage()[1];
+ isSharp = accidentals < 0 ? false : true;
+ accidentals = accidentals < 0 ? -accidentals : accidentals;
+ // create the key event
+ //
+ try {
+ rosegardenEvent = Rosegarden::Key
+ (accidentals, isSharp, isMinor).
+ getAsEvent(rosegardenTime);
+ }
+ catch (...) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::convertToRosegarden - "
+ << " badly formed key signature"
+ << std::endl;
+#endif
+
+ break;
+ }
+ break;
+
+ case MIDI_SEQUENCE_NUMBER:
+ case MIDI_CHANNEL_PREFIX_OR_PORT:
+ case MIDI_CUE_POINT:
+ case MIDI_CHANNEL_PREFIX:
+ case MIDI_SEQUENCER_SPECIFIC:
+ case MIDI_SMPTE_OFFSET:
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::convertToRosegarden - "
+ << "unsupported META event code "
+ << (int)((*midiEvent)->getMetaEventCode()) << endl;
+#endif
+
+ break;
+ }
+
+ } else
+ switch ((*midiEvent)->getMessageType()) {
+ case MIDI_NOTE_ON:
+
+ // A zero velocity here is a virtual "NOTE OFF"
+ // so we ignore this event
+ //
+ if ((*midiEvent)->getVelocity() == 0)
+ break;
+
+ endOfLastNote = rosegardenTime + rosegardenDuration;
+
+ //std::cerr << "MidiFile::convertToRosegarden: note at " << rosegardenTime << ", midi time " << (*midiEvent)->getTime() << std::endl;
+
+ // create and populate event
+ rosegardenEvent = new Event(Note::EventType,
+ rosegardenTime,
+ rosegardenDuration);
+ rosegardenEvent->set
+ <Int>(BaseProperties::PITCH,
+ (*midiEvent)->getPitch());
+ rosegardenEvent->set
+ <Int>(BaseProperties::VELOCITY,
+ (*midiEvent)->getVelocity());
+ break;
+
+ // We ignore any NOTE OFFs here as we've already
+ // converted NOTE ONs to have duration
+ //
+ case MIDI_NOTE_OFF:
+ continue;
+ break;
+
+ case MIDI_PROG_CHANGE:
+ // Attempt to turn the prog change we've found into an
+ // Instrument. Send the program number and whether or
+ // not we're on the percussion channel.
+ //
+ // Note that we make no attempt to do the right
+ // thing with program changes during a track -- we
+ // just save them as events. Only the first is
+ // used to select the instrument. If it's at time
+ // zero, it's not saved as an event.
+ //
+// std::cerr << "Program change found" << std::endl;
+
+ if (!instrument) {
+
+ bool percussion = (*midiEvent)->getChannelNumber() ==
+ MIDI_PERCUSSION_CHANNEL;
+ int program = (*midiEvent)->getData1();
+
+ if (type == CONVERT_REPLACE) {
+
+ instrument = m_studio->getInstrumentById(compInstrument);
+ if (instrument) {
+ instrument->setPercussion(percussion);
+ instrument->setSendProgramChange(true);
+ instrument->setProgramChange(program);
+ instrument->setSendBankSelect(msb >= 0 || lsb >= 0);
+ if (instrument->sendsBankSelect()) {
+ instrument->setMSB(msb >= 0 ? msb : 0);
+ instrument->setLSB(lsb >= 0 ? lsb : 0);
+ }
+ }
+ } else { // not CONVERT_REPLACE
+ instrument =
+ m_studio->assignMidiProgramToInstrument
+ (program, msb, lsb, percussion);
+ }
+ }
+
+ // assign it here
+ if (instrument) {
+ track->setInstrument(instrument->getId());
+ // We used to set the segment name from the instrument
+ // here, but now we do them all at the end only if the
+ // segment has no other name set (e.g. from instrument
+ // meta event)
+ if ((*midiEvent)->getTime() == 0) break; // no insert
+ }
+
+ // did we have a bank select? if so, insert that too
+
+ if (msb >= 0) {
+ rosegardenSegment->insert
+ (Controller(MIDI_CONTROLLER_BANK_MSB, msb).
+ getAsEvent(rosegardenTime));
+ }
+ if (lsb >= 0) {
+ rosegardenSegment->insert
+ (Controller(MIDI_CONTROLLER_BANK_LSB, msb).
+ getAsEvent(rosegardenTime));
+ }
+
+ rosegardenEvent =
+ ProgramChange((*midiEvent)->getData1()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_CTRL_CHANGE:
+
+ // If it's a bank select, interpret it (or remember
+ // for later insertion) instead of just inserting it
+ // as a Rosegarden event
+
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_BANK_MSB) {
+ msb = (*midiEvent)->getData2();
+ break;
+ }
+
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_BANK_LSB) {
+ lsb = (*midiEvent)->getData2();
+ break;
+ }
+
+ // If it's something we can use as an instrument
+ // parameter, and it's at time zero, and we already
+ // have an instrument, then apply it to the instrument
+ // instead of inserting
+
+ if (instrument && (*midiEvent)->getTime() == 0) {
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_VOLUME) {
+ instrument->setVolume((*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_PAN) {
+ instrument->setPan((*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_ATTACK) {
+ instrument->setControllerValue(MIDI_CONTROLLER_ATTACK, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_RELEASE) {
+ instrument->setControllerValue(MIDI_CONTROLLER_RELEASE, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_FILTER) {
+ instrument->setControllerValue(MIDI_CONTROLLER_FILTER, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_RESONANCE) {
+ instrument->setControllerValue(MIDI_CONTROLLER_RESONANCE, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_CHORUS) {
+ instrument->setControllerValue(MIDI_CONTROLLER_CHORUS, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_REVERB) {
+ instrument->setControllerValue(MIDI_CONTROLLER_REVERB, (*midiEvent)->getData2());
+ break;
+ }
+ }
+
+ rosegardenEvent =
+ Controller((*midiEvent)->getData1(),
+ (*midiEvent)->getData2()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_PITCH_BEND:
+ rosegardenEvent =
+ PitchBend((*midiEvent)->getData2(),
+ (*midiEvent)->getData1()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_SYSTEM_EXCLUSIVE:
+ rosegardenEvent =
+ SystemExclusive((*midiEvent)->getMetaMessage()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_POLY_AFTERTOUCH:
+ rosegardenEvent =
+ KeyPressure((*midiEvent)->getData1(),
+ (*midiEvent)->getData2()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_CHNL_AFTERTOUCH:
+ rosegardenEvent =
+ ChannelPressure((*midiEvent)->getData1()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::convertToRosegarden - "
+ << "Unsupported event code = "
+ << (int)(*midiEvent)->getMessageType() << std::endl;
+#endif
+
+ break;
+ }
+
+ if (rosegardenEvent) {
+ // if (fillFromTime < rosegardenTime) {
+ // rosegardenSegment->fillWithRests(fillFromTime, rosegardenTime);
+ // }
+ if (endOfLastNote < rosegardenTime) {
+ rosegardenSegment->fillWithRests(endOfLastNote, rosegardenTime);
+ }
+ rosegardenSegment->insert(rosegardenEvent);
+ }
+ }
+
+ if (rosegardenSegment->size() > 0) {
+
+ // if all we have is key signatures and rests, take this
+ // to be a conductor segment and don't insert it
+ //
+ bool keySigsOnly = true;
+ bool haveKeySig = false;
+ for (Segment::iterator i = rosegardenSegment->begin();
+ i != rosegardenSegment->end(); ++i) {
+ if (!(*i)->isa(Rosegarden::Key::EventType) &&
+ !(*i)->isa(Note::EventRestType)) {
+ keySigsOnly = false;
+ break;
+ } else if ((*i)->isa(Rosegarden::Key::EventType)) {
+ haveKeySig = true;
+ }
+ }
+
+ if (keySigsOnly) {
+ conductorSegment = rosegardenSegment;
+ continue;
+ } else if (!haveKeySig && conductorSegment) {
+ // copy across any key sigs from the conductor segment
+
+ timeT segmentStartTime = rosegardenSegment->getStartTime();
+ timeT earliestEventEndTime = segmentStartTime;
+
+ for (Segment::iterator i = conductorSegment->begin();
+ i != conductorSegment->end(); ++i) {
+ if ((*i)->getAbsoluteTime() + (*i)->getDuration() <
+ earliestEventEndTime) {
+ earliestEventEndTime =
+ (*i)->getAbsoluteTime() + (*i)->getDuration();
+ }
+ rosegardenSegment->insert(new Event(**i));
+ }
+
+ if (earliestEventEndTime < segmentStartTime) {
+ rosegardenSegment->fillWithRests(earliestEventEndTime,
+ segmentStartTime);
+ }
+ }
+
+#ifdef MIDI_DEBUG
+ std::cerr << "MIDI import: adding segment with start time " << rosegardenSegment->getStartTime() << " and end time " << rosegardenSegment->getEndTime() << std::endl;
+ if (rosegardenSegment->getEndTime() == 2880) {
+ std::cerr << "events:" << std::endl;
+ for (Segment::iterator i = rosegardenSegment->begin();
+ i != rosegardenSegment->end(); ++i) {
+ std::cerr << "type = " << (*i)->getType() << std::endl;
+ std::cerr << "time = " << (*i)->getAbsoluteTime() << std::endl;
+ std::cerr << "duration = " << (*i)->getDuration() << std::endl;
+ }
+ }
+#endif
+
+ // add the Segment to the Composition and increment the
+ // Rosegarden segment number
+ //
+ composition.addTrack(track);
+ composition.addSegment(rosegardenSegment);
+ addedSegments.push_back(rosegardenSegment);
+ compTrack++;
+
+ } else {
+ delete rosegardenSegment;
+ rosegardenSegment = 0;
+ delete track;
+ track = 0;
+ }
+ }
+
+ if (type == CONVERT_REPLACE || maxTime > composition.getEndMarker()) {
+ composition.setEndMarker(composition.getBarEndForTime(maxTime));
+ }
+
+ for (std::vector<Segment *>::iterator i = addedSegments.begin();
+ i != addedSegments.end(); ++i) {
+ Segment *s = *i;
+ if (s) {
+ timeT duration = s->getEndMarkerTime() - s->getStartTime();
+/*
+ std::cerr << "duration = " << duration << " (start "
+ << s->getStartTime() << ", end " << s->getEndTime()
+ << ", marker " << s->getEndMarkerTime() << ")" << std::endl;
+*/
+ if (duration == 0) {
+ s->setEndMarkerTime(s->getStartTime() +
+ Note(Note::Crotchet).getDuration());
+ }
+ Instrument *instr = m_studio->getInstrumentFor(s);
+ if (instr) {
+ if (s->getLabel() == "") {
+ s->setLabel(m_studio->getSegmentName(instr->getId()));
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+// Takes a Composition and turns it into internal MIDI representation
+// that can then be written out to file.
+//
+// For the moment we should watch to make sure that multiple Segment
+// (parts) don't equate to multiple segments in the MIDI Composition.
+//
+// This is a two pass operation - firstly convert the RG Composition
+// into MIDI events and insert anything extra we need (i.e. NOTE OFFs)
+// with absolute times before then processing all timings into delta
+// times.
+//
+//
+void
+MidiFile::convertToMidi(Composition &comp)
+{
+ MidiEvent *midiEvent;
+ int conductorTrack = 0;
+
+ timeT midiEventAbsoluteTime;
+ MidiByte midiVelocity;
+ MidiByte midiChannel = 0;
+
+ // [cc] int rather than floating point
+ //
+ m_timingDivision = 480; //!!! make this configurable
+ timeT crotchetDuration = Note(Note::Crotchet).getDuration();
+
+ // Export as this format only
+ //
+ m_format = MIDI_SIMULTANEOUS_TRACK_FILE;
+
+ // Clear out the MidiComposition internal store
+ //
+ clearMidiComposition();
+
+ // Insert the Rosegarden Signature Track here and any relevant
+ // file META information - this will get written out just like
+ // any other MIDI track.
+ //
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_COPYRIGHT_NOTICE,
+ comp.getCopyrightNote());
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+ "Created by Rosegarden");
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+ "http://www.rosegardenmusic.com/");
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+
+ // Insert tempo events
+ //
+ for (int i = 0; i < comp.getTempoChangeCount(); i++) // i=0 should be comp.getStart-something
+ {
+ std::pair<timeT, tempoT> tempo = comp.getTempoChange(i);
+
+ midiEventAbsoluteTime = tempo.first * m_timingDivision
+ / crotchetDuration;
+
+ double qpm = Composition::getTempoQpm(tempo.second);
+ long tempoValue = long(60000000.0 / qpm + 0.01);
+
+ string tempoString;
+ tempoString += (MidiByte) ( tempoValue >> 16 & 0xFF );
+ tempoString += (MidiByte) ( tempoValue >> 8 & 0xFF );
+ tempoString += (MidiByte) ( tempoValue & 0xFF );
+
+ midiEvent = new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_SET_TEMPO,
+ tempoString);
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+ }
+
+ // Insert time signatures (don't worry that the times might be out
+ // of order with those of the tempo events -- we sort the track later)
+ //
+ for (int i = 0; i < comp.getTimeSignatureCount(); i++) {
+ std::pair<timeT, TimeSignature> timeSig =
+ comp.getTimeSignatureChange(i);
+
+ midiEventAbsoluteTime = timeSig.first * m_timingDivision
+ / crotchetDuration;
+
+ string timeSigString;
+ timeSigString += (MidiByte) (timeSig.second.getNumerator());
+ int denominator = timeSig.second.getDenominator();
+ int denPowerOf2 = 0;
+
+ // Work out how many powers of two are in the denominator
+ //
+ while (denominator >>= 1)
+ denPowerOf2++;
+
+ timeSigString += (MidiByte) denPowerOf2;
+
+ // The third byte is the number of MIDI clocks per beat.
+ // There are 24 clocks per quarter-note (the MIDI clock
+ // is tempo-independent and is not related to the timebase).
+ //
+ int cpb = 24 * timeSig.second.getBeatDuration() / crotchetDuration;
+ timeSigString += (MidiByte) cpb;
+
+ // And the fourth byte is always 8, for us (it expresses
+ // the number of notated 32nd-notes in a MIDI quarter-note,
+ // for applications that may want to notate and perform
+ // in different units)
+ //
+ timeSigString += (MidiByte) 8;
+
+ midiEvent = new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_TIME_SIGNATURE,
+ timeSigString);
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+ }
+
+ // Insert markers
+ // fix for bug#
+ Composition::markercontainer marks = comp.getMarkers();
+
+ for (unsigned int i = 0; i < marks.size(); i++) {
+ midiEventAbsoluteTime = marks[i]->getTime() * m_timingDivision
+ / crotchetDuration;
+
+ midiEvent = new MidiEvent( midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_TEXT_MARKER,
+ marks[i]->getName() );
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+ }
+
+ m_numberOfTracks = 1;
+ std::map<int, int> trackPosMap; // RG track pos -> MIDI track no
+
+ // In pass one just insert all events including new NOTE OFFs at the right
+ // absolute times.
+ //
+ for (Composition::const_iterator segment = comp.begin();
+ segment != comp.end(); ++segment) {
+
+ // We use this later to get NOTE durations
+ //
+ SegmentPerformanceHelper helper(**segment);
+
+ Track *track = comp.getTrackById((*segment)->getTrack());
+
+ if (track->isMuted()) continue;
+
+ // Fix #1602023, map Rosegarden tracks to MIDI tracks, instead of
+ // putting each segment out on a new track
+
+ int trackPosition = track->getPosition();
+ bool firstSegmentThisTrack = false;
+
+ if (trackPosMap.find(trackPosition) == trackPosMap.end()) {
+ firstSegmentThisTrack = true;
+ trackPosMap[trackPosition] = m_numberOfTracks++;
+ }
+
+ int trackNumber = trackPosMap[trackPosition];
+
+ MidiTrack &mtrack = m_midiComposition[trackNumber];
+
+ midiEvent = new MidiEvent(0,
+ MIDI_FILE_META_EVENT,
+ MIDI_TRACK_NAME,
+ track->getLabel());
+
+ mtrack.push_back(midiEvent);
+
+ // Get the Instrument
+ //
+ Instrument *instr =
+ m_studio->getInstrumentById(track->getInstrument());
+
+ if (firstSegmentThisTrack) {
+
+ MidiByte program = 0;
+ midiChannel = 0;
+
+ bool useBank = false;
+ MidiByte lsb = 0;
+ MidiByte msb = 0;
+
+ if (instr) {
+ midiChannel = instr->getMidiChannel();
+ program = instr->getProgramChange();
+ if (instr->sendsBankSelect()) {
+ lsb = instr->getLSB();
+ msb = instr->getMSB();
+ useBank = true;
+ }
+ }
+
+ if (useBank) {
+
+ // insert a bank select
+
+ if (msb != 0) {
+ midiEvent = new MidiEvent(0,
+ MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_BANK_MSB,
+ msb);
+ mtrack.push_back(midiEvent);
+ }
+
+ if (lsb != 0) {
+ midiEvent = new MidiEvent(0,
+ MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_BANK_LSB,
+ lsb);
+ mtrack.push_back(midiEvent);
+ }
+ }
+
+ // insert a program change
+ midiEvent = new MidiEvent(0, // time
+ MIDI_PROG_CHANGE | midiChannel,
+ program);
+ mtrack.push_back(midiEvent);
+
+ if (instr) {
+
+ // MidiInstrument parameters: volume, pan, attack,
+ // release, filter, resonance, chorus, reverb. Always
+ // write these: the Instrument has an additional parameter
+ // to record whether they should be sent, but it isn't
+ // actually set anywhere so we have to ignore it.
+
+ static int controllers[] = {
+ MIDI_CONTROLLER_ATTACK,
+ MIDI_CONTROLLER_RELEASE,
+ MIDI_CONTROLLER_FILTER,
+ MIDI_CONTROLLER_RESONANCE,
+ MIDI_CONTROLLER_CHORUS,
+ MIDI_CONTROLLER_REVERB
+ };
+
+ mtrack.push_back
+ (new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_VOLUME, instr->getVolume()));
+
+ mtrack.push_back
+ (new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_PAN, instr->getPan()));
+
+ for (int i = 0; i < sizeof(controllers)/sizeof(controllers[0]); ++i) {
+ try {
+ mtrack.push_back
+ (new MidiEvent
+ (0, MIDI_CTRL_CHANGE | midiChannel, controllers[i],
+ instr->getControllerValue(controllers[i])));
+ } catch (...) {
+ /* do nothing */
+ }
+ }
+ } // if (instr)
+ } // if (firstSegmentThisTrack)
+
+ timeT segmentMidiDuration =
+ ((*segment)->getEndMarkerTime() -
+ (*segment)->getStartTime()) * m_timingDivision /
+ crotchetDuration;
+
+ for (Segment::iterator el = (*segment)->begin();
+ (*segment)->isBeforeEndMarker(el); ++el) {
+ midiEventAbsoluteTime =
+ (*el)->getAbsoluteTime() + (*segment)->getDelay();
+
+ timeT absoluteTimeLimit = midiEventAbsoluteTime;
+ if ((*segment)->isRepeating()) {
+ absoluteTimeLimit = ((*segment)->getRepeatEndTime() - 1) +
+ (*segment)->getDelay();
+ }
+
+ if ((*segment)->getRealTimeDelay() != RealTime::zeroTime) {
+ RealTime evRT = comp.getElapsedRealTime(midiEventAbsoluteTime);
+ timeT timeBeforeDelay = midiEventAbsoluteTime;
+ midiEventAbsoluteTime = comp.getElapsedTimeForRealTime
+ (evRT + (*segment)->getRealTimeDelay());
+ absoluteTimeLimit += (midiEventAbsoluteTime - timeBeforeDelay);
+ }
+
+ midiEventAbsoluteTime =
+ midiEventAbsoluteTime * m_timingDivision / crotchetDuration;
+ absoluteTimeLimit =
+ absoluteTimeLimit * m_timingDivision / crotchetDuration;
+
+ while (midiEventAbsoluteTime <= absoluteTimeLimit) {
+
+ try {
+
+ if ((*el)->isa(Note::EventType)) {
+ if ((*el)->has(BaseProperties::VELOCITY))
+ midiVelocity = (*el)->get
+ <Int>(BaseProperties::VELOCITY);
+ else
+ midiVelocity = 127;
+
+ // Get the sounding time for the matching NOTE_OFF.
+ // We use SegmentPerformanceHelper::getSoundingDuration()
+ // to work out the tied duration of the NOTE.
+ timeT soundingDuration = helper.getSoundingDuration(el);
+ if (soundingDuration > 0) {
+
+ timeT midiEventEndTime = midiEventAbsoluteTime +
+ soundingDuration * m_timingDivision /
+ crotchetDuration;
+
+ long pitch = 60;
+ (*el)->get
+ <Int>(BaseProperties::PITCH, pitch);
+ pitch += (*segment)->getTranspose();
+
+ // insert the NOTE_ON at the appropriate channel
+ //
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_NOTE_ON | midiChannel,
+ pitch,
+ midiVelocity);
+
+ mtrack.push_back(midiEvent);
+
+ // insert the matching NOTE OFF
+ //
+ midiEvent =
+ new MidiEvent(midiEventEndTime,
+ MIDI_NOTE_OFF | midiChannel,
+ pitch,
+ 127); // full volume silence
+
+ mtrack.push_back(midiEvent);
+ }
+ } else if ((*el)->isa(PitchBend::EventType)) {
+ PitchBend pb(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_PITCH_BEND | midiChannel,
+ pb.getLSB(), pb.getMSB());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(Rosegarden::Key::EventType)) {
+ Rosegarden::Key key(**el);
+
+ int accidentals = key.getAccidentalCount();
+ if (!key.isSharp())
+ accidentals = -accidentals;
+
+ // stack out onto the meta string
+ //
+ std::string metaMessage;
+ metaMessage += MidiByte(accidentals);
+ metaMessage += MidiByte(key.isMinor());
+
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_KEY_SIGNATURE,
+ metaMessage);
+
+ //mtrack.push_back(midiEvent);
+
+ } else if ((*el)->isa(Controller::EventType)) {
+ Controller c(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_CTRL_CHANGE | midiChannel,
+ c.getNumber(), c.getValue());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(ProgramChange::EventType)) {
+ ProgramChange pc(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_PROG_CHANGE | midiChannel,
+ pc.getProgram());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(SystemExclusive::EventType)) {
+ SystemExclusive s(**el);
+ std::string data = s.getRawData();
+
+ // check for closing EOX and add one if none found
+ //
+ if (MidiByte(data[data.length() - 1]) != MIDI_END_OF_EXCLUSIVE) {
+ data += MIDI_END_OF_EXCLUSIVE;
+ }
+
+ // construct plain SYSEX event
+ //
+ midiEvent = new MidiEvent(midiEventAbsoluteTime,
+ MIDI_SYSTEM_EXCLUSIVE,
+ data);
+
+ mtrack.push_back(midiEvent);
+
+ } else if ((*el)->isa(ChannelPressure::EventType)) {
+ ChannelPressure cp(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_CHNL_AFTERTOUCH | midiChannel,
+ cp.getPressure());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(KeyPressure::EventType)) {
+ KeyPressure kp(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_POLY_AFTERTOUCH | midiChannel,
+ kp.getPitch(), kp.getPressure());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(Text::EventType)) {
+ Text text(**el);
+ std::string metaMessage = text.getText();
+
+ MidiByte midiTextType = MIDI_TEXT_EVENT;
+
+ if (text.getTextType() == Text::Lyric) {
+ midiTextType = MIDI_LYRIC;
+ }
+
+ if (text.getTextType() != Text::Annotation) {
+ // (we don't write annotations)
+
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ midiTextType,
+ metaMessage);
+
+ mtrack.push_back(midiEvent);
+ }
+ } else if ((*el)->isa(Note::EventRestType)) {
+ // skip legitimately
+ } else {
+ /*
+ cerr << "MidiFile::convertToMidi - "
+ << "unsupported MidiType \""
+ << (*el)->getType()
+ << "\" at export"
+ << std::endl;
+ */
+ }
+
+ } catch (MIDIValueOutOfRange r) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MIDI value out of range at "
+ << (*el)->getAbsoluteTime() << std::endl;
+#endif
+
+ } catch (Event::NoData d) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Caught Event::NoData at "
+ << (*el)->getAbsoluteTime() << ", message is:"
+ << std::endl << d.getMessage() << std::endl;
+#endif
+
+ } catch (Event::BadType b) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Caught Event::BadType at "
+ << (*el)->getAbsoluteTime() << ", message is:"
+ << std::endl << b.getMessage() << std::endl;
+#endif
+
+ } catch (SystemExclusive::BadEncoding e) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Caught bad SysEx encoding at "
+ << (*el)->getAbsoluteTime() << std::endl;
+#endif
+
+ }
+
+ if (segmentMidiDuration > 0) {
+ midiEventAbsoluteTime += segmentMidiDuration;
+ } else
+ break;
+ }
+ }
+ }
+
+ // Now gnash through the MIDI events and turn the absolute times
+ // into delta times.
+ //
+ //
+ MidiTrack::iterator it;
+ timeT deltaTime, lastMidiTime;
+
+ for (TrackId i = 0; i < m_numberOfTracks; i++) {
+ lastMidiTime = 0;
+
+ // First sort the track with the MidiEvent comparator. Use
+ // stable_sort so that events with equal times are maintained
+ // in their current order (important for e.g. bank-program
+ // pairs, or the controllers at the start of the track which
+ // should follow the program so we can treat them correctly
+ // when re-reading).
+ //
+ std::stable_sort(m_midiComposition[i].begin(),
+ m_midiComposition[i].end(),
+ MidiEventCmp());
+
+ for (it = m_midiComposition[i].begin();
+ it != m_midiComposition[i].end();
+ it++) {
+ deltaTime = (*it)->getTime() - lastMidiTime;
+ lastMidiTime = (*it)->getTime();
+ (*it)->setTime(deltaTime);
+ }
+
+ // Insert end of track event (delta time = 0)
+ //
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT,
+ MIDI_END_OF_TRACK, "");
+
+ m_midiComposition[i].push_back(midiEvent);
+
+ }
+
+ return ;
+}
+
+
+
+// Convert an integer into a two byte representation and
+// write out to the MidiFile.
+//
+void
+MidiFile::intToMidiBytes(std::ofstream* midiFile, int number)
+{
+ MidiByte upper;
+ MidiByte lower;
+
+ upper = (number & 0xFF00) >> 8;
+ lower = (number & 0x00FF);
+
+ *midiFile << (MidiByte) upper;
+ *midiFile << (MidiByte) lower;
+
+}
+
+void
+MidiFile::longToMidiBytes(std::ofstream* midiFile, unsigned long number)
+{
+ MidiByte upper1;
+ MidiByte lower1;
+ MidiByte upper2;
+ MidiByte lower2;
+
+ upper1 = (number & 0xff000000) >> 24;
+ lower1 = (number & 0x00ff0000) >> 16;
+ upper2 = (number & 0x0000ff00) >> 8;
+ lower2 = (number & 0x000000ff);
+
+ *midiFile << (MidiByte) upper1;
+ *midiFile << (MidiByte) lower1;
+ *midiFile << (MidiByte) upper2;
+ *midiFile << (MidiByte) lower2;
+
+}
+
+// Turn a delta time into a MIDI time - overlapping into
+// a maximum of four bytes using the MSB as the carry on
+// flag.
+//
+std::string
+MidiFile::longToVarBuffer(unsigned long number)
+{
+ std::string rS;
+
+ long inNumber = number;
+ long outNumber;
+
+ // get the lowest 7 bits of the number
+ outNumber = number & 0x7f;
+
+ // Shift and test and move the numbers
+ // on if we need them - setting the MSB
+ // as we go.
+ //
+ while ((inNumber >>= 7 ) > 0) {
+ outNumber <<= 8;
+ outNumber |= 0x80;
+ outNumber += (inNumber & 0x7f);
+ }
+
+ // Now move the converted number out onto the buffer
+ //
+ while (true) {
+ rS += (MidiByte)(outNumber & 0xff);
+ if (outNumber & 0x80)
+ outNumber >>= 8;
+ else
+ break;
+ }
+
+ return rS;
+}
+
+
+
+// Write out the MIDI file header
+//
+bool
+MidiFile::writeHeader(std::ofstream* midiFile)
+{
+ // Our identifying Header string
+ //
+ *midiFile << MIDI_FILE_HEADER.c_str();
+
+ // Write number of Bytes to follow
+ //
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) 0x06;
+
+ // Write File Format
+ //
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) m_format;
+
+ // Number of Tracks we're writing out
+ //
+ intToMidiBytes(midiFile, m_numberOfTracks);
+
+ // Timing Division
+ //
+ intToMidiBytes(midiFile, m_timingDivision);
+
+ return (true);
+}
+
+// Write a MIDI track to file
+//
+bool
+MidiFile::writeTrack(std::ofstream* midiFile, TrackId trackNumber)
+{
+ bool retOK = true;
+ MidiByte eventCode = 0;
+ MidiTrack::iterator midiEvent;
+
+ // First we write into the trackBuffer, then write it out to the
+ // file with it's accompanying length.
+ //
+ string trackBuffer;
+
+ long progressTotal = m_midiComposition[trackNumber].size();
+ long progressCount = 0;
+
+ for (midiEvent = m_midiComposition[trackNumber].begin();
+ midiEvent != m_midiComposition[trackNumber].end();
+ midiEvent++) {
+ // Write the time to the buffer in MIDI format
+ //
+ //
+ trackBuffer += longToVarBuffer((*midiEvent)->getTime());
+
+ if ((*midiEvent)->isMeta()) {
+ trackBuffer += MIDI_FILE_META_EVENT;
+ trackBuffer += (*midiEvent)->getMetaEventCode();
+
+ // Variable length number field
+ trackBuffer += longToVarBuffer((*midiEvent)->
+ getMetaMessage().length());
+
+ trackBuffer += (*midiEvent)->getMetaMessage();
+ } else {
+ // Send the normal event code (with encoded channel information)
+ //
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030531)
+ if (((*midiEvent)->getEventCode() != eventCode) ||
+ ((*midiEvent)->getEventCode() == MIDI_SYSTEM_EXCLUSIVE)) {
+ trackBuffer += (*midiEvent)->getEventCode();
+ eventCode = (*midiEvent)->getEventCode();
+ }
+
+ // Send the relevant data
+ //
+ switch ((*midiEvent)->getMessageType()) {
+ case MIDI_NOTE_ON:
+ case MIDI_NOTE_OFF:
+ case MIDI_POLY_AFTERTOUCH:
+ trackBuffer += (*midiEvent)->getData1();
+ trackBuffer += (*midiEvent)->getData2();
+ break;
+
+ case MIDI_CTRL_CHANGE:
+ trackBuffer += (*midiEvent)->getData1();
+ trackBuffer += (*midiEvent)->getData2();
+ break;
+
+ case MIDI_PROG_CHANGE:
+ trackBuffer += (*midiEvent)->getData1();
+ break;
+
+ case MIDI_CHNL_AFTERTOUCH:
+ trackBuffer += (*midiEvent)->getData1();
+ break;
+
+ case MIDI_PITCH_BEND:
+ trackBuffer += (*midiEvent)->getData1();
+ trackBuffer += (*midiEvent)->getData2();
+ break;
+
+ case MIDI_SYSTEM_EXCLUSIVE:
+
+ // write out message length
+ trackBuffer +=
+ longToVarBuffer((*midiEvent)->getMetaMessage().length());
+
+ // now the message
+ trackBuffer += (*midiEvent)->getMetaMessage();
+
+ break;
+
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::writeTrack()"
+ << " - cannot write unsupported MIDI event"
+ << endl;
+#endif
+
+ break;
+ }
+ }
+
+ // For the moment just keep the app updating until we work
+ // out a good way of accounting for this write.
+ //
+ ++progressCount;
+
+ if (progressCount % 500 == 0) {
+ emit setProgress(progressCount * 100 / progressTotal);
+ kapp->processEvents(500);
+ }
+ }
+
+ // Now we write the track - First the standard header..
+ //
+ *midiFile << MIDI_TRACK_HEADER.c_str();
+
+ // ..now the length of the buffer..
+ //
+ longToMidiBytes(midiFile, (long)trackBuffer.length());
+
+ // ..then the buffer itself..
+ //
+ *midiFile << trackBuffer;
+
+ return (retOK);
+}
+
+// Writes out a MIDI file from the internal Midi representation
+//
+bool
+MidiFile::write()
+{
+ bool retOK = true;
+
+ std::ofstream *midiFile =
+ new std::ofstream(m_fileName.c_str(), ios::out | ios::binary);
+
+
+ if (!(*midiFile)) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::write() - can't write file" << endl;
+#endif
+
+ m_format = MIDI_FILE_NOT_LOADED;
+ return false;
+ }
+
+ // Write out the Header
+ //
+ writeHeader(midiFile);
+
+ // And now the tracks
+ //
+ for (TrackId i = 0; i < m_numberOfTracks; i++ )
+ if (!writeTrack(midiFile, i))
+ retOK = false;
+
+ midiFile->close();
+
+ if (!retOK)
+ m_format = MIDI_FILE_NOT_LOADED;
+
+ return (retOK);
+}
+
+// Delete dead NOTE OFF and NOTE ON/Zero Velocty Events after
+// reading them and modifying their relevant NOTE ONs
+//
+bool
+MidiFile::consolidateNoteOffEvents(TrackId track)
+{
+ MidiTrack::iterator nOE, mE = m_midiComposition[track].begin();
+ bool notesOnTrack = false;
+ bool noteOffFound;
+
+ for (;mE != m_midiComposition[track].end(); mE++) {
+ if ((*mE)->getMessageType() == MIDI_NOTE_ON && (*mE)->getVelocity() > 0) {
+ // We've found a note - flag it
+ //
+ if (!notesOnTrack)
+ notesOnTrack = true;
+
+ noteOffFound = false;
+
+ for (nOE = mE; nOE != m_midiComposition[track].end(); nOE++) {
+ if (((*nOE)->getChannelNumber() == (*mE)->getChannelNumber()) &&
+ ((*nOE)->getPitch() == (*mE)->getPitch()) &&
+ ((*nOE)->getMessageType() == MIDI_NOTE_OFF ||
+ ((*nOE)->getMessageType() == MIDI_NOTE_ON &&
+ (*nOE)->getVelocity() == 0x00))) {
+ (*mE)->setDuration((*nOE)->getTime() - (*mE)->getTime());
+
+ delete *nOE;
+ m_midiComposition[track].erase(nOE);
+
+ noteOffFound = true;
+ break;
+ }
+ }
+
+ // If no matching NOTE OFF has been found then set
+ // Event duration to length of Segment
+ //
+ if (noteOffFound == false) {
+ --nOE; // avoid crash due to nOE == track.end()
+ (*mE)->setDuration((*nOE)->getTime() - (*mE)->getTime());
+ }
+ }
+ }
+
+ return notesOnTrack;
+}
+
+// Clear down the MidiFile Composition
+//
+void
+MidiFile::clearMidiComposition()
+{
+ for (MidiComposition::iterator ci = m_midiComposition.begin();
+ ci != m_midiComposition.end(); ++ci) {
+
+ //std::cerr << "MidiFile::clearMidiComposition: track " << ci->first << std::endl;
+
+ for (MidiTrack::iterator ti = ci->second.begin();
+ ti != ci->second.end(); ++ti) {
+ delete *ti;
+ }
+
+ ci->second.clear();
+ }
+
+ m_midiComposition.clear();
+ m_trackChannelMap.clear();
+}
+
+// Doesn't do anything yet - doesn't need to. We need to satisfy
+// the pure virtual function in the base class.
+//
+void
+MidiFile::close()
+{}
+
+
+
+}
+
+#include "MidiFile.moc"
diff --git a/src/sound/MidiFile.h b/src/sound/MidiFile.h
new file mode 100644
index 0000000..da97374
--- /dev/null
+++ b/src/sound/MidiFile.h
@@ -0,0 +1,173 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _ROSEGARDEN_MIDI_FILE_H_
+#define _ROSEGARDEN_MIDI_FILE_H_
+
+#include <fstream>
+#include <string>
+#include <list>
+#include <map>
+
+#include <qobject.h>
+
+#include "Midi.h"
+#include "MidiEvent.h"
+#include "Composition.h"
+#include "SoundFile.h"
+
+// Conversion class for Composition to and
+// from MIDI Files. Despite the fact you can reuse this
+// object it's probably safer just to create it for a
+// single way conversion and then throw it away (MIDI
+// to Composition conversion invalidates the internal
+// MIDI model).
+//
+// Derived from SoundFile but still had some features
+// in common with it which could theoretically be moved
+// up into the base for use in other derived classes.
+//
+// [rwb]
+//
+//
+
+namespace Rosegarden
+{
+
+// Our internal MIDI structure is just a list of MidiEvents.
+// We use a list and not a set because we want the order of
+// the events to be arbitrary until we explicitly sort them
+// (necessary when converting Composition absolute times to
+// MIDI delta times).
+//
+typedef std::vector<MidiEvent *> MidiTrack;
+typedef std::map<unsigned int, MidiTrack> MidiComposition;
+
+class Studio;
+
+class MidiFile : public QObject, public SoundFile
+{
+ Q_OBJECT
+public:
+
+ typedef enum
+ {
+ MIDI_SINGLE_TRACK_FILE = 0x00,
+ MIDI_SIMULTANEOUS_TRACK_FILE = 0x01,
+ MIDI_SEQUENTIAL_TRACK_FILE = 0x02,
+ MIDI_CONVERTED_TO_APPLICATION = 0xFE,
+ MIDI_FILE_NOT_LOADED = 0xFF
+ } MIDIFileFormatType;
+
+ typedef enum
+ {
+ CONVERT_REPLACE,
+ CONVERT_AUGMENT,
+ CONVERT_APPEND
+ } ConversionType;
+
+ MidiFile(Studio *studio);
+ MidiFile (const std::string &fn, Studio *studio);
+ ~MidiFile();
+
+ // Declare our virtuals
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ int timingDivision() { return m_timingDivision; }
+ MIDIFileFormatType format() { return m_format; }
+ unsigned int numberOfTracks() { return m_numberOfTracks; }
+ bool hasTimeChanges() { return m_containsTimeChanges; }
+
+ // If a file open or save failed
+ std::string getError() { return m_error; }
+
+ /**
+ * Convert a MIDI file to a Rosegarden composition. Return true
+ * for success.
+ */
+ bool convertToRosegarden(Composition &c, ConversionType type);
+
+ /**
+ * Convert a Rosegarden composition to MIDI format, storing the
+ * result internally for later writing.
+ */
+ void convertToMidi(Composition &comp);
+
+signals:
+ void setProgress(int);
+ void incrementProgress(int);
+
+private:
+
+ int m_timingDivision; // pulses per quarter note
+ MIDIFileFormatType m_format;
+ unsigned int m_numberOfTracks;
+ bool m_containsTimeChanges;
+
+ // Internal counters
+ //
+ long m_trackByteCount;
+ bool m_decrementCount;
+
+ // Internal MidiComposition
+ //
+ MidiComposition m_midiComposition;
+ std::map<int, int> m_trackChannelMap;
+
+ // Clear the m_midiComposition
+ //
+ void clearMidiComposition();
+
+ // Split the tasks up with these top level private methods
+ //
+ bool parseHeader(const std::string& midiHeader);
+ bool parseTrack(std::ifstream* midiFile, unsigned int &trackNum);
+ bool writeHeader(std::ofstream* midiFile);
+ bool writeTrack(std::ofstream* midiFile, unsigned int trackNum);
+
+ bool consolidateNoteOffEvents(TrackId track);
+
+ // Internal convenience functions
+ //
+ int midiBytesToInt(const std::string& bytes);
+ long midiBytesToLong(const std::string& bytes);
+ long getNumberFromMidiBytes(std::ifstream* midiFile, int firstByte = -1);
+ MidiByte getMidiByte(std::ifstream* midiFile);
+ std::string getMidiBytes(std::ifstream* midiFile,
+ unsigned long bytes);
+ bool skipToNextTrack(std::ifstream *midiFile);
+ void intToMidiBytes(std::ofstream* midiFile, int number);
+ void longToMidiBytes(std::ofstream* midiFile, unsigned long number);
+ std::string longToVarBuffer(unsigned long number);
+
+ // The pointer to the Studio for Instrument stuff
+ //
+ Studio *m_studio;
+
+ std::string m_error;
+};
+
+}
+
+#endif // _ROSEGARDEN_MIDI_FILE_H_
diff --git a/src/sound/MidiMapping.xml b/src/sound/MidiMapping.xml
new file mode 100644
index 0000000..13b7138
--- /dev/null
+++ b/src/sound/MidiMapping.xml
@@ -0,0 +1,133 @@
+
+<midi>
+ <bank id="0" name="General MIDI">
+ <program id="0" name="Acoustic Grand Piano"/>
+ <program id="1" name="Bright Acoustic Piano"/>
+ <program id="2" name="Electric Grand Piano"/>
+ <program id="3" name="Honky-tonk Piano"/>
+ <program id="4" name="Electric Piano 1"/>
+ <program id="5" name="Electric Piano 2"/>
+ <program id="6" name="Harpsichord"/>
+ <program id="7" name="Clavi"/>
+ <program id="8" name="Celesta"/>
+ <program id="9" name="Glockenspiel"/>
+ <program id="10" name="Music Box"/>
+ <program id="11" name="Vibraphone"/>
+ <program id="12" name="Marimba"/>
+ <program id="13" name="Xylophone"/>
+ <program id="14" name="Tubular Bells"/>
+ <program id="15" name="Dulcimer"/>
+ <program id="16" name="Drawbar Organ"/>
+ <program id="17" name="Percussive Organ"/>
+ <program id="18" name="Rock Organ"/>
+ <program id="19" name="Church Organ"/>
+ <program id="20" name="Reed Organ"/>
+ <program id="21" name="Accordion"/>
+ <program id="22" name="Harmonica"/>
+ <program id="23" name="Tango Accordion"/>
+ <program id="24" name="Acoustic Guitar (nylon)"/>
+ <program id="25" name="Acoustic Guitar (steel)"/>
+ <program id="26" name="Electric Guitar (jazz)"/>
+ <program id="27" name="Electric Guitar (clean)"/>
+ <program id="28" name="Electric Guitar (muted)"/>
+ <program id="29" name="Overdriven Guitar"/>
+ <program id="30" name="Distortion Guitar"/>
+ <program id="31" name="Guitar harmonics"/>
+ <program id="32" name="Acoustic Bass"/>
+ <program id="33" name="Fingered Bass"/>
+ <program id="34" name="Picked Bass"/>
+ <program id="35" name="Fretless Bass"/>
+ <program id="36" name="Slap Bass 1"/>
+ <program id="37" name="Slap Bass 2"/>
+ <program id="38" name="Synth Bass 1"/>
+ <program id="39" name="Synth Bass 2"/>
+ <program id="40" name="Violin"/>
+ <program id="41" name="Viola"/>
+ <program id="42" name="Cello"/>
+ <program id="43" name="Contrabass"/>
+ <program id="44" name="Tremolo Strings"/>
+ <program id="45" name="Pizzicato Strings"/>
+ <program id="46" name="Orchestral Harp"/>
+ <program id="47" name="Timpani"/>
+ <program id="48" name="String Ensemble 1"/>
+ <program id="49" name="String Ensemble 2"/>
+ <program id="50" name="SynthStrings 1"/>
+ <program id="51" name="SynthStrings 2"/>
+ <program id="52" name="Choir Aahs"/>
+ <program id="53" name="Voice Oohs"/>
+ <program id="54" name="Synth Voice"/>
+ <program id="55" name="Orchestra Hit"/>
+ <program id="56" name="Trumpet"/>
+ <program id="57" name="Trombone"/>
+ <program id="58" name="Tuba"/>
+ <program id="59" name="Muted Trumpet"/>
+ <program id="60" name="French Horn"/>
+ <program id="61" name="Brass Section"/>
+ <program id="62" name="SynthBrass 1"/>
+ <program id="63" name="SynthBrass 2"/>
+ <program id="64" name="Soprano Sax"/>
+ <program id="65" name="Alto Sax"/>
+ <program id="66" name="Tenor Sax"/>
+ <program id="67" name="Baritone Sax"/>
+ <program id="68" name="Oboe"/>
+ <program id="69" name="English Horn"/>
+ <program id="70" name="Bassoon"/>
+ <program id="71" name="Clarinet"/>
+ <program id="72" name="Piccolo"/>
+ <program id="73" name="Flute"/>
+ <program id="74" name="Recorder"/>
+ <program id="75" name="Pan Flute"/>
+ <program id="76" name="Blown Bottle"/>
+ <program id="77" name="Shakuhachi"/>
+ <program id="78" name="Whistle"/>
+ <program id="79" name="Ocarina"/>
+ <program id="80" name="Lead 1 (square)"/>
+ <program id="81" name="Lead 2 (sawtooth)"/>
+ <program id="82" name="Lead 3 (calliope)"/>
+ <program id="83" name="Lead 4 (chiff)"/>
+ <program id="84" name="Lead 5 (charang)"/>
+ <program id="85" name="Lead 6 (voice)"/>
+ <program id="86" name="Lead 7 (fifths)"/>
+ <program id="87" name="Lead 8 (bass + lead)"/>
+ <program id="88" name="Pad 1 (new age)"/>
+ <program id="89" name="Pad 2 (warm)"/>
+ <program id="90" name="Pad 3 (polysynth)"/>
+ <program id="91" name="Pad 4 (choir)"/>
+ <program id="92" name="Pad 5 (bowed)"/>
+ <program id="93" name="Pad 6 (metallic)"/>
+ <program id="94" name="Pad 7 (halo)"/>
+ <program id="95" name="Pad 8 (sweep)"/>
+ <program id="96" name="FX 1 (rain)"/>
+ <program id="97" name="FX 2 (soundtrack)"/>
+ <program id="98" name="FX 3 (crystal)"/>
+ <program id="99" name="FX 4 (atmosphere)"/>
+ <program id="100" name="FX 5 (brightness)"/>
+ <program id="101" name="FX 6 (goblins)"/>
+ <program id="102" name="FX 7 (echoes)"/>
+ <program id="103" name="FX 8 (sci-fi)"/>
+ <program id="104" name="Sitar"/>
+ <program id="105" name="Banjo"/>
+ <program id="106" name="Shamisen"/>
+ <program id="107" name="Koto"/>
+ <program id="108" name="Kalimba"/>
+ <program id="109" name="Bag pipe"/>
+ <program id="110" name="Fiddle"/>
+ <program id="111" name="Shanai"/>
+ <program id="112" name="Tinkle Bell"/>
+ <program id="113" name="Agogo"/>
+ <program id="114" name="Steel Drums"/>
+ <program id="115" name="Woodblock"/>
+ <program id="116" name="Taiko Drum"/>
+ <program id="117" name="Melodic Tom"/>
+ <program id="118" name="Synth Drum"/>
+ <program id="119" name="Reverse Cymbal"/>
+ <program id="120" name="Guitar Fret Noise"/>
+ <program id="121" name="Breath Noise"/>
+ <program id="122" name="Seashore"/>
+ <program id="123" name="Bird Tweet"/>
+ <program id="124" name="Telephone Ring"/>
+ <program id="125" name="Helicopter"/>
+ <program id="126" name="Applause"/>
+ <program id="127" name="Gunshot"/>
+ </bank>
+</midi>
diff --git a/src/sound/PeakFile.cpp b/src/sound/PeakFile.cpp
new file mode 100644
index 0000000..8881114
--- /dev/null
+++ b/src/sound/PeakFile.cpp
@@ -0,0 +1,1033 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /*
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <cmath>
+#include <cstdlib>
+#include <kapplication.h>
+
+#include <qdatetime.h>
+#include <qstringlist.h>
+#include <qpalette.h>
+#include <kapp.h>
+
+#include "PeakFile.h"
+#include "AudioFile.h"
+#include "Profiler.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_PEAKFILE 1
+//#define DEBUG_PEAKFILE_BRIEF 1
+//#define DEBUG_PEAKFILE_CACHE 1
+
+#ifdef DEBUG_PEAKFILE
+#define DEBUG_PEAKFILE_BRIEF 1
+#endif
+
+namespace Rosegarden
+{
+
+PeakFile::PeakFile(AudioFile *audioFile):
+ SoundFile(audioFile->getPeakFilename()),
+ m_audioFile(audioFile),
+ m_version( -1), // -1 defines new file - start at 0
+ m_format(1), // default is 8-bit peak format
+ m_pointsPerValue(0),
+ m_blockSize(256), // default block size is 256 samples
+ m_channels(0),
+ m_numberOfPeaks(0),
+ m_positionPeakOfPeaks(0),
+ m_offsetToPeaks(0),
+ m_modificationTime(QDate(1970, 1, 1), QTime(0, 0, 0)),
+ m_chunkStartPosition(0),
+ m_lastPreviewStartTime(0, 0),
+ m_lastPreviewEndTime(0, 0),
+ m_lastPreviewWidth( -1),
+ m_lastPreviewShowMinima(false)
+{}
+
+PeakFile::~PeakFile()
+{}
+
+bool
+PeakFile::open()
+{
+ // Set the file size
+ //
+ QFileInfo info(QString(m_fileName.c_str()));
+ m_fileSize = info.size();
+
+ // If we're already open then don't open again
+ //
+ if (m_inFile && m_inFile->is_open())
+ return true;
+
+ // Open
+ //
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+ // Check we're open
+ //
+ if (!(*m_inFile))
+ return false;
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException s) {
+
+#ifdef DEBUG_PEAKFILE
+ cerr << "PeakFile::open - EXCEPTION \"" << s.getMessage() << "\""
+ << endl;
+#endif
+
+ return false;
+ }
+
+ return true;
+}
+
+void
+PeakFile::parseHeader()
+{
+ if (!(*m_inFile))
+ return ;
+
+ m_inFile->seekg(0, std::ios::beg);
+
+ // get full header length
+ //
+ std::string header = getBytes(128);
+
+#if (__GNUC__ < 3)
+
+ if (header.compare(AUDIO_BWF_PEAK_ID, 0, 4) != 0)
+#else
+
+ if (header.compare(0, 4, AUDIO_BWF_PEAK_ID) != 0)
+#endif
+
+ {
+ throw(BadSoundFileException(m_fileName, "PeakFile::parseHeader - can't find LEVL identifier"));
+ }
+
+ int length = getIntegerFromLittleEndian(header.substr(4, 4));
+
+ // Get the length of the header minus the first 8 bytes
+ //
+ if (length == 0)
+ throw(BadSoundFileException(m_fileName, "PeakFile::parseHeader - can't get header length"));
+
+ // Get the file information
+ //
+ m_version = getIntegerFromLittleEndian(header.substr(8, 4));
+ m_format = getIntegerFromLittleEndian(header.substr(12, 4));
+ m_pointsPerValue = getIntegerFromLittleEndian(header.substr(16, 4));
+ m_blockSize = getIntegerFromLittleEndian(header.substr(20, 4));
+ m_channels = getIntegerFromLittleEndian(header.substr(24, 4));
+ m_numberOfPeaks = getIntegerFromLittleEndian(header.substr(28, 4));
+ m_positionPeakOfPeaks = getIntegerFromLittleEndian(header.substr(32, 4));
+
+ // Read in date string and convert it up to QDateTime
+ //
+ QString dateString = QString(header.substr(40, 28).c_str());
+
+ QStringList dateTime = QStringList::split(":", dateString);
+
+ m_modificationTime.setDate(QDate(dateTime[0].toInt(),
+ dateTime[1].toInt(),
+ dateTime[2].toInt()));
+
+ m_modificationTime.setTime(QTime(dateTime[3].toInt(),
+ dateTime[4].toInt(),
+ dateTime[5].toInt(),
+ dateTime[6].toInt()));
+
+ //printStats();
+
+}
+
+void
+PeakFile::printStats()
+{
+ cout << endl;
+ cout << "STATS for PeakFile \"" << m_fileName << "\"" << endl
+ << "-----" << endl << endl;
+
+ cout << " VERSION = " << m_version << endl
+ << " FORMAT = " << m_format << endl
+ << " BYTES/VALUE = " << m_pointsPerValue << endl
+ << " BLOCKSIZE = " << m_blockSize << endl
+ << " CHANNELS = " << m_channels << endl
+ << " PEAK FRAMES = " << m_numberOfPeaks << endl
+ << " PEAK OF PKS = " << m_positionPeakOfPeaks << endl
+ << endl;
+
+ cout << "DATE" << endl
+ << "----" << endl << endl
+ << " YEAR = " << m_modificationTime.date().year() << endl
+ << " MONTH = " << m_modificationTime.date().month() << endl
+ << " DAY = " << m_modificationTime.date().day() << endl
+ << " HOUR = " << m_modificationTime.time().hour() << endl
+ << " MINUTE = " << m_modificationTime.time().minute()
+ << endl
+ << " SECOND = " << m_modificationTime.time().second()
+ << endl
+ << " MSEC = " << m_modificationTime.time().msec()
+ << endl << endl;
+}
+
+bool
+PeakFile::write()
+{
+ return write(5); // default update every 5%
+}
+
+bool
+PeakFile::write(unsigned short updatePercentage)
+{
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+ // Attempt to open AudioFile so that we can extract sample data
+ // for preview file generation
+ //
+ try {
+ if (!m_audioFile->open())
+ return false;
+ } catch (BadSoundFileException e) {
+#ifdef DEBUG_PEAKFILE
+ std::cerr << "PeakFile::write - \"" << e.getMessage() << "\"" << std::endl;
+#endif
+
+ return false;
+ }
+
+ // create and test that we've made it
+ m_outFile = new std::ofstream(m_fileName.c_str(),
+ std::ios::out | std::ios::binary);
+ if (!(*m_outFile))
+ return false;
+
+ // write out the header
+ writeHeader(m_outFile);
+
+ // and now the peak values
+ writePeaks(updatePercentage, m_outFile);
+
+ return true;
+}
+
+// Close the peak file and tidy up
+//
+void
+PeakFile::close()
+{
+ // Close any input file handle
+ //
+ if (m_inFile && m_inFile->is_open()) {
+ m_inFile->close();
+ delete m_inFile;
+ m_inFile = 0;
+ }
+
+ if (m_outFile == 0)
+ return ;
+
+ // Seek to start of chunk
+ //
+ m_outFile->seekp(m_chunkStartPosition, std::ios::beg);
+
+ // Seek to size field at set it
+ //
+ m_outFile->seekp(4, std::ios::cur);
+ putBytes(m_outFile, getLittleEndianFromInteger(m_bodyBytes + 120, 4));
+
+ // Seek to format and set it (m_format is only set at the
+ // end of writePeaks()
+ //
+ m_outFile->seekp(4, std::ios::cur);
+ putBytes(m_outFile, getLittleEndianFromInteger(m_format, 4));
+
+ // Seek to number of peak frames and write value
+ //
+ m_outFile->seekp(12, std::ios::cur);
+ putBytes(m_outFile,
+ getLittleEndianFromInteger(m_numberOfPeaks, 4));
+
+ // Peak of peaks
+ //
+ putBytes(m_outFile,
+ getLittleEndianFromInteger(m_positionPeakOfPeaks, 4));
+
+ // Seek to date field
+ //
+ m_outFile->seekp(4, std::ios::cur);
+
+ // Set modification time to now
+ //
+ m_modificationTime = m_modificationTime.currentDateTime();
+
+ QString fDate;
+ fDate.sprintf("%04d:%02d:%02d:%02d:%02d:%02d:%03d",
+ m_modificationTime.date().year(),
+ m_modificationTime.date().month(),
+ m_modificationTime.date().day(),
+ m_modificationTime.time().hour(),
+ m_modificationTime.time().minute(),
+ m_modificationTime.time().second(),
+ m_modificationTime.time().msec());
+
+ std::string dateString(fDate.data());
+
+ // Pad with spaces to make up to 28 bytes long and output
+ //
+ dateString += " ";
+ putBytes(m_outFile, dateString);
+
+ // Ok, now close and tidy up
+ //
+ m_outFile->close();
+ delete m_outFile;
+ m_outFile = 0;
+}
+
+// If the audio file is more recently modified that the modification time
+// on this peak file then we're invalid. The action to rectify this is
+// usually to regenerate the peak data.
+//
+bool
+PeakFile::isValid()
+{
+ if (m_audioFile->getModificationDateTime() > m_modificationTime)
+ return false;
+
+ return true;
+}
+
+bool
+PeakFile::writeToHandle(std::ofstream *file,
+ unsigned short /*updatePercentage*/)
+{
+ // Remember the position where we pass in the ofstream pointer
+ // so we can return there to write close() information.
+ //
+ m_chunkStartPosition = file->tellp();
+
+ return false;
+}
+
+// Build up a header string and then pump it out to the file handle
+//
+void
+PeakFile::writeHeader(std::ofstream *file)
+{
+ if (!file || !(*file))
+ return ;
+
+ std::string header;
+
+ // The "levl" identifer for this chunk
+ //
+ header += AUDIO_BWF_PEAK_ID;
+
+ // Add a four byte version of the size of the header chunk (120
+ // bytes from this point onwards)
+ //
+ header += getLittleEndianFromInteger(120, 4);
+
+ // A four byte version number (incremented every time)
+ //
+ header += getLittleEndianFromInteger(++m_version, 4);
+
+ // Format of the peak points - 1 = unsigned char
+ // 2 = unsigned short
+ //
+ header += getLittleEndianFromInteger(m_format, 4);
+
+ // Points per value - 1 = 1 peak and has vertical about x-axis
+ // 2 = 2 peaks so differs above and below x-axis
+ //
+ // .. hardcode to 2 for the mo
+ m_pointsPerValue = 2;
+ header += getLittleEndianFromInteger(m_pointsPerValue, 4);
+
+ // Block size - default and recommended is 256
+ //
+ header += getLittleEndianFromInteger(m_blockSize, 4);
+
+ // Set channels up if they're currently empty
+ //
+ if (m_channels == 0 && m_audioFile)
+ m_channels = m_audioFile->getChannels();
+
+ // Peak channels - same as AudioFile channels
+ //
+ header += getLittleEndianFromInteger(m_channels, 4);
+
+ // Number of peak frames - we write this at close() and so
+ // for the moment put spacing 0's in.
+ header += getLittleEndianFromInteger(0, 4);
+
+ // Position of peak of peaks - written at close()
+ //
+ header += getLittleEndianFromInteger(0, 4);
+
+ // Offset to start of peaks - usually the total size of this header
+ //
+ header += getLittleEndianFromInteger(128, 4);
+
+ // Creation timestamp - fill in on close() so just use spacing
+ // of 28 bytes for the moment.
+ //
+ header += getLittleEndianFromInteger(0, 28);
+
+ // reserved space - 60 bytes
+ header += getLittleEndianFromInteger(0, 60);
+
+ //cout << "HEADER LENGTH = " << header.length() << endl;
+
+ // write out the header
+ //
+ putBytes(file, header);
+}
+
+bool
+PeakFile::scanToPeak(int peak)
+{
+ if (!m_inFile)
+ return false;
+
+ if (!m_inFile->is_open())
+ return false;
+
+ // Scan to start of chunk and then seek to peak number
+ //
+ ssize_t pos = (ssize_t)m_chunkStartPosition + 128 +
+ peak * m_format * m_channels * m_pointsPerValue;
+
+ ssize_t off = pos - m_inFile->tellg();
+
+ if (off == 0) {
+ return true;
+ } else if (off < 0) {
+ // std::cerr << "PeakFile::scanToPeak: warning: seeking backwards for peak " << peak << " (" << m_inFile->tellg() << " -> " << pos << ")" << std::endl;
+ m_inFile->seekg(pos);
+ } else {
+ m_inFile->seekg(off, std::ios::cur);
+ }
+
+ // Ensure we re-read the input buffer if we're
+ // doing buffered reads as it's now meaningless
+ //
+ m_loseBuffer = true;
+
+ if (m_inFile->eof()) {
+ m_inFile->clear();
+ return false;
+ }
+
+ return true;
+}
+
+bool
+PeakFile::scanForward(int numberOfPeaks)
+{
+ if (!m_inFile)
+ return false;
+
+ if (!m_inFile->is_open())
+ return false;
+
+ // Seek forward and number of peaks
+ //
+ m_inFile->seekg(numberOfPeaks * m_format * m_channels * m_pointsPerValue,
+ std::ios::cur);
+
+ // Ensure we re-read the input buffer
+ m_loseBuffer = true;
+
+ if (m_inFile->eof()) {
+ m_inFile->clear();
+ return false;
+ }
+
+ return true;
+}
+
+
+void
+PeakFile::writePeaks(unsigned short /*updatePercentage*/,
+ std::ofstream *file)
+{
+ if (!file || !(*file))
+ return ;
+ m_keepProcessing = true;
+
+#ifdef DEBUG_PEAKFILE
+
+ cout << "PeakFile::writePeaks - calculating peaks" << endl;
+#endif
+
+ // Scan to beginning of audio data
+ m_audioFile->scanTo(RealTime(0, 0));
+
+ // Store our samples
+ //
+ std::vector<std::pair<int, int> > channelPeaks;
+ std::string samples;
+ unsigned char *samplePtr;
+
+ int sampleValue;
+ int sampleMax = 0 ;
+ int sampleFrameCount = 0;
+
+ int channels = m_audioFile->getChannels();
+ int bytes = m_audioFile->getBitsPerSample() / 8;
+
+ m_format = bytes;
+ if (bytes == 3 || bytes == 4) // 24-bit PCM or 32-bit float
+ m_format = 2; // write 16-bit PCM instead
+
+ // for the progress dialog
+ unsigned int apprxTotalBytes = m_audioFile->getSize();
+ unsigned int byteCount = 0;
+
+ for (int i = 0; i < channels; i++)
+ channelPeaks.push_back(std::pair<int, int>());
+
+ // clear down info
+ m_numberOfPeaks = 0;
+ m_bodyBytes = 0;
+ m_positionPeakOfPeaks = 0;
+
+ while (m_keepProcessing) {
+ try {
+ samples = m_audioFile->
+ getBytes(m_blockSize * channels * bytes);
+ } catch (BadSoundFileException e) {
+ std::cerr << "PeakFile::writePeaks: " << e.getMessage()
+ << std::endl;
+ break;
+ }
+
+ // If no bytes or less than the total number of bytes are returned
+ // then break out
+ //
+ if (samples.length() == 0 ||
+ samples.length() < (m_blockSize * m_audioFile->getChannels()
+ * bytes))
+ break;
+
+ byteCount += samples.length();
+
+ emit setProgress((int)(double(byteCount) /
+ double(apprxTotalBytes) * 100.0));
+ kapp->processEvents();
+
+ samplePtr = (unsigned char *)samples.c_str();
+
+ for (int i = 0; i < m_blockSize; i++) {
+ for (unsigned int ch = 0; ch < m_audioFile->getChannels(); ch++) {
+ // Single byte format values range from 0-255 and then
+ // shifted down about the x-axis. Double byte and above
+ // are already centred about x-axis.
+ //
+ if (bytes == 1) {
+ // get value
+ sampleValue = int(*samplePtr) - 128;
+ samplePtr++;
+ } else if (bytes == 2) {
+ unsigned char b2 = samplePtr[0];
+ unsigned char b1 = samplePtr[1];
+ unsigned int bits = (b1 << 8) + b2;
+ sampleValue = (short)bits;
+ samplePtr += 2;
+ } else if (bytes == 3) {
+ unsigned char b3 = samplePtr[0];
+ unsigned char b2 = samplePtr[1];
+ unsigned char b1 = samplePtr[2];
+ unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8);
+
+ // write out as 16-bit (m_format == 2)
+ sampleValue = int(bits) / 65536;
+
+ samplePtr += 3;
+ } else if (bytes == 4) // IEEE float (enforced by RIFFAudioFile)
+ {
+ // write out as 16-bit (m_format == 2)
+ float val = *(float *)samplePtr;
+ sampleValue = (int)(32767.0 * val);
+ samplePtr += 4;
+ } else {
+ throw(BadSoundFileException(m_fileName, "PeakFile::writePeaks - unsupported bit depth"));
+ }
+
+ // First time for each channel
+ //
+ if (i == 0) {
+ channelPeaks[ch].first = sampleValue;
+ channelPeaks[ch].second = sampleValue;
+ } else {
+ // Compare and store
+ //
+ if (sampleValue > channelPeaks[ch].first)
+ channelPeaks[ch].first = sampleValue;
+
+ if (sampleValue < channelPeaks[ch].second)
+ channelPeaks[ch].second = sampleValue;
+ }
+
+ // Store peak of peaks if it fits
+ //
+ if (abs(sampleValue) > sampleMax) {
+ sampleMax = abs(sampleValue);
+ m_positionPeakOfPeaks = sampleFrameCount;
+ }
+ }
+
+ // for peak of peaks as well as frame count
+ sampleFrameCount++;
+ }
+
+ // Write absolute peak data in channel order
+ //
+ for (unsigned int i = 0; i < m_audioFile->getChannels(); i++) {
+ putBytes(file, getLittleEndianFromInteger(channelPeaks[i].first,
+ m_format));
+ putBytes(file, getLittleEndianFromInteger(channelPeaks[i].second,
+ m_format));
+ m_bodyBytes += m_format * 2;
+ }
+
+ // increment number of peak frames
+ m_numberOfPeaks++;
+ }
+
+#ifdef DEBUG_PEAKFILE
+ cout << "PeakFile::writePeaks - "
+ << "completed peaks" << endl;
+#endif
+
+}
+
+// Get a normalised vector for the preview at a given horizontal resolution.
+// We return a value for each channel and if returnLow is set we also return
+// an interleaved low value for each channel.
+//
+//
+std::vector<float>
+PeakFile::getPreview(const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima)
+{
+#ifdef DEBUG_PEAKFILE_BRIEF
+ std::cout << "PeakFile::getPreview - "
+ << "startTime = " << startTime
+ << ", endTime = " << endTime
+ << ", width = " << width
+ << ", showMinima = " << showMinima << std::endl;
+#endif
+
+ if (getSize() == 0) {
+ std::cout << "PeakFile::getPreview - PeakFile size == 0" << std::endl;
+ return std::vector<float>();
+ }
+
+ // Regenerate cache on these conditions
+ //
+ if (!m_peakCache.length()) {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cerr << "PeakFile::getPreview - no peak cache" << std::endl;
+#endif
+
+ if (getSize() < (256 *1024)) // if less than 256K PeakFile
+ {
+ // Scan to start of peak data
+ scanToPeak(0);
+ try
+ {
+ m_peakCache = getBytes(m_inFile, getSize() - 128);
+ } catch (BadSoundFileException e)
+ {
+ std::cerr << "PeakFile::getPreview: " << e.getMessage()
+ << std::endl;
+ }
+
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - generated peak cache - "
+ << "size = " << m_peakCache.length() << std::endl;
+#endif
+
+ } else {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - file size = " << getSize()
+ << ", not generating cache" << std::endl;
+#endif
+
+ }
+ }
+
+ // Check to see if we hit the "lastPreview" cache by comparing the last
+ // query parameters we used.
+ //
+ if (startTime == m_lastPreviewStartTime && endTime == m_lastPreviewEndTime
+ && width == m_lastPreviewWidth && showMinima == m_lastPreviewShowMinima) {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - hit last preview cache" << std::endl;
+#endif
+
+ return m_lastPreviewCache;
+ } else {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - last preview " << m_lastPreviewStartTime
+ << " -> " << m_lastPreviewEndTime << ", w " << m_lastPreviewWidth << "; this " << startTime << " -> " << endTime << ", w " << width << std::endl;
+#endif
+
+ }
+
+ // Clear the cache - we need to regenerate it
+ //
+ m_lastPreviewCache.clear();
+
+ int startPeak = getPeak(startTime);
+ int endPeak = getPeak(endTime);
+
+ // Sanity check
+ if (startPeak > endPeak)
+ return m_lastPreviewCache;
+
+ // Actual possible sample length in RealTime
+ //
+ double step = double(endPeak - startPeak) / double(width);
+ std::string peakData;
+ int peakNumber;
+
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "PeakFile::getPreview - getting preview for \""
+ << m_audioFile->getFilename() << "\"" << endl;
+#endif
+
+ // Get a divisor
+ //
+ float divisor = 0.0f;
+ switch (m_format) {
+ case 1:
+ divisor = SAMPLE_MAX_8BIT;
+ break;
+
+ case 2:
+ divisor = SAMPLE_MAX_16BIT;
+ break;
+
+ default:
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "PeakFile::getPreview - "
+ << "unsupported peak length format (" << m_format << ")"
+ << endl;
+#endif
+
+ return m_lastPreviewCache;
+ }
+
+ float *hiValues = new float[m_channels];
+ float *loValues = new float[m_channels];
+
+ for (int i = 0; i < width; i++) {
+
+ peakNumber = startPeak + int(double(i) * step);
+ int nextPeakNumber = startPeak + int(double(i + 1) * step);
+
+ // Seek to value
+ //
+ if (!m_peakCache.length()) {
+
+ if (scanToPeak(peakNumber) == false) {
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview: scanToPeak(" << peakNumber << ") failed" << std::endl;
+#endif
+
+ m_lastPreviewCache.push_back(0.0f);
+ }
+ }
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview: step is " << step << ", format * pointsPerValue * chans is " << (m_format * m_pointsPerValue * m_channels) << std::endl;
+ std::cout << "i = " << i << ", peakNumber = " << peakNumber << ", nextPeakNumber = " << nextPeakNumber << std::endl;
+#endif
+
+ for (int ch = 0; ch < m_channels; ch++) {
+ hiValues[ch] = 0.0f;
+ loValues[ch] = 0.0f;
+ }
+
+ // Get peak value over channels
+ //
+ for (int k = 0; peakNumber < nextPeakNumber; ++k) {
+
+ for (int ch = 0; ch < m_channels; ch++) {
+
+ if (!m_peakCache.length()) {
+
+ try {
+ peakData = getBytes(m_inFile, m_format * m_pointsPerValue);
+ } catch (BadSoundFileException e) {
+ // Problem with the get - probably an EOF
+ // return the results so far.
+ //
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview - \"" << e.getMessage() << "\"\n"
+ << endl;
+#endif
+
+ goto done;
+ }
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview - "
+ << "read from file" << std::endl;
+#endif
+
+ } else {
+
+ int valueNum = peakNumber * m_channels + ch;
+ int charNum = valueNum * m_format * m_pointsPerValue;
+ int charLength = m_format * m_pointsPerValue;
+
+ // Get peak value from the cached string if
+ // the value is valid.
+ //
+ if (charNum + charLength <= m_peakCache.length()) {
+ peakData = m_peakCache.substr(charNum, charLength);
+#ifdef DEBUG_PEAKFILE
+
+ std::cout << "PeakFile::getPreview - "
+ << "hit peakCache" << std::endl;
+#endif
+
+ }
+ }
+
+
+ if (peakData.length() != (unsigned int)(m_format *
+ m_pointsPerValue)) {
+ // We didn't get the whole peak block - return what
+ // we've got so far
+ //
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview - "
+ << "failed to get complete peak block"
+ << endl;
+#endif
+
+ goto done;
+ }
+
+ int intDivisor = int(divisor);
+ int inValue =
+ getIntegerFromLittleEndian(peakData.substr(0, m_format));
+
+ while (inValue > intDivisor) {
+ inValue -= (1 << (m_format * 8));
+ }
+
+#ifdef DEBUG_PEAKFILE
+ std::cout << "found potential hivalue " << inValue << std::endl;
+#endif
+
+ if (k == 0 || inValue > hiValues[ch]) {
+ hiValues[ch] = float(inValue);
+ }
+
+ if (m_pointsPerValue == 2) {
+
+ inValue =
+ getIntegerFromLittleEndian(
+ peakData.substr(m_format, m_format));
+
+ while (inValue > intDivisor) {
+ inValue -= (1 << (m_format * 8));
+ }
+
+ if (k == 0 || inValue < loValues[ch]) {
+ loValues[ch] = inValue;
+ }
+ }
+ }
+
+ ++peakNumber;
+ }
+
+ for (int ch = 0; ch < m_channels; ++ch) {
+
+ float value = hiValues[ch] / divisor;
+
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "VALUE = " << hiValues[ch] / divisor << std::endl;
+#endif
+
+ if (showMinima) {
+ m_lastPreviewCache.push_back(loValues[ch] / divisor);
+ } else {
+ value = fabs(value);
+ if (m_pointsPerValue == 2) {
+ value = std::max(value, fabsf(loValues[ch] / divisor));
+ }
+ m_lastPreviewCache.push_back(value);
+ }
+ }
+ }
+
+done:
+ resetStream();
+ delete[] hiValues;
+ delete[] loValues;
+
+ // We have a good preview in the cache so store our parameters
+ //
+ m_lastPreviewStartTime = startTime;
+ m_lastPreviewEndTime = endTime;
+ m_lastPreviewWidth = width;
+ m_lastPreviewShowMinima = showMinima;
+
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "Returning " << m_lastPreviewCache.size() << " items" << std::endl;
+#endif
+
+ return m_lastPreviewCache;
+}
+
+int
+PeakFile::getPeak(const RealTime &time)
+{
+ double frames = ((time.sec * 1000000.0) + time.usec()) *
+ m_audioFile->getSampleRate() / 1000000.0;
+ return int(frames / double(m_blockSize));
+}
+
+RealTime
+PeakFile::getTime(int peak)
+{
+ int usecs = int((double)peak * (double)m_blockSize *
+ double(1000000.0) / double(m_audioFile->getSampleRate()));
+ return RealTime(usecs / 1000000, (usecs % 1000000) * 1000);
+}
+
+// Get pairs of split points for areas that exceed a percentage
+// threshold
+//
+std::vector<SplitPointPair>
+PeakFile::getSplitPoints(const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minLength)
+{
+ std::vector<SplitPointPair> points;
+ std::string peakData;
+
+ int startPeak = getPeak(startTime);
+ int endPeak = getPeak(endTime);
+
+ if (endPeak < startPeak)
+ return std::vector<SplitPointPair>();
+
+ scanToPeak(startPeak);
+
+ float divisor = 0.0f;
+ switch (m_format) {
+ case 1:
+ divisor = SAMPLE_MAX_8BIT;
+ break;
+
+ case 2:
+ divisor = SAMPLE_MAX_16BIT;
+ break;
+
+ default:
+ return points;
+ }
+
+ float value;
+ float fThreshold = float(threshold) / 100.0;
+ bool belowThreshold = true;
+ RealTime startSplit = RealTime::zeroTime;
+ bool inSplit = false;
+
+ for (int i = startPeak; i < endPeak; i++) {
+ value = 0.0;
+
+ for (int ch = 0; ch < m_channels; ch++) {
+ try {
+ peakData = getBytes(m_inFile, m_format * m_pointsPerValue);
+ } catch (BadSoundFileException e) {
+ std::cerr << "PeakFile::getSplitPoints: "
+ << e.getMessage() << std::endl;
+ break;
+ }
+
+ if (peakData.length() == (unsigned int)(m_format *
+ m_pointsPerValue)) {
+ int peakValue =
+ getIntegerFromLittleEndian(peakData.substr(0, m_format));
+
+ value += fabs(float(peakValue) / divisor);
+ }
+ }
+
+ value /= float(m_channels);
+
+ if (belowThreshold) {
+ if (value > fThreshold) {
+ startSplit = getTime(i);
+ inSplit = true;
+ belowThreshold = false;
+ }
+ } else {
+ if (value < fThreshold && getTime(i) - startSplit > minLength) {
+ // insert values
+ if (inSplit) {
+ points.push_back(SplitPointPair(startSplit, getTime(i)));
+ }
+ inSplit = false;
+ belowThreshold = true;
+ }
+ }
+ }
+
+ // if we've got a split point open the close it
+ if (inSplit) {
+ points.push_back(SplitPointPair(startSplit,
+ getTime(endPeak)));
+ }
+
+ return points;
+}
+
+
+}
+
+
+#include "PeakFile.moc"
diff --git a/src/sound/PeakFile.h b/src/sound/PeakFile.h
new file mode 100644
index 0000000..26ef71c
--- /dev/null
+++ b/src/sound/PeakFile.h
@@ -0,0 +1,196 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /*
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <vector>
+
+#include <qobject.h>
+#include <qdatetime.h>
+
+#include "SoundFile.h"
+#include "RealTime.h"
+
+#ifndef _PEAKFILE_H_
+#define _PEAKFILE_H_
+
+// A PeakFile is generated to the BWF Supplement 3 Peak Envelope Chunk
+// format as defined here:
+//
+// http://www.ebu.ch/pmc_bwf.html
+//
+// To comply with BWF format files this chunk can be embedded into
+// the sample file itself (writeToHandle()) or used to generate an
+// external peak file (write()). At the moment the only type of file
+// with an embedded peak chunk is the BWF file itself.
+//
+//
+
+
+
+namespace Rosegarden
+{
+
+class AudioFile;
+
+
+typedef std::pair<RealTime, RealTime> SplitPointPair;
+
+class PeakFile : public QObject, public SoundFile
+{
+ Q_OBJECT
+
+public:
+ PeakFile(AudioFile *audioFile);
+ virtual ~PeakFile();
+
+ // Copy constructor
+ //
+ PeakFile(const PeakFile &);
+
+ // Standard file methods
+ //
+ virtual bool open();
+ virtual void close();
+
+ // Write to standard peak file
+ //
+ virtual bool write();
+
+ // Write the file, emit progress signal and process app events
+ //
+ virtual bool write(unsigned short updatePercentage);
+
+ // Write peak chunk to file handle (BWF)
+ //
+ bool writeToHandle(std::ofstream *file, unsigned short updatePercentage);
+
+ // Is the peak file valid and up to date?
+ //
+ bool isValid();
+
+ // Vital file stats
+ //
+ void printStats();
+
+ // Get a preview of a section of the audio file where that section
+ // is "width" pixels.
+ //
+ std::vector<float> getPreview(const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima);
+
+ AudioFile* getAudioFile() { return m_audioFile; }
+ const AudioFile* getAudioFile() const { return m_audioFile; }
+
+ // Scan to a peak and scan forward a number of peaks
+ //
+ bool scanToPeak(int peak);
+ bool scanForward(int numberOfPeaks);
+
+ // Find threshold crossing points
+ //
+ std::vector<SplitPointPair> getSplitPoints(const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minLength);
+ // Accessors
+ //
+ int getVersion() const { return m_version; }
+ int getFormat() const { return m_format; }
+ int getPointsPerValue() const { return m_pointsPerValue; }
+ int getBlockSize() const { return m_blockSize; }
+ int getChannels() const { return m_channels; }
+ int getNumberOfPeaks() const { return m_numberOfPeaks; }
+ int getPositionPeakOfPeaks() const { return m_positionPeakOfPeaks; }
+ int getOffsetToPeaks() const { return m_offsetToPeaks; }
+ int getBodyBytes() const { return m_bodyBytes; }
+ QDateTime getModificationTime() const { return m_modificationTime; }
+ std::streampos getChunkStartPosition() const
+ { return m_chunkStartPosition; }
+
+ bool isProcessingPeaks() const { return m_keepProcessing; }
+ void setProcessingPeaks(bool value) { m_keepProcessing = value; }
+
+signals:
+ void setProgress(int);
+
+protected:
+ // Write the peak header and the peaks themselves
+ //
+ void writeHeader(std::ofstream *file);
+ void writePeaks(unsigned short updatePercentage,
+ std::ofstream *file);
+
+ // Get the position of a peak for a given time
+ //
+ int getPeak(const RealTime &time);
+
+ // And the time of a peak
+ //
+ RealTime getTime(int peak);
+
+ // Parse the header
+ //
+ void parseHeader();
+
+ AudioFile *m_audioFile;
+
+ // Some Peak Envelope Chunk parameters
+ //
+ int m_version;
+ int m_format; // bytes in peak value (1 or 2)
+ int m_pointsPerValue;
+ int m_blockSize;
+ int m_channels;
+ int m_numberOfPeaks;
+ int m_positionPeakOfPeaks;
+ int m_offsetToPeaks;
+ int m_bodyBytes;
+
+ // Peak timestamp
+ //
+ QDateTime m_modificationTime;
+
+ std::streampos m_chunkStartPosition;
+
+ // For cacheing of peak information in memory we use the last query
+ // parameters as our key to the cached data.
+ //
+ RealTime m_lastPreviewStartTime;
+ RealTime m_lastPreviewEndTime;
+ int m_lastPreviewWidth;
+ bool m_lastPreviewShowMinima;
+ std::vector<float> m_lastPreviewCache;
+
+ // Do we actually want to keep processing this peakfile?
+ // In case we get a cancel.
+ //
+ bool m_keepProcessing;
+
+ std::string m_peakCache;
+
+};
+
+}
+
+
+#endif // _PEAKFILE_H_
+
+
diff --git a/src/sound/PeakFileManager.cpp b/src/sound/PeakFileManager.cpp
new file mode 100644
index 0000000..10293e6
--- /dev/null
+++ b/src/sound/PeakFileManager.cpp
@@ -0,0 +1,327 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+// Accepts a file handle positioned somewhere in sample data (could
+// be at the start) along with the necessary meta information for
+// decoding (channels, bits per sample) and turns the sample data
+// into peak data and generates a BWF format peak chunk file. This
+// file can exist by itself (in the case this is being generated
+// by a WAV) or be accomodated inside a BWF format file.
+//
+//
+
+#include <string>
+#include <vector>
+
+#include <qobject.h>
+
+#include "PeakFileManager.h"
+#include "AudioFile.h"
+#include "RealTime.h"
+#include "PeakFile.h"
+
+namespace Rosegarden
+{
+
+
+PeakFileManager::PeakFileManager():
+ m_updatePercentage(0),
+ m_currentPeakFile(0)
+{}
+
+PeakFileManager::~PeakFileManager()
+{}
+
+// Inserts PeakFile based on AudioFile if it doesn't already exist
+bool
+PeakFileManager::insertAudioFile(AudioFile *audioFile)
+{
+ std::vector<PeakFile*>::iterator it;
+
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++) {
+ if ((*it)->getAudioFile()->getId() == audioFile->getId())
+ return false;
+ }
+
+ /*
+ std::cout << "PeakFileManager::insertAudioFile - creating peak file "
+ << m_peakFiles.size() + 1
+ << " for \"" << audioFile->getFilename()
+ << "\"" << std::endl;
+ */
+
+ // Insert
+ m_peakFiles.push_back(new PeakFile(audioFile));
+
+ return true;
+}
+
+// Removes peak file from PeakFileManager - doesn't affect audioFile
+//
+bool
+PeakFileManager::removeAudioFile(AudioFile *audioFile)
+{
+ std::vector<PeakFile*>::iterator it;
+
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++) {
+ if ((*it)->getAudioFile()->getId() == audioFile->getId()) {
+ if (m_currentPeakFile == *it)
+ m_currentPeakFile = 0;
+ delete *it;
+ m_peakFiles.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Auto-insert PeakFile into manager if it doesn't already exist
+//
+PeakFile*
+PeakFileManager::getPeakFile(AudioFile *audioFile)
+{
+ std::vector<PeakFile*>::iterator it;
+ PeakFile *ptr = 0;
+
+ while (ptr == 0) {
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++)
+ if ((*it)->getAudioFile()->getId() == audioFile->getId())
+ ptr = *it;
+
+ // If nothing is found then insert and retry
+ //
+ if (ptr == 0) {
+ // Insert - if we fail we return as empty
+ //
+ if (insertAudioFile(audioFile) == false)
+ return 0;
+ }
+ }
+
+ return ptr;
+}
+
+
+// Does a given AudioFile have a valid peak file or peak chunk?
+//
+bool
+PeakFileManager::hasValidPeaks(AudioFile *audioFile)
+{
+ if (audioFile->getType() == WAV) {
+ // Check external peak file
+ PeakFile *peakFile = getPeakFile(audioFile);
+
+ if (peakFile == 0) {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cerr << "PeakFileManager::hasValidPeaks - no peak file found"
+ << std::endl;
+#endif
+
+ return false;
+ }
+ // If it doesn't open and parse correctly
+ if (peakFile->open() == false)
+ return false;
+
+ // or if the data is old or invalid
+ if (peakFile->isValid() == false)
+ return false;
+
+ } else if (audioFile->getType() == BWF) {
+ // check internal peak chunk
+ } else {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cout << "PeakFileManager::hasValidPeaks - unsupported file type"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ return true;
+
+}
+
+// Generate the peak file. Checks to see if peak file exists
+// already and if so if it's up to date. If it isn't then we
+// regenerate.
+//
+void
+PeakFileManager::generatePeaks(AudioFile *audioFile,
+ unsigned short updatePercentage)
+{
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cout << "PeakFileManager::generatePeaks - generating peaks for \""
+ << audioFile->getFilename() << "\"" << std::endl;
+#endif
+
+ if (audioFile->getType() == WAV) {
+ m_currentPeakFile = getPeakFile(audioFile);
+
+ QObject::connect(m_currentPeakFile, SIGNAL(setProgress(int)),
+ this, SIGNAL(setProgress(int)));
+
+ // Just write out a peak file
+ //
+ if (m_currentPeakFile->write(updatePercentage) == false) {
+ std::cerr << "Can't write peak file for " << audioFile->getFilename() << " - no preview generated" << std::endl;
+ throw BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ // The m_currentPeakFile might have been cancelled (see stopPreview())
+ //
+ if (m_currentPeakFile) {
+ // close writes out important things
+ m_currentPeakFile->close();
+ m_currentPeakFile->disconnect();
+ }
+ } else if (audioFile->getType() == BWF) {
+ // write the file out and incorporate the peak chunk
+ } else {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cerr << "PeakFileManager::generatePeaks - unsupported file type"
+ << std::endl;
+#endif
+
+ return ;
+ }
+
+ m_currentPeakFile = 0;
+
+}
+
+std::vector<float>
+PeakFileManager::getPreview(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima)
+{
+ std::vector<float> rV;
+
+ // If we've got no channels then the audio file hasn't
+ // completed (recording) - so don't generate a preview
+ //
+ if (audioFile->getChannels() == 0)
+ return rV;
+
+ if (audioFile->getType() == WAV) {
+ PeakFile *peakFile = getPeakFile(audioFile);
+
+ // just write out a peak file
+ try {
+ peakFile->open();
+ rV = peakFile->getPreview(startTime,
+ endTime,
+ width,
+ showMinima);
+ } catch (SoundFile::BadSoundFileException e) {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cout << "PeakFileManager::getPreview "
+ << "\"" << e << "\"" << std::endl;
+#else
+
+ ;
+#endif
+
+ throw BadPeakFileException(e);
+ }
+ } else if (audioFile->getType() == BWF) {
+ // write the file out and incorporate the peak chunk
+ }
+#ifdef DEBUG_PEAKFILEMANAGER
+ else {
+ std::cerr << "PeakFileManager::getPreview - unsupported file type"
+ << std::endl;
+ }
+#endif
+
+ return rV;
+}
+
+void
+PeakFileManager::clear()
+{
+ std::vector<PeakFile*>::iterator it;
+
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++)
+ delete (*it);
+
+ m_peakFiles.erase(m_peakFiles.begin(), m_peakFiles.end());
+
+ m_currentPeakFile = 0;
+}
+
+
+std::vector<SplitPointPair>
+PeakFileManager::getSplitPoints(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime)
+{
+ PeakFile *peakFile = getPeakFile(audioFile);
+
+ if (peakFile == 0)
+ return std::vector<SplitPointPair>();
+
+ return peakFile->getSplitPoints(startTime,
+ endTime,
+ threshold,
+ minTime);
+
+}
+
+void
+PeakFileManager::stopPreview()
+{
+ if (m_currentPeakFile) {
+ // Stop processing
+ //
+ QString fileName = QString(m_currentPeakFile->getFilename().data());
+ m_currentPeakFile->setProcessingPeaks(false);
+ m_currentPeakFile->disconnect();
+
+ QFile file(fileName);
+ bool removed = file.remove();
+
+#ifdef DEBUG_PEAKFILEMANAGER
+
+ if (removed) {
+ std::cout << "PeakFileManager::stopPreview() - removed preview"
+ << std::endl;
+ }
+#endif
+ //delete m_currentPeakFile;
+ m_currentPeakFile = 0;
+ }
+}
+
+
+
+
+}
+
+
+#include "PeakFileManager.moc"
diff --git a/src/sound/PeakFileManager.h b/src/sound/PeakFileManager.h
new file mode 100644
index 0000000..07ff704
--- /dev/null
+++ b/src/sound/PeakFileManager.h
@@ -0,0 +1,162 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+// Accepts an AudioFIle and turns the sample data into peak data for
+// storage in a peak file or a BWF format peak chunk. Pixmaps or
+// sample data is returned to callers on demand using these cached
+// values.
+//
+//
+
+#ifndef _PEAKFILEMANAGER_H_
+#define _PEAKFILEMANAGER_H_
+
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+#include <qobject.h>
+
+
+#include "PeakFile.h"
+
+namespace Rosegarden
+{
+
+class AudioFile;
+class RealTime;
+
+class PeakFileManager : public QObject
+{
+ Q_OBJECT
+public:
+ // updatePercentage tells this object how often to throw a
+ // percentage complete message - active between 0-100 only
+ // if it's set to 5 then we send an update exception every
+ // five percent. The percentage complete is sent with
+ // each exception.
+ //
+ PeakFileManager();
+ virtual ~PeakFileManager();
+
+ class BadPeakFileException : public Exception
+ {
+ public:
+ BadPeakFileException(std::string path) :
+ Exception("Bad peak file " + path), m_path(path) { }
+ BadPeakFileException(std::string path, std::string file, int line) :
+ Exception("Bad peak file " + path, file, line), m_path(path) { }
+ BadPeakFileException(const SoundFile::BadSoundFileException &e) :
+ Exception("Bad peak file (malformed audio?) " + e.getPath()), m_path(e.getPath()) { }
+
+ ~BadPeakFileException() throw() { }
+
+ std::string getPath() const { return m_path; }
+
+ private:
+ std::string m_path;
+ };
+
+private:
+ PeakFileManager(const PeakFileManager &pFM);
+ PeakFileManager& operator=(const PeakFileManager &);
+
+public:
+ // Check that a given audio file has a valid and up to date
+ // peak file or peak chunk.
+ //
+ bool hasValidPeaks(AudioFile *audioFile);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Generate a peak file from file details - if the peak file already
+ // exists _and_ it's up to date then we don't do anything. For BWF
+ // files we generate an internal peak chunk.
+ //
+ //
+ void generatePeaks(AudioFile *audioFile,
+ unsigned short updatePercentage);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Get a vector of floats as the preview
+ //
+ std::vector<float> getPreview(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Remove cache for a single audio file (if audio file to be deleted etc)
+ //
+ bool removeAudioFile(AudioFile *audioFile);
+
+ // Clear down
+ //
+ void clear();
+
+ // Get split points for a peak file
+ //
+ std::vector<SplitPointPair>
+ getSplitPoints(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime);
+
+ std::vector<PeakFile*>::const_iterator begin() const
+ { return m_peakFiles.begin(); }
+
+ std::vector<PeakFile*>::const_iterator end() const
+ { return m_peakFiles.end(); }
+
+ // Stop a preview during its build
+ //
+ void stopPreview();
+
+signals:
+ void setProgress(int);
+
+protected:
+
+ // Add and remove from our PeakFile cache
+ //
+ bool insertAudioFile(AudioFile *audioFile);
+ PeakFile* getPeakFile(AudioFile *audioFile);
+
+ std::vector<PeakFile*> m_peakFiles;
+ unsigned short m_updatePercentage; // how often we send updates
+
+ // Whilst processing - the current PeakFile
+ //
+ PeakFile *m_currentPeakFile;
+
+
+};
+
+
+}
+
+
+#endif // _PEAKFILEMANAGER_H_
+
+
diff --git a/src/sound/PlayableAudioFile.cpp b/src/sound/PlayableAudioFile.cpp
new file mode 100644
index 0000000..b5ddcf7
--- /dev/null
+++ b/src/sound/PlayableAudioFile.cpp
@@ -0,0 +1,1086 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "PlayableAudioFile.h"
+#include <cassert>
+
+namespace Rosegarden
+{
+
+//#define DEBUG_RING_BUFFER_POOL 1
+//#define DEBUG_PLAYABLE 1
+//#define DEBUG_PLAYABLE_READ 1
+
+class RingBufferPool
+{
+public:
+ typedef float sample_t;
+
+ RingBufferPool(size_t bufferSize);
+ virtual ~RingBufferPool();
+
+ /**
+ * Set the default size for buffers. Buffers currently allocated
+ * will not be resized until they are returned.
+ */
+ void setBufferSize(size_t n);
+
+ size_t getBufferSize() const
+ {
+ return m_bufferSize;
+ }
+
+ /**
+ * Discard or create buffers as necessary so as to have n buffers
+ * in the pool. This will not discard any buffers that are
+ * currently allocated, so if more than n are allocated, more than
+ * n will remain.
+ */
+ void setPoolSize(size_t n);
+
+ size_t getPoolSize() const
+ {
+ return m_buffers.size();
+ }
+
+ /**
+ * Return true if n buffers available, false otherwise.
+ */
+ bool getBuffers(size_t n, RingBuffer<sample_t> **buffers);
+
+ /**
+ * Return a buffer to the pool.
+ */
+ void returnBuffer(RingBuffer<sample_t> *buffer);
+
+protected:
+ // Want to avoid memory allocation if possible when marking a buffer
+ // unallocated or allocated, so we use a single container for all
+
+ typedef std::pair<RingBuffer<sample_t> *, bool> AllocPair;
+ typedef std::vector<AllocPair> AllocList;
+ AllocList m_buffers;
+
+ size_t m_bufferSize;
+ size_t m_available;
+
+ pthread_mutex_t m_lock;
+};
+
+
+RingBufferPool::RingBufferPool(size_t bufferSize) :
+ m_bufferSize(bufferSize),
+ m_available(0)
+{
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_lock, &initialisingMutex, sizeof(pthread_mutex_t));
+}
+
+RingBufferPool::~RingBufferPool()
+{
+ size_t allocatedCount = 0;
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (i->second)
+ ++allocatedCount;
+ }
+
+ if (allocatedCount > 0) {
+ std::cerr << "WARNING: RingBufferPool::~RingBufferPool: deleting pool with " << allocatedCount << " allocated buffers" << std::endl;
+ }
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ delete i->first;
+ }
+
+ m_buffers.clear();
+
+ pthread_mutex_destroy(&m_lock);
+}
+
+void
+RingBufferPool::setBufferSize(size_t n)
+{
+ if (m_bufferSize == n)
+ return ;
+
+ pthread_mutex_lock(&m_lock);
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::setBufferSize: from " << m_bufferSize
+ << " to " << n << std::endl;
+ int c = 0;
+#endif
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (!i->second) {
+ delete i->first;
+ i->first = new RingBuffer<sample_t>(n);
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "Resized buffer " << c++ << std::endl;
+#endif
+
+ } else {
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "Buffer " << c++ << " is already in use, resizing in place" << std::endl;
+#endif
+
+ i->first->resize(n);
+ }
+ }
+
+ m_bufferSize = n;
+ pthread_mutex_unlock(&m_lock);
+}
+
+void
+RingBufferPool::setPoolSize(size_t n)
+{
+ pthread_mutex_lock(&m_lock);
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::setPoolSize: from " << m_buffers.size()
+ << " to " << n << std::endl;
+#endif
+
+ size_t allocatedCount = 0, count = 0;
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (i->second)
+ ++allocatedCount;
+ ++count;
+ }
+
+ if (count > n) {
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ) {
+ if (!i->second) {
+ delete i->first;
+ m_buffers.erase(i);
+ if (--count == n)
+ break;
+ } else {
+ ++i;
+ }
+ }
+ }
+
+ while (count < n) {
+ m_buffers.push_back(AllocPair(new RingBuffer<sample_t>(m_bufferSize),
+ false));
+ ++count;
+ }
+
+ m_available = std::max(allocatedCount, n) - allocatedCount;
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::setPoolSize: have " << m_buffers.size()
+ << " buffers (" << allocatedCount << " allocated, " << m_available << " available)" << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_lock);
+}
+
+bool
+RingBufferPool::getBuffers(size_t n, RingBuffer<sample_t> **buffers)
+{
+ pthread_mutex_lock(&m_lock);
+
+ size_t count = 0;
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (!i->second && ++count == n)
+ break;
+ }
+
+ if (count < n) {
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "RingBufferPool::getBuffers(" << n << "): not available (in pool of " << m_buffers.size() << "), resizing" << std::endl;
+#endif
+
+ AllocList newBuffers;
+
+ while (count < n) {
+ for (size_t i = 0; i < m_buffers.size(); ++i) {
+ newBuffers.push_back(m_buffers[i]);
+ }
+ for (size_t i = 0; i < m_buffers.size(); ++i) {
+ newBuffers.push_back(AllocPair(new RingBuffer<sample_t>(m_bufferSize),
+ false));
+ }
+ count += m_buffers.size();
+ m_available += m_buffers.size();
+ }
+
+ m_buffers = newBuffers;
+ }
+
+ count = 0;
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::getBuffers(" << n << "): available" << std::endl;
+#endif
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (!i->second) {
+ i->second = true;
+ i->first->reset();
+ i->first->mlock();
+ buffers[count] = i->first;
+ --m_available;
+ if (++count == n)
+ break;
+ }
+ }
+
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "RingBufferPool::getBuffers: " << m_available << " remain in pool of " << m_buffers.size() << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_lock);
+ return true;
+}
+
+void
+RingBufferPool::returnBuffer(RingBuffer<sample_t> *buffer)
+{
+ pthread_mutex_lock(&m_lock);
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::returnBuffer" << std::endl;
+#endif
+
+ buffer->munlock();
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (i->first == buffer) {
+ i->second = false;
+ ++m_available;
+ if (buffer->getSize() != m_bufferSize) {
+ delete buffer;
+ i->first = new RingBuffer<sample_t>(m_bufferSize);
+ }
+ }
+ }
+
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "RingBufferPool::returnBuffer: " << m_available << " remain in pool of " << m_buffers.size() << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_lock);
+}
+
+
+AudioCache PlayableAudioFile::m_smallFileCache;
+
+std::vector<PlayableAudioFile::sample_t *> PlayableAudioFile::m_workBuffers;
+size_t PlayableAudioFile::m_workBufferSize = 0;
+
+char *PlayableAudioFile::m_rawFileBuffer;
+size_t PlayableAudioFile::m_rawFileBufferSize = 0;
+
+RingBufferPool *PlayableAudioFile::m_ringBufferPool = 0;
+
+size_t PlayableAudioFile::m_xfadeFrames = 30;
+
+PlayableAudioFile::PlayableAudioFile(InstrumentId instrumentId,
+ AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &startIndex,
+ const RealTime &duration,
+ size_t bufferSize,
+ size_t smallFileSize,
+ int targetChannels,
+ int targetSampleRate) :
+ m_startTime(startTime),
+ m_startIndex(startIndex),
+ m_duration(duration),
+ m_file(0),
+ m_audioFile(audioFile),
+ m_instrumentId(instrumentId),
+ m_targetChannels(targetChannels),
+ m_targetSampleRate(targetSampleRate),
+ m_fileEnded(false),
+ m_firstRead(true),
+ m_runtimeSegmentId( -1),
+ m_isSmallFile(false),
+ m_currentScanPoint(RealTime::zeroTime),
+ m_smallFileScanFrame(0),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime)
+{
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::PlayableAudioFile - creating " << this << " for instrument " << instrumentId << " with file " << (m_audioFile ? m_audioFile->getShortFilename() : "(none)") << std::endl;
+#endif
+
+ if (!m_ringBufferPool) {
+ //!!! Problematic -- how do we deal with different playable audio
+ // files requiring different buffer sizes? That shouldn't be the
+ // usual case, but it's not unthinkable.
+ m_ringBufferPool = new RingBufferPool(bufferSize);
+ } else {
+ m_ringBufferPool->setBufferSize
+ (std::max(bufferSize, m_ringBufferPool->getBufferSize()));
+ }
+
+ initialise(bufferSize, smallFileSize);
+}
+
+
+void
+PlayableAudioFile::setRingBufferPoolSizes(size_t n, size_t nframes)
+{
+ if (!m_ringBufferPool) {
+ m_ringBufferPool = new RingBufferPool(nframes);
+ } else {
+ m_ringBufferPool->setBufferSize
+ (std::max(nframes, m_ringBufferPool->getBufferSize()));
+ }
+ m_ringBufferPool->setPoolSize(n);
+}
+
+
+void
+PlayableAudioFile::initialise(size_t bufferSize, size_t smallFileSize)
+{
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::initialise() " << this << std::endl;
+#endif
+
+ checkSmallFileCache(smallFileSize);
+
+ if (!m_isSmallFile) {
+
+ m_file = new std::ifstream(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!*m_file) {
+ std::cerr << "ERROR: PlayableAudioFile::initialise: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ delete m_file;
+ m_file = 0;
+ }
+ }
+
+ // Scan to the beginning of the data chunk we need
+ //
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::initialise - scanning to " << m_startIndex << std::endl;
+#endif
+
+ if (m_file) {
+ scanTo(m_startIndex);
+ } else {
+ m_fileEnded = false;
+ m_currentScanPoint = m_startIndex;
+ m_smallFileScanFrame = RealTime::realTime2Frame
+ (m_currentScanPoint, m_audioFile->getSampleRate());
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::initialise: buffer size is " << bufferSize << " frames, file size is " << m_audioFile->getSize() << std::endl;
+#endif
+
+ if (m_targetChannels <= 0)
+ m_targetChannels = m_audioFile->getChannels();
+ if (m_targetSampleRate <= 0)
+ m_targetSampleRate = m_audioFile->getSampleRate();
+
+ m_ringBuffers = new RingBuffer<sample_t> *[m_targetChannels];
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ m_ringBuffers[ch] = 0;
+ }
+}
+
+PlayableAudioFile::~PlayableAudioFile()
+{
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ }
+
+ returnRingBuffers();
+ delete[] m_ringBuffers;
+ m_ringBuffers = 0;
+
+ if (m_isSmallFile) {
+ m_smallFileCache.decrementReference(m_audioFile);
+ }
+
+#ifdef DEBUG_PLAYABLE
+ // std::cerr << "PlayableAudioFile::~PlayableAudioFile - destroying - " << this << std::endl;
+#endif
+}
+
+void
+PlayableAudioFile::returnRingBuffers()
+{
+ for (int i = 0; i < m_targetChannels; ++i) {
+ if (m_ringBuffers[i]) {
+ m_ringBufferPool->returnBuffer(m_ringBuffers[i]);
+ m_ringBuffers[i] = 0;
+ }
+ }
+}
+
+bool
+PlayableAudioFile::scanTo(const RealTime &time)
+{
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::scanTo(" << time << ")" << std::endl;
+#endif
+
+ m_fileEnded = false; // until we know otherwise -- this flag is an
+ // optimisation, not a reliable record
+
+ bool ok = false;
+
+ if (m_isSmallFile) {
+
+ m_currentScanPoint = time;
+ m_smallFileScanFrame = RealTime::realTime2Frame
+ (time, m_audioFile->getSampleRate());
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "... maps to frame " << m_smallFileScanFrame << std::endl;
+#endif
+ ok = true;
+
+ } else {
+
+ ok = m_audioFile->scanTo(m_file, time);
+ if (ok) {
+ m_currentScanPoint = time;
+ }
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::scanTo(" << time << "): set m_currentScanPoint to " << m_currentScanPoint << std::endl;
+#endif
+
+ m_firstRead = true; // so we know to xfade in
+
+ return ok;
+}
+
+
+size_t
+PlayableAudioFile::getSampleFramesAvailable()
+{
+ size_t actual = 0;
+
+ if (m_isSmallFile) {
+ size_t cchannels;
+ size_t cframes;
+ (void)m_smallFileCache.getData(m_audioFile, cchannels, cframes);
+ if (cframes > m_smallFileScanFrame)
+ return cframes - m_smallFileScanFrame;
+ else
+ return 0;
+ }
+
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ if (!m_ringBuffers[ch])
+ return 0;
+ size_t thisChannel = m_ringBuffers[ch]->getReadSpace();
+ if (ch == 0 || thisChannel < actual)
+ actual = thisChannel;
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile(" << (m_audioFile ? m_audioFile->getShortFilename() : "(none)") << " " << this << ")::getSampleFramesAvailable: have " << actual << std::endl;
+#endif
+
+ return actual;
+}
+
+size_t
+PlayableAudioFile::addSamples(std::vector<sample_t *> &destination,
+ size_t channels, size_t nframes, size_t offset)
+{
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): channels " << channels << ", my target channels " << m_targetChannels << std::endl;
+#endif
+
+ if (!m_isSmallFile) {
+
+ size_t qty = 0;
+ bool done = m_fileEnded;
+
+ for (int ch = 0; ch < int(channels) && ch < m_targetChannels; ++ch) {
+ if (!m_ringBuffers[ch])
+ return 0; //!!! fatal
+ size_t here = m_ringBuffers[ch]->readAdding(destination[ch] + offset, nframes);
+ if (ch == 0 || here < qty)
+ qty = here;
+ if (done && (m_ringBuffers[ch]->getReadSpace() > 0))
+ done = false;
+ }
+
+ for (int ch = channels; ch < m_targetChannels; ++ch) {
+ m_ringBuffers[ch]->skip(nframes);
+ }
+
+ if (done) {
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): reached end, returning buffers" << std::endl;
+#endif
+
+ returnRingBuffers();
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): returning " << qty << " frames (at least " << (m_ringBuffers[0] ? m_ringBuffers[0]->getReadSpace() : 0) << " remaining)" << std::endl;
+#endif
+
+ return qty;
+
+ } else {
+
+ size_t cchannels;
+ size_t cframes;
+ float **cached = m_smallFileCache.getData(m_audioFile, cchannels, cframes);
+
+ if (!cached) {
+ std::cerr << "WARNING: PlayableAudioFile::addSamples: Failed to find small file in cache" << std::endl;
+ m_isSmallFile = false;
+ } else {
+
+ size_t scanFrame = m_smallFileScanFrame;
+
+ if (scanFrame >= cframes) {
+ m_fileEnded = true;
+ return 0;
+ }
+
+ size_t endFrame = scanFrame + nframes;
+ size_t n = nframes;
+
+ if (endFrame >= cframes) {
+ m_fileEnded = true;
+ n = cframes - scanFrame;
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples: it's a small file: want frames " << scanFrame << " to " << endFrame << " of " << cframes << std::endl;
+#endif
+
+ size_t xfadeIn = (m_firstRead ? m_xfadeFrames : 0);
+ size_t xfadeOut = (m_fileEnded ? m_xfadeFrames : 0);
+
+ // all this could be neater!
+
+ if (channels == 1 && cchannels == 2) { // mix
+ for (size_t i = 0; i < n; ++i) {
+ sample_t v =
+ cached[0][scanFrame + i] +
+ cached[1][scanFrame + i];
+ // if ((i + 1) < xfadeIn)
+ // v = (v * (i + 1)) / xfadeIn;
+ //if ((n - i) < xfadeOut)
+ // v = (v * (n - i)) / xfadeOut;
+ destination[0][i + offset] += v;
+ }
+ } else {
+ for (size_t ch = 0; ch < channels; ++ch) {
+ int sch = ch;
+ if (ch >= cchannels) {
+ if (channels == 2 && cchannels == 1)
+ sch = 0;
+ else
+ break;
+ } else {
+ for (size_t i = 0; i < n; ++i) {
+ sample_t v = cached[sch][scanFrame + i];
+ // if ((i + 1) < xfadeIn)
+ // v = (v * (i + 1)) / xfadeIn;
+ //if ((n - i) < xfadeOut)
+ // v = (v * (n - i)) / xfadeOut;
+ destination[ch][i + offset] += v;
+ }
+ }
+ }
+ }
+
+ m_smallFileScanFrame += nframes;
+ m_currentScanPoint = m_currentScanPoint +
+ RealTime::frame2RealTime(nframes, m_targetSampleRate);
+ return nframes;
+ }
+ }
+
+ return 0;
+}
+
+void
+PlayableAudioFile::checkSmallFileCache(size_t smallFileSize)
+{
+ if (m_smallFileCache.has(m_audioFile)) {
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: Found file in small file cache" << std::endl;
+#endif
+
+ m_smallFileCache.incrementReference(m_audioFile);
+ m_isSmallFile = true;
+
+ } else if (m_audioFile->getSize() <= smallFileSize) {
+
+ std::ifstream file(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!file) {
+ std::cerr << "ERROR: PlayableAudioFile::checkSmallFileCache: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ return ;
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: Adding file to small file cache" << std::endl;
+#endif
+
+ // We always encache files with their original number of
+ // channels (because they might be called for in any channel
+ // configuration subsequently) but with the current sample
+ // rate, not their original one.
+
+ m_audioFile->scanTo(&file, RealTime::zeroTime);
+
+ size_t reqd = m_audioFile->getSize() / m_audioFile->getBytesPerFrame();
+ unsigned char *buffer = new unsigned char[m_audioFile->getSize()];
+ size_t obtained = m_audioFile->getSampleFrames(&file, (char *)buffer, reqd);
+
+// std::cerr <<"obtained=" << obtained << std::endl;
+
+ size_t nch = getSourceChannels();
+ size_t nframes = obtained;
+ if (int(getSourceSampleRate()) != m_targetSampleRate) {
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: Resampling badly from " << getSourceSampleRate() << " to " << m_targetSampleRate << std::endl;
+#endif
+ nframes = size_t(float(nframes) * float(m_targetSampleRate) /
+ float(getSourceSampleRate()));
+ }
+
+ std::vector<sample_t *> samples;
+ for (size_t ch = 0; ch < nch; ++ch) {
+ samples.push_back(new sample_t[nframes]);
+ }
+
+ if (!m_audioFile->decode(buffer,
+ obtained * m_audioFile->getBytesPerFrame(),
+ m_targetSampleRate,
+ nch,
+ nframes,
+ samples)) {
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: failed to decode file" << std::endl;
+ } else {
+ sample_t **toCache = new sample_t * [nch];
+ for (size_t ch = 0; ch < nch; ++ch) {
+ toCache[ch] = samples[ch];
+ }
+ m_smallFileCache.addData(m_audioFile, nch, nframes, toCache);
+ m_isSmallFile = true;
+ }
+
+ delete[] buffer;
+
+ file.close();
+ }
+
+ if (m_isSmallFile) {
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ m_file = 0;
+ }
+ }
+}
+
+
+void
+PlayableAudioFile::fillBuffers()
+{
+#ifdef DEBUG_PLAYABLE
+ if (m_audioFile) {
+ std::cerr << "PlayableAudioFile(" << m_audioFile->getShortFilename() << ")::fillBuffers() [async] -- scanning to " << m_startIndex << std::endl;
+ } else {
+ std::cerr << "PlayableAudioFile::fillBuffers() [async] -- scanning to " << m_startIndex << std::endl;
+ }
+#endif
+
+ if (!m_isSmallFile && (!m_file || !*m_file)) {
+ m_file = new std::ifstream(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+ if (!*m_file) {
+ std::cerr << "ERROR: PlayableAudioFile::fillBuffers: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ delete m_file;
+ m_file = 0;
+ return ;
+ }
+ }
+
+ scanTo(m_startIndex);
+ updateBuffers();
+}
+
+void
+PlayableAudioFile::clearBuffers()
+{
+ returnRingBuffers();
+}
+
+bool
+PlayableAudioFile::fillBuffers(const RealTime &currentTime)
+{
+#ifdef DEBUG_PLAYABLE
+ if (!m_isSmallFile) {
+ if (m_audioFile) {
+ std::cerr << "PlayableAudioFile(" << m_audioFile->getShortFilename() << " " << this << ")::fillBuffers(" << currentTime << "):\n my start time " << m_startTime << ", start index " << m_startIndex << ", duration " << m_duration << std::endl;
+ } else {
+ std::cerr << "PlayableAudioFile::fillBuffers(" << currentTime << "): my start time " << m_startTime << ", start index " << m_startIndex << ", duration " << m_duration << std::endl;
+ }
+ }
+#endif
+
+ if (currentTime > m_startTime + m_duration) {
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::fillBuffers: seeking past end, returning buffers" << std::endl;
+#endif
+
+ returnRingBuffers();
+ return true;
+ }
+
+ if (!m_isSmallFile && (!m_file || !*m_file)) {
+ m_file = new std::ifstream(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+ if (!*m_file) {
+ std::cerr << "ERROR: PlayableAudioFile::fillBuffers: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ delete m_file;
+ m_file = 0;
+ return false;
+ }
+ scanTo(m_startIndex);
+ }
+
+ RealTime scanTime = m_startIndex;
+
+ if (currentTime > m_startTime) {
+ scanTime = m_startIndex + currentTime - m_startTime;
+ }
+
+ // size_t scanFrames = RealTime::realTime2Frame
+ // (scanTime,
+ // m_isSmallFile ? m_targetSampleRate : m_audioFile->getSampleRate());
+
+ if (scanTime != m_currentScanPoint) {
+ scanTo(scanTime);
+ }
+
+ if (!m_isSmallFile) {
+ for (int i = 0; i < m_targetChannels; ++i) {
+ if (m_ringBuffers[i])
+ m_ringBuffers[i]->reset();
+ }
+ updateBuffers();
+ }
+
+ return true;
+}
+
+bool
+PlayableAudioFile::updateBuffers()
+{
+ if (m_isSmallFile)
+ return false;
+ if (!m_file)
+ return false;
+
+ if (m_fileEnded) {
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::updateBuffers: at end of file already" << std::endl;
+#endif
+
+ return false;
+ }
+
+ if (!m_ringBuffers[0]) {
+
+ if (m_targetChannels < 0) {
+ std::cerr << "WARNING: PlayableAudioFile::updateBuffers: m_targetChannels < 0, can't allocate ring buffers" << std::endl;
+ return false;
+ }
+
+ // need a buffer: can we get one?
+ if (!m_ringBufferPool->getBuffers(m_targetChannels, m_ringBuffers)) {
+ std::cerr << "WARNING: PlayableAudioFile::updateBuffers: no ring buffers available" << std::endl;
+ return false;
+ }
+ }
+
+ size_t nframes = 0;
+
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ if (!m_ringBuffers[ch])
+ continue;
+ size_t writeSpace = m_ringBuffers[ch]->getWriteSpace();
+ if (ch == 0 || writeSpace < nframes)
+ nframes = writeSpace;
+ }
+
+ if (nframes == 0) {
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::updateBuffers: frames == 0, ignoring" << std::endl;
+#endif
+
+ return false;
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::updateBuffers: want " << nframes << " frames" << std::endl;
+#endif
+
+
+ RealTime block = RealTime::frame2RealTime(nframes, m_targetSampleRate);
+ if (m_currentScanPoint + block >= m_startIndex + m_duration) {
+ block = m_startIndex + m_duration - m_currentScanPoint;
+ if (block <= RealTime::zeroTime)
+ nframes = 0;
+ else
+ nframes = RealTime::realTime2Frame(block, m_targetSampleRate);
+ m_fileEnded = true;
+ }
+
+ size_t fileFrames = nframes;
+ if (m_targetSampleRate != int(getSourceSampleRate())) {
+ fileFrames = size_t(float(fileFrames) * float(getSourceSampleRate()) /
+ float(m_targetSampleRate));
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "Want " << fileFrames << " (" << block << ") from file (" << (m_duration + m_startIndex - m_currentScanPoint - block) << " to go)" << std::endl;
+#endif
+
+ //!!! need to be doing this in initialise, want to avoid allocations here
+ if ((getBytesPerFrame() * fileFrames) > m_rawFileBufferSize) {
+ delete[] m_rawFileBuffer;
+ m_rawFileBufferSize = getBytesPerFrame() * fileFrames;
+#ifdef DEBUG_PLAYABLE_READ
+
+ std::cerr << "Expanding raw file buffer to " << m_rawFileBufferSize << " chars" << std::endl;
+#endif
+
+ m_rawFileBuffer = new char[m_rawFileBufferSize];
+ }
+
+ size_t obtained =
+ m_audioFile->getSampleFrames(m_file, m_rawFileBuffer, fileFrames);
+
+ if (obtained < fileFrames || m_file->eof()) {
+ m_fileEnded = true;
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "requested " << fileFrames << " frames from file for " << nframes << " frames, got " << obtained << " frames" << std::endl;
+#endif
+
+ if (nframes > m_workBufferSize) {
+
+ for (size_t i = 0; i < m_workBuffers.size(); ++i) {
+ delete[] m_workBuffers[i];
+ }
+
+ m_workBuffers.clear();
+ m_workBufferSize = nframes;
+#ifdef DEBUG_PLAYABLE_READ
+
+ std::cerr << "Expanding work buffer to " << m_workBufferSize << " frames" << std::endl;
+#endif
+
+ for (int i = 0; i < m_targetChannels; ++i) {
+ m_workBuffers.push_back(new sample_t[m_workBufferSize]);
+ }
+
+ } else {
+
+ while (m_targetChannels > m_workBuffers.size()) {
+ m_workBuffers.push_back(new sample_t[m_workBufferSize]);
+ }
+ }
+
+ if (m_audioFile->decode((const unsigned char *)m_rawFileBuffer,
+ obtained * getBytesPerFrame(),
+ m_targetSampleRate,
+ m_targetChannels,
+ nframes,
+ m_workBuffers,
+ false)) {
+
+ /*!!! No -- GUI and notification side of things isn't up to this yet,
+ so comment it out just in case
+
+ if (m_autoFade) {
+
+ if (m_currentScanPoint < m_startIndex + m_fadeInTime) {
+
+ size_t fadeSamples =
+ RealTime::realTime2Frame(m_fadeInTime, getTargetSampleRate());
+ size_t originSamples =
+ RealTime::realTime2Frame(m_currentScanPoint - m_startIndex,
+ getTargetSampleRate());
+
+ for (size_t i = 0; i < nframes; ++i) {
+ if (i + originSamples > fadeSamples) {
+ break;
+ }
+ float gain = float(i + originSamples) / float(fadeSamples);
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ m_workBuffers[ch][i] *= gain;
+ }
+ }
+ }
+
+ if (m_currentScanPoint + block >
+ m_startIndex + m_duration - m_fadeOutTime) {
+
+ size_t fadeSamples =
+ RealTime::realTime2Frame(m_fadeOutTime, getTargetSampleRate());
+ size_t originSamples = // counting from end
+ RealTime::realTime2Frame
+ (m_startIndex + m_duration - m_currentScanPoint,
+ getTargetSampleRate());
+
+ for (size_t i = 0; i < nframes; ++i) {
+ float gain = 1.0;
+ if (originSamples < i) gain = 0.0;
+ else {
+ size_t fromEnd = originSamples - i;
+ if (fromEnd < fadeSamples) {
+ gain = float(fromEnd) / float(fadeSamples);
+ }
+ }
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ m_workBuffers[ch][i] *= gain;
+ }
+ }
+ }
+ }
+ */
+
+ m_currentScanPoint = m_currentScanPoint + block;
+
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+
+ if (m_firstRead || m_fileEnded) {
+ float xfade = std::min(m_xfadeFrames, nframes);
+ if (m_firstRead) {
+ for (size_t i = 0; i < xfade; ++i) {
+ m_workBuffers[ch][i] *= float(i + 1) / xfade;
+ }
+ }
+ if (m_fileEnded) {
+ for (size_t i = 0; i < xfade; ++i) {
+ m_workBuffers[ch][nframes - i - 1] *=
+ float(i + 1) / xfade;
+ }
+ }
+ }
+
+ if (m_ringBuffers[ch]) {
+ m_ringBuffers[ch]->write(m_workBuffers[ch], nframes);
+ }
+ }
+ }
+
+ m_firstRead = false;
+
+ if (obtained < fileFrames) {
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ m_file = 0;
+ }
+ }
+
+ return true;
+}
+
+
+// How many channels in the base AudioFile?
+//
+unsigned int
+PlayableAudioFile::getSourceChannels()
+{
+ if (m_audioFile) {
+ return m_audioFile->getChannels();
+ }
+ return 0;
+}
+
+unsigned int
+PlayableAudioFile::getTargetChannels()
+{
+ return m_targetChannels;
+}
+
+unsigned int
+PlayableAudioFile::getBytesPerFrame()
+{
+ if (m_audioFile) {
+ return m_audioFile->getBytesPerFrame();
+ }
+ return 0;
+}
+
+unsigned int
+PlayableAudioFile::getSourceSampleRate()
+{
+ if (m_audioFile) {
+ return m_audioFile->getSampleRate();
+ }
+ return 0;
+}
+
+unsigned int
+PlayableAudioFile::getTargetSampleRate()
+{
+ return m_targetSampleRate;
+}
+
+
+// How many bits per sample in the base AudioFile?
+//
+unsigned int
+PlayableAudioFile::getBitsPerSample()
+{
+ if (m_audioFile) {
+ return m_audioFile->getBitsPerSample();
+ }
+ return 0;
+}
+
+
+}
+
diff --git a/src/sound/PlayableAudioFile.h b/src/sound/PlayableAudioFile.h
new file mode 100644
index 0000000..648ad4c
--- /dev/null
+++ b/src/sound/PlayableAudioFile.h
@@ -0,0 +1,219 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _PLAYABLE_AUDIO_FILE_H_
+#define _PLAYABLE_AUDIO_FILE_H_
+
+#include "Instrument.h"
+#include "RingBuffer.h"
+#include "AudioFile.h"
+#include "AudioCache.h"
+
+#include <string>
+#include <map>
+
+namespace Rosegarden
+{
+
+class RingBufferPool;
+
+
+class PlayableAudioFile
+{
+public:
+ typedef float sample_t;
+
+ PlayableAudioFile(InstrumentId instrumentId,
+ AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &startIndex,
+ const RealTime &duration,
+ size_t bufferSize = 4096,
+ size_t smallFileSize = 131072,
+ int targetChannels = -1, // default same as file
+ int targetSampleRate = -1); // default same as file
+ ~PlayableAudioFile();
+
+ static void setRingBufferPoolSizes(size_t n, size_t nframes);
+
+ void setStartTime(const RealTime &time) { m_startTime = time; }
+ RealTime getStartTime() const { return m_startTime; }
+
+ void setDuration(const RealTime &time) { m_duration = time; }
+ RealTime getDuration() const { return m_duration; }
+ RealTime getEndTime() const { return m_startTime + m_duration; }
+
+ void setStartIndex(const RealTime &time) { m_startIndex = time; }
+ RealTime getStartIndex() const { return m_startIndex; }
+
+ bool isSmallFile() const { return m_isSmallFile; }
+
+ // Get audio file for interrogation
+ //
+ AudioFile* getAudioFile() const { return m_audioFile; }
+
+ // Get instrument ID - we need to be able to map back
+ // at the GUI.
+ //
+ InstrumentId getInstrument() const { return m_instrumentId; }
+
+ // Return the number of frames currently buffered. The next call
+ // to getSamples on any channel is guaranteed to return at least
+ // this many samples.
+ //
+ size_t getSampleFramesAvailable();
+
+ // Read samples from the given channel on the file and add them
+ // into the destination.
+ //
+ // If insufficient frames are available, this will leave the
+ // excess samples unchanged.
+ //
+ // Returns the actual number of samples written.
+ //
+ // If offset is non-zero, the samples will be written starting at
+ // offset frames from the start of the target block.
+ //
+ size_t addSamples(std::vector<sample_t *> &target,
+ size_t channels, size_t nframes, size_t offset = 0);
+
+ unsigned int getSourceChannels();
+ unsigned int getTargetChannels();
+ unsigned int getSourceSampleRate();
+ unsigned int getTargetSampleRate();
+
+ unsigned int getBitsPerSample();
+ unsigned int getBytesPerFrame();
+
+ // Clear out and refill the ring buffer for immediate
+ // (asynchronous) play.
+ //
+ void fillBuffers();
+
+ // Clear out and refill the ring buffer (in preparation for
+ // playback) according to the proposed play time.
+ //
+ // This call and updateBuffers are not thread-safe (for
+ // performance reasons). They should be called for all files
+ // sequentially within a single thread.
+ //
+ bool fillBuffers(const RealTime &currentTime);
+
+ void clearBuffers();
+
+ // Update the buffer during playback.
+ //
+ // This call and fillBuffers are not thread-safe (for performance
+ // reasons). They should be called for all files sequentially
+ // within a single thread.
+ //
+ bool updateBuffers();
+
+ // Has fillBuffers been called and completed yet?
+ //
+ bool isBuffered() const { return m_currentScanPoint > m_startIndex; }
+
+ // Has all the data in this file now been read into the buffers?
+ //
+ bool isFullyBuffered() const { return m_isSmallFile || m_fileEnded; }
+
+ // Stop playing this file.
+ //
+ void cancel() { m_fileEnded = true; }
+
+ // Segment id that allows us to crosscheck against playing audio
+ // segments.
+ //
+ int getRuntimeSegmentId() const { return m_runtimeSegmentId; }
+ void setRuntimeSegmentId(int id) { m_runtimeSegmentId = id; }
+
+ // Auto fading of a playable audio file
+ //
+ bool isAutoFading() const { return m_autoFade; }
+ void setAutoFade(bool value) { m_autoFade = value; }
+
+ RealTime getFadeInTime() const { return m_fadeInTime; }
+ void setFadeInTime(const RealTime &time)
+ { m_fadeInTime = time; }
+
+ RealTime getFadeOutTime() const { return m_fadeOutTime; }
+ void setFadeOutTime(const RealTime &time)
+ { m_fadeOutTime = time; }
+
+
+protected:
+ void initialise(size_t bufferSize, size_t smallFileSize);
+ void checkSmallFileCache(size_t smallFileSize);
+ bool scanTo(const RealTime &time);
+ void returnRingBuffers();
+
+ RealTime m_startTime;
+ RealTime m_startIndex;
+ RealTime m_duration;
+
+ // Performance file handle - must open non-blocking to
+ // allow other potential PlayableAudioFiles access to
+ // the same file.
+ //
+ std::ifstream *m_file;
+
+ // AudioFile handle
+ //
+ AudioFile *m_audioFile;
+
+ // Originating Instrument Id
+ //
+ InstrumentId m_instrumentId;
+
+ int m_targetChannels;
+ int m_targetSampleRate;
+
+ bool m_fileEnded;
+ bool m_firstRead;
+ static size_t m_xfadeFrames;
+ int m_runtimeSegmentId;
+
+ static AudioCache m_smallFileCache;
+ bool m_isSmallFile;
+
+ static std::vector<sample_t *> m_workBuffers;
+ static size_t m_workBufferSize;
+
+ static char *m_rawFileBuffer;
+ static size_t m_rawFileBufferSize;
+
+ RingBuffer<sample_t> **m_ringBuffers;
+ static RingBufferPool *m_ringBufferPool;
+
+ RealTime m_currentScanPoint;
+ size_t m_smallFileScanFrame;
+
+ bool m_autoFade;
+ RealTime m_fadeInTime;
+ RealTime m_fadeOutTime;
+
+private:
+ PlayableAudioFile(const PlayableAudioFile &pAF); // not provided
+};
+
+}
+
+#endif
diff --git a/src/sound/PluginFactory.cpp b/src/sound/PluginFactory.cpp
new file mode 100644
index 0000000..49c1014
--- /dev/null
+++ b/src/sound/PluginFactory.cpp
@@ -0,0 +1,120 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "PluginFactory.h"
+#include "PluginIdentifier.h"
+
+#ifdef HAVE_LADSPA
+#include "LADSPAPluginFactory.h"
+#endif
+
+#ifdef HAVE_DSSI
+#include "DSSIPluginFactory.h"
+#endif
+
+#include <iostream>
+
+namespace Rosegarden
+{
+
+int PluginFactory::m_sampleRate = 48000;
+
+#ifdef HAVE_LADSPA
+static LADSPAPluginFactory *_ladspaInstance = 0;
+#endif
+
+#ifdef HAVE_DSSI
+static LADSPAPluginFactory *_dssiInstance = 0;
+#endif
+
+PluginFactory *
+PluginFactory::instance(QString pluginType)
+{
+ if (pluginType == "ladspa") {
+#ifdef HAVE_LADSPA
+ if (!_ladspaInstance) {
+ std::cerr << "PluginFactory::instance(" << pluginType
+ << "): creating new LADSPAPluginFactory" << std::endl;
+ _ladspaInstance = new LADSPAPluginFactory();
+ _ladspaInstance->discoverPlugins();
+ }
+ return _ladspaInstance;
+#else
+
+ return 0;
+#endif
+
+ } else if (pluginType == "dssi") {
+#ifdef HAVE_DSSI
+ if (!_dssiInstance) {
+ std::cerr << "PluginFactory::instance(" << pluginType
+ << "): creating new DSSIPluginFactory" << std::endl;
+ _dssiInstance = new DSSIPluginFactory();
+ _dssiInstance->discoverPlugins();
+ }
+ return _dssiInstance;
+#else
+
+ return 0;
+#endif
+
+ }
+ else
+ return 0;
+}
+
+PluginFactory *
+PluginFactory::instanceFor(QString identifier)
+{
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soName, label);
+ return instance(type);
+}
+
+void
+PluginFactory::enumerateAllPlugins(MappedObjectPropertyList &list)
+{
+ PluginFactory *factory;
+
+ // Plugins can change the locale, store it for reverting afterwards
+ char *loc = setlocale(LC_ALL, 0);
+
+ // Query DSSI plugins before LADSPA ones.
+ // This is to provide for the interesting possibility of plugins
+ // providing either DSSI or LADSPA versions of themselves,
+ // returning both versions if the LADSPA identifiers are queried
+ // first but only the DSSI version if the DSSI identifiers are
+ // queried first.
+
+ factory = instance("dssi");
+ if (factory)
+ factory->enumeratePlugins(list);
+
+ factory = instance("ladspa");
+ if (factory)
+ factory->enumeratePlugins(list);
+
+ setlocale(LC_ALL, loc);
+}
+
+
+}
+
diff --git a/src/sound/PluginFactory.h b/src/sound/PluginFactory.h
new file mode 100644
index 0000000..820b233
--- /dev/null
+++ b/src/sound/PluginFactory.h
@@ -0,0 +1,97 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _PLUGIN_FACTORY_H_
+#define _PLUGIN_FACTORY_H_
+
+#include <qstring.h>
+#include <vector>
+
+#include "MappedCommon.h"
+
+namespace Rosegarden
+{
+
+class RunnablePluginInstance;
+class MappedPluginSlot;
+
+class PluginFactory
+{
+public:
+ static PluginFactory *instance(QString pluginType);
+ static PluginFactory *instanceFor(QString identifier);
+ static void enumerateAllPlugins(MappedObjectPropertyList &);
+
+ static void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; }
+
+ /**
+ * Look up the plugin path and find the plugins in it. Called
+ * automatically after construction of a factory.
+ */
+ virtual void discoverPlugins() = 0;
+
+ /**
+ * Return a reference to a list of all plugin identifiers that can
+ * be created by this factory.
+ */
+ virtual const std::vector<QString> &getPluginIdentifiers() const = 0;
+
+ /**
+ * Append to the given list descriptions of all the available
+ * plugins and their ports. This is in a standard format, see
+ * the LADSPA implementation for details.
+ */
+ virtual void enumeratePlugins(MappedObjectPropertyList &list) = 0;
+
+ /**
+ * Populate the given plugin slot with information about its
+ * plugin. This is called from the plugin slot's set method
+ * when it's been asked to set its plugin identifier. This
+ * method should also destroy and recreate the plugin slot's
+ * port child objects.
+ */
+ virtual void populatePluginSlot(QString identifier,
+ MappedPluginSlot &slot) = 0;
+
+ /**
+ * Instantiate a plugin.
+ */
+ virtual RunnablePluginInstance *instantiatePlugin(QString identifier,
+ int instrumentId,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels) = 0;
+
+protected:
+ PluginFactory() { }
+
+ // for call by RunnablePluginInstance dtor
+ virtual void releasePlugin(RunnablePluginInstance *, QString identifier) = 0;
+ friend class RunnablePluginInstance;
+
+ static int m_sampleRate;
+};
+
+
+}
+
+#endif
diff --git a/src/sound/PluginIdentifier.cpp b/src/sound/PluginIdentifier.cpp
new file mode 100644
index 0000000..25b3317
--- /dev/null
+++ b/src/sound/PluginIdentifier.cpp
@@ -0,0 +1,72 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "PluginIdentifier.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+
+QString
+PluginIdentifier::createIdentifier(QString type,
+ QString soName,
+ QString label)
+{
+ QString identifier = type + ":" + soName + ":" + label;
+ return identifier;
+}
+
+void
+PluginIdentifier::parseIdentifier(QString identifier,
+ QString &type,
+ QString &soName,
+ QString &label)
+{
+ type = identifier.section(':', 0, 0);
+ soName = identifier.section(':', 1, 1);
+ label = identifier.section(':', 2);
+}
+
+bool
+PluginIdentifier::areIdentifiersSimilar(QString id1, QString id2)
+{
+ QString type1, type2, soName1, soName2, label1, label2;
+
+ parseIdentifier(id1, type1, soName1, label1);
+ parseIdentifier(id2, type2, soName2, label2);
+
+ if (type1 != type2 || label1 != label2)
+ return false;
+
+ bool similar = (soName1.section('/', -1).section('.', 0, 0) ==
+ soName2.section('/', -1).section('.', 0, 0));
+
+ return similar;
+}
+
+// The prefix of this key is also used as a literal in base/AudioPluginInstance.C.
+// If you change one, change the other.
+// Better still, don't change one.
+QString
+PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY = "__ROSEGARDEN__:__RESERVED__:ProjectDirectoryKey";
+
+}
+
diff --git a/src/sound/PluginIdentifier.h b/src/sound/PluginIdentifier.h
new file mode 100644
index 0000000..e8519ad
--- /dev/null
+++ b/src/sound/PluginIdentifier.h
@@ -0,0 +1,50 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _PLUGIN_IDENTIFIER_H_
+#define _PLUGIN_IDENTIFIER_H_
+
+#include <qstring.h>
+
+
+// A plugin identifier is simply a string; this class provides methods
+// to parse it into its constituent bits (plugin type, DLL path and label).
+
+namespace Rosegarden {
+
+class PluginIdentifier {
+
+public:
+
+ static QString createIdentifier(QString type, QString soName, QString label);
+
+ static void parseIdentifier(QString identifier,
+ QString &type, QString &soName, QString &label);
+
+ static bool areIdentifiersSimilar(QString id1, QString id2);
+
+ // Not strictly related to identifiers
+ static QString RESERVED_PROJECT_DIRECTORY_KEY;
+};
+
+}
+
+#endif
diff --git a/src/sound/RIFFAudioFile.cpp b/src/sound/RIFFAudioFile.cpp
new file mode 100644
index 0000000..c34435f
--- /dev/null
+++ b/src/sound/RIFFAudioFile.cpp
@@ -0,0 +1,686 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "RIFFAudioFile.h"
+#include "RealTime.h"
+#include "Profiler.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_RIFF
+
+
+namespace Rosegarden
+{
+
+RIFFAudioFile::RIFFAudioFile(unsigned int id,
+ const std::string &name,
+ const std::string &fileName):
+ AudioFile(id, name, fileName),
+ m_subFormat(PCM),
+ m_bytesPerSecond(0),
+ m_bytesPerFrame(0)
+{}
+
+RIFFAudioFile::RIFFAudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bytesPerSecond = 6000,
+ unsigned int bytesPerFrame = 2,
+ unsigned int bitsPerSample = 16):
+ AudioFile(0, "", fileName)
+{
+ m_bitsPerSample = bitsPerSample;
+ m_sampleRate = sampleRate;
+ m_bytesPerSecond = bytesPerSecond;
+ m_bytesPerFrame = bytesPerFrame;
+ m_channels = channels;
+
+ if (bitsPerSample == 16)
+ m_subFormat = PCM;
+ else if (bitsPerSample == 32)
+ m_subFormat = FLOAT;
+ else
+ throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports 16 or 32-bit PCM or IEEE floating-point RIFF files for writing"));
+
+}
+
+RIFFAudioFile::~RIFFAudioFile()
+{}
+
+
+// Show some stats on this file
+//
+void
+RIFFAudioFile::printStats()
+{
+ cout << "filename : " << m_fileName << endl
+ << "channels : " << m_channels << endl
+ << "sample rate : " << m_sampleRate << endl
+ << "bytes per second : " << m_bytesPerSecond << endl
+ << "bits per sample : " << m_bitsPerSample << endl
+ << "bytes per frame : " << m_bytesPerFrame << endl
+ << "file length : " << m_fileSize << " bytes" << endl
+ << endl;
+}
+
+bool
+RIFFAudioFile::appendSamples(const std::string &buffer)
+{
+ /*
+ if (m_outFile == 0 || m_type != WAV)
+ return false;
+ */
+
+ // write out
+ putBytes(m_outFile, buffer);
+
+ return true;
+}
+
+bool
+RIFFAudioFile::appendSamples(const char *buf, unsigned int frames)
+{
+ putBytes(m_outFile, buf, frames * m_bytesPerFrame);
+ return true;
+}
+
+// scan on from a descriptor position
+bool
+RIFFAudioFile::scanForward(std::ifstream *file, const RealTime &time)
+{
+ // sanity
+ if (file == 0)
+ return false;
+
+ unsigned int totalSamples = m_sampleRate * time.sec +
+ ( ( m_sampleRate * time.usec() ) / 1000000 );
+ unsigned int totalBytes = totalSamples * m_bytesPerFrame;
+
+ m_loseBuffer = true;
+
+ // do the seek
+ file->seekg(totalBytes, std::ios::cur);
+
+ if (file->eof())
+ return false;
+
+ return true;
+}
+
+bool
+RIFFAudioFile::scanForward(const RealTime &time)
+{
+ if (*m_inFile)
+ return scanForward(m_inFile, time);
+ else
+ return false;
+}
+
+bool
+RIFFAudioFile::scanTo(const RealTime &time)
+{
+ if (*m_inFile)
+ return scanTo(m_inFile, time);
+ else
+ return false;
+
+}
+
+bool
+RIFFAudioFile::scanTo(std::ifstream *file, const RealTime &time)
+{
+ // sanity
+ if (file == 0)
+ return false;
+
+ // whatever we do here we invalidate the read buffer
+ //
+ m_loseBuffer = true;
+
+ file->clear();
+
+ // seek past header - don't hardcode this - use the file format
+ // spec to get header length and then scoot to that.
+ //
+ file->seekg(16, std::ios::beg);
+
+ unsigned int lengthOfFormat = 0;
+
+ try {
+ lengthOfFormat = getIntegerFromLittleEndian(getBytes(file, 4));
+ file->seekg(lengthOfFormat, std::ios::cur);
+
+ // check we've got data chunk start
+ std::string chunkName;
+ int chunkLength = 0;
+
+ while ((chunkName = getBytes(file, 4)) != "data") {
+ if (file->eof()) {
+ std::cerr << "RIFFAudioFile::scanTo(): failed to find data "
+ << std::endl;
+ return false;
+ }
+//#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::scanTo(): skipping chunk: "
+ << chunkName << std::endl;
+//#endif
+ chunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
+ if (chunkLength < 0) {
+ std::cerr << "RIFFAudioFile::scanTo(): negative chunk length "
+ << chunkLength << " for chunk " << chunkName << std::endl;
+ return false;
+ }
+ file->seekg(chunkLength, std::ios::cur);
+ }
+
+ // get the length of the data chunk, and scan past it as a side-effect
+ chunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
+#ifdef DEBUG_RIFF
+
+ std::cout << "RIFFAudioFile::scanTo() - data chunk size = "
+ << chunkLength << std::endl;
+#endif
+
+ } catch (BadSoundFileException s) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::scanTo - EXCEPTION - \""
+ << s.getMessage() << "\"" << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Ok, we're past all the header information in the data chunk.
+ // Now, how much do we scan forward?
+ //
+ size_t totalFrames = RealTime::realTime2Frame(time, m_sampleRate);
+
+ unsigned int totalBytes = totalFrames * m_bytesPerFrame;
+
+ // When using seekg we have to keep an eye on the boundaries ourselves
+ //
+ if (totalBytes > m_fileSize - (lengthOfFormat + 16 + 8)) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::scanTo() - attempting to move past end of "
+ << "data block" << std::endl;
+#endif
+
+ return false;
+ }
+
+#ifdef DEBUG_RIFF
+ std::cout << "RIFFAudioFile::scanTo - seeking to " << time
+ << " (" << totalBytes << " bytes from current " << file->tellg()
+ << ")" << std::endl;
+#endif
+
+ file->seekg(totalBytes, std::ios::cur);
+
+ return true;
+}
+
+// Get a certain number of sample frames - a frame is a set
+// of samples (all channels) for a given sample quanta.
+//
+// For example, getting one frame of 16-bit stereo will return
+// four bytes of data (two per channel).
+//
+//
+std::string
+RIFFAudioFile::getSampleFrames(std::ifstream *file, unsigned int frames)
+{
+ // sanity
+ if (file == 0)
+ return std::string("");
+
+ // Bytes per sample already takes into account the number
+ // of channels we're using
+ //
+ long totalBytes = frames * m_bytesPerFrame;
+
+ try {
+ return getBytes(file, totalBytes);
+ } catch (BadSoundFileException s) {
+ return "";
+ }
+}
+
+unsigned int
+RIFFAudioFile::getSampleFrames(std::ifstream *file, char *buf,
+ unsigned int frames)
+{
+ if (file == 0)
+ return 0;
+ try {
+ return getBytes(file, buf, frames * m_bytesPerFrame) / m_bytesPerFrame;
+ } catch (BadSoundFileException s) {
+ return 0;
+ }
+}
+
+std::string
+RIFFAudioFile::getSampleFrames(unsigned int frames)
+{
+ if (*m_inFile) {
+ return getSampleFrames(m_inFile, frames);
+ } else {
+ return std::string("");
+ }
+}
+
+// Return a slice of frames over a time period
+//
+std::string
+RIFFAudioFile::getSampleFrameSlice(std::ifstream *file, const RealTime &time)
+{
+ // sanity
+ if (file == 0)
+ return std::string("");
+
+ long totalFrames = RealTime::realTime2Frame(time, m_sampleRate);
+ long totalBytes = totalFrames * m_bytesPerFrame;
+
+ try {
+ return getBytes(file, totalBytes);
+ } catch (BadSoundFileException s) {
+ return "";
+ }
+}
+
+std::string
+RIFFAudioFile::getSampleFrameSlice(const RealTime &time)
+{
+ if (*m_inFile) {
+ return getSampleFrameSlice(m_inFile, time);
+ } else {
+ return std::string("");
+ }
+}
+
+RealTime
+RIFFAudioFile::getLength()
+{
+ // Fixed header size = 44 but prove by getting it from the file too
+ //
+ unsigned int headerLength = 44;
+
+ if (m_inFile) {
+ m_inFile->seekg(16, std::ios::beg);
+ headerLength = getIntegerFromLittleEndian(getBytes(m_inFile, 4));
+ m_inFile->seekg(headerLength, std::ios::cur);
+ headerLength += (16 + 8);
+ }
+
+ if (!m_bytesPerFrame || !m_sampleRate) return RealTime::zeroTime;
+
+ double frames = (m_fileSize - headerLength) / m_bytesPerFrame;
+ double seconds = frames / ((double)m_sampleRate);
+
+ int secs = int(seconds);
+ int nsecs = int((seconds - secs) * 1000000000.0);
+
+ return RealTime(secs, nsecs);
+}
+
+
+// The RIFF file format chunk defines our internal meta data.
+//
+// Courtesy of:
+// http://www.technology.niagarac.on.ca/courses/comp630/WavFileFormat.html
+//
+// 'The WAV file itself consists of three "chunks" of information:
+// The RIFF chunk which identifies the file as a WAV file, The FORMAT
+// chunk which identifies parameters such as sample rate and the DATA
+// chunk which contains the actual data (samples).'
+//
+//
+void
+RIFFAudioFile::readFormatChunk()
+{
+ if (m_inFile == 0)
+ return ;
+
+ m_loseBuffer = true;
+
+ // seek to beginning
+ m_inFile->seekg(0, std::ios::beg);
+
+ // get the header string
+ //
+ std::string hS = getBytes(36);
+
+ // Look for the RIFF identifier and bomb out if we don't find it
+ //
+#if (__GNUC__ < 3)
+
+ if (hS.compare(AUDIO_RIFF_ID, 0, 4) != 0)
+#else
+
+ if (hS.compare(0, 4, AUDIO_RIFF_ID) != 0)
+#endif
+
+ {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::readFormatChunk - "
+ << "can't find RIFF identifier\n";
+#endif
+
+ throw(BadSoundFileException(m_fileName, "RIFFAudioFile::readFormatChunk - can't find RIFF identifier"));
+ }
+
+ // Look for the WAV identifier
+ //
+#if (__GNUC__ < 3)
+ if (hS.compare(AUDIO_WAVE_ID, 8, 4) != 0)
+#else
+
+ if (hS.compare(8, 4, AUDIO_WAVE_ID) != 0)
+#endif
+
+ {
+#ifdef DEBUG_RIFF
+ std::cerr << "Can't find WAV identifier\n";
+#endif
+
+ throw(BadSoundFileException(m_fileName, "Can't find WAV identifier"));
+ }
+
+ // Look for the FORMAT identifier - note that this doesn't actually
+ // have to be in the first chunk we come across, but for the moment
+ // this is the only place we check for it because I'm lazy.
+ //
+ //
+#if (__GNUC__ < 3)
+ if (hS.compare(AUDIO_FORMAT_ID, 12, 4) != 0)
+#else
+
+ if (hS.compare(12, 4, AUDIO_FORMAT_ID) != 0)
+#endif
+
+ {
+#ifdef DEBUG_RIFF
+ std::cerr << "Can't find FORMAT identifier\n";
+#endif
+
+ throw(BadSoundFileException(m_fileName, "Can't find FORMAT identifier"));
+ }
+
+ // Little endian conversion of length bytes into file length
+ // (add on eight for RIFF id and length field and compare to
+ // real file size).
+ //
+ unsigned int length = getIntegerFromLittleEndian(hS.substr(4, 4)) + 8;
+
+ if (length != m_fileSize) {
+ std::cerr << "WARNING: RIFFAudioFile: incorrect length ("
+ << length << ", file size is " << m_fileSize << "), ignoring"
+ << std::endl;
+ length = m_fileSize;
+ }
+
+ // Check the format length
+ //
+ unsigned int lengthOfFormat = getIntegerFromLittleEndian(hS.substr(16, 4));
+
+ // Make sure we step to the end of the format chunk ignoring the
+ // tail if it exists
+ //
+ if (lengthOfFormat > 0x10) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::readFormatChunk - "
+ << "extended Format Chunk (" << lengthOfFormat << ")"
+ << std::endl;
+#endif
+
+ // ignore any overlapping bytes
+ m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur);
+ } else if (lengthOfFormat < 0x10) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::readFormatChunk - "
+ << "truncated Format Chunk (" << lengthOfFormat << ")"
+ << std::endl;
+#endif
+
+ m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur);
+ //throw(BadSoundFileException(m_fileName, "Format chunk too short"));
+ }
+
+
+ // Check sub format - we support PCM or IEEE floating point.
+ //
+ unsigned int subFormat = getIntegerFromLittleEndian(hS.substr(20, 2));
+
+ if (subFormat == 0x01) {
+ m_subFormat = PCM;
+ } else if (subFormat == 0x03) {
+ m_subFormat = FLOAT;
+ } else {
+ throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports PCM or IEEE floating-point RIFF files"));
+ }
+
+ // We seem to have a good looking .WAV file - extract the
+ // sample information and populate this locally
+ //
+ unsigned int channelNumbers = getIntegerFromLittleEndian(hS.substr(22, 2));
+
+ switch (channelNumbers) {
+ case 0x01:
+ case 0x02:
+ m_channels = channelNumbers;
+ break;
+
+ default: {
+ throw(BadSoundFileException(m_fileName, "Unsupported number of channels"));
+ }
+ break;
+ }
+
+ // Now the rest of the information
+ //
+ m_sampleRate = getIntegerFromLittleEndian(hS.substr(24, 4));
+ m_bytesPerSecond = getIntegerFromLittleEndian(hS.substr(28, 4));
+ m_bytesPerFrame = getIntegerFromLittleEndian(hS.substr(32, 2));
+ m_bitsPerSample = getIntegerFromLittleEndian(hS.substr(34, 2));
+
+ if (m_subFormat == PCM) {
+ if (m_bitsPerSample != 8 && m_bitsPerSample != 16 && m_bitsPerSample != 24) {
+ throw BadSoundFileException("Rosegarden currently only supports 8-, 16- or 24-bit PCM in RIFF files");
+ }
+ } else if (m_subFormat == FLOAT) {
+ if (m_bitsPerSample != 32) {
+ throw BadSoundFileException("Rosegarden currently only supports 32-bit floating-point in RIFF files");
+ }
+ }
+
+ // printStats();
+
+}
+
+// Write out the format chunk from our internal data
+//
+void
+RIFFAudioFile::writeFormatChunk()
+{
+ if (m_outFile == 0 || m_type != WAV)
+ return ;
+
+ std::string outString;
+
+ // RIFF type is all we support for the moment
+ outString += AUDIO_RIFF_ID;
+
+ // Now write the total length of the file minus these first 8 bytes.
+ // We won't know this until we've finished recording the file.
+ //
+ outString += "0000";
+
+ // WAV file is all we support
+ //
+ outString += AUDIO_WAVE_ID;
+
+ // Begin the format chunk
+ outString += AUDIO_FORMAT_ID;
+
+ // length
+ //cout << "LENGTH = " << getLittleEndianFromInteger(0x10, 4) << endl;
+ outString += getLittleEndianFromInteger(0x10, 4);
+
+ // 1 for PCM, 3 for float
+ if (m_subFormat == PCM) {
+ outString += getLittleEndianFromInteger(0x01, 2);
+ } else {
+ outString += getLittleEndianFromInteger(0x03, 2);
+ }
+
+ // channel
+ outString += getLittleEndianFromInteger(m_channels, 2);
+
+ // sample rate
+ outString += getLittleEndianFromInteger(m_sampleRate, 4);
+
+ // bytes per second
+ outString += getLittleEndianFromInteger(m_bytesPerSecond, 4);
+
+ // bytes per sample
+ outString += getLittleEndianFromInteger(m_bytesPerFrame, 2);
+
+ // bits per sample
+ outString += getLittleEndianFromInteger(m_bitsPerSample, 2);
+
+ // Now mark the beginning of the "data" chunk and leave the file
+ // open for writing.
+ outString += "data";
+
+ // length of data to follow - again needs to be written after
+ // we've completed the file.
+ //
+ outString += "0000";
+
+ // write out
+ //
+ putBytes(m_outFile, outString);
+}
+
+
+AudioFileType
+RIFFAudioFile::identifySubType(const std::string &filename)
+{
+ std::ifstream *testFile =
+ new std::ifstream(filename.c_str(), std::ios::in | std::ios::binary);
+
+ if (!(*testFile))
+ return UNKNOWN;
+
+ std::string hS;
+ unsigned int numberOfBytes = 36;
+ char *bytes = new char[numberOfBytes];
+
+ testFile->read(bytes, numberOfBytes);
+ for (unsigned int i = 0; i < numberOfBytes; i++)
+ hS += (unsigned char)bytes[i];
+
+ AudioFileType type = UNKNOWN;
+
+ // Test for BWF first because it's an extension of a plain WAV
+ //
+#if (__GNUC__ < 3)
+
+ if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 &&
+ hS.compare(AUDIO_WAVE_ID, 8, 4) == 0 &&
+ hS.compare(AUDIO_BWF_ID, 12, 4) == 0)
+#else
+
+ if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 &&
+ hS.compare(8, 4, AUDIO_WAVE_ID) == 0 &&
+ hS.compare(12, 4, AUDIO_BWF_ID) == 0)
+#endif
+
+ {
+ type = BWF;
+ }
+ // Now for a WAV
+#if (__GNUC__ < 3)
+ else if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 &&
+ hS.compare(AUDIO_WAVE_ID, 8, 4) == 0)
+#else
+
+ else if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 &&
+ hS.compare(8, 4, AUDIO_WAVE_ID) == 0)
+#endif
+
+ {
+ type = WAV;
+ } else
+ type = UNKNOWN;
+
+ testFile->close();
+ delete [] bytes;
+
+ return type;
+}
+
+float
+RIFFAudioFile::convertBytesToSample(const unsigned char *ubuf)
+{
+ switch (getBitsPerSample()) {
+
+ case 8: {
+ // WAV stores 8-bit samples unsigned, other sizes signed.
+ return (float)(ubuf[0] - 128.0) / 128.0;
+ }
+
+ case 16: {
+ // Two's complement little-endian 16-bit integer.
+ // We convert endianness (if necessary) but assume 16-bit short.
+ unsigned char b2 = ubuf[0];
+ unsigned char b1 = ubuf[1];
+ unsigned int bits = (b1 << 8) + b2;
+ return (float)(short(bits)) / 32767.0;
+ }
+
+ case 24: {
+ // Two's complement little-endian 24-bit integer.
+ // Again, convert endianness but assume 32-bit int.
+ unsigned char b3 = ubuf[0];
+ unsigned char b2 = ubuf[1];
+ unsigned char b1 = ubuf[2];
+ // Rotate 8 bits too far in order to get the sign bit
+ // in the right place; this gives us a 32-bit value,
+ // hence the larger float divisor
+ unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8);
+ return (float)(int(bits)) / 2147483647.0;
+ }
+
+ case 32: {
+ // IEEE floating point
+ return *(float *)ubuf;
+ }
+
+ default:
+ return 0.0f;
+ }
+}
+
+}
+
diff --git a/src/sound/RIFFAudioFile.h b/src/sound/RIFFAudioFile.h
new file mode 100644
index 0000000..a846306
--- /dev/null
+++ b/src/sound/RIFFAudioFile.h
@@ -0,0 +1,168 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+// Resource Interchange File Formt - a chunk based audio
+// file format. Type of chunk varies with specialisation
+// of this class - WAV files are a specialisation with just
+// a format chunk, BWF has more chunks.
+//
+//
+
+#ifndef _RIFFAUDIOFILE_H_
+#define _RIFFAUDIOFILE_H_
+
+#include <string>
+#include <vector>
+
+#include "AudioFile.h"
+#include "RealTime.h"
+
+namespace Rosegarden
+{
+
+class RIFFAudioFile : public AudioFile
+{
+public:
+ RIFFAudioFile(unsigned int id,
+ const std::string &name,
+ const std::string &fileName);
+
+ RIFFAudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerFrame,
+ unsigned int bitsPerSample);
+
+ ~RIFFAudioFile();
+
+ typedef enum {
+ PCM,
+ FLOAT
+ } SubFormat;
+
+ // Our main control methods - again keeping abstract at this level
+ //
+ //virtual bool open() = 0;
+ //virtual bool write() = 0;
+ //virtual void close() = 0;
+
+ // Show the information we have on this file
+ //
+ virtual void printStats();
+
+ // Slightly dodgy code here - we keep these functions here
+ // because I don't want to duplicate them in PlayableRIFFAudioFile
+ // and also don't want that class to inherit this one.
+ //
+ // Of course the file handle we use in might be pointing to
+ // any file - for the most part we just assume it's an audio
+ // file.
+ //
+ //
+ // 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.
+ //
+ virtual bool scanTo(const RealTime &time);
+ virtual bool scanTo(std::ifstream *file, const RealTime &time);
+
+ // Scan forward in a file by a certain amount of time
+ //
+ virtual bool scanForward(const RealTime &time);
+ virtual bool scanForward(std::ifstream *file, const RealTime &time);
+
+ // 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);
+ virtual unsigned int getSampleFrames(std::ifstream *file,
+ char *buf,
+ unsigned int frames);
+ virtual std::string getSampleFrames(unsigned int frames);
+
+ // 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);
+ virtual std::string getSampleFrameSlice(const RealTime &time);
+
+ // Append a string of samples to an already open (for writing)
+ // audio file.
+ //
+ virtual bool appendSamples(const std::string &buffer);
+ virtual bool appendSamples(const char *buf, unsigned int frames);
+
+ // Get the length of the sample in Seconds/Microseconds
+ //
+ virtual RealTime getLength();
+
+ // Accessors
+ //
+ virtual unsigned int getBytesPerFrame() { return m_bytesPerFrame; }
+ unsigned int getBytesPerSecond() { return m_bytesPerSecond; }
+
+ // Allow easy identification of wav file type
+ //
+ static AudioFileType identifySubType(const std::string &filename);
+
+ // Convert a single sample from byte format, given the right
+ // number of bytes for the sample width
+ float convertBytesToSample(const unsigned char *bytes);
+
+ // 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<float *> &targetData,
+ bool addToResultBuffers = false) = 0;
+
+protected:
+ //virtual void parseHeader(const std::string &header);
+ //virtual void parseBody();
+
+ // Find and read in the format chunk of a RIFF file - without
+ // this chunk we don't actually have a RIFF file.
+ //
+ void readFormatChunk();
+
+ // Write out the Format chunk from the internal data we have
+ //
+ void writeFormatChunk();
+
+ SubFormat m_subFormat;
+ unsigned int m_bytesPerSecond;
+ unsigned int m_bytesPerFrame;
+};
+
+}
+
+
+#endif // _RIFFAUDIOFILE_H_
diff --git a/src/sound/RecordableAudioFile.cpp b/src/sound/RecordableAudioFile.cpp
new file mode 100644
index 0000000..09420dd
--- /dev/null
+++ b/src/sound/RecordableAudioFile.cpp
@@ -0,0 +1,164 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "RecordableAudioFile.h"
+
+#include <cstdlib>
+
+//#define DEBUG_RECORDABLE 1
+
+namespace Rosegarden
+{
+
+RecordableAudioFile::RecordableAudioFile(AudioFile *audioFile,
+ size_t bufferSize) :
+ m_audioFile(audioFile),
+ m_status(IDLE)
+{
+ for (unsigned int ch = 0; ch < audioFile->getChannels(); ++ch) {
+
+ m_ringBuffers.push_back(new RingBuffer<sample_t>(bufferSize));
+
+ if (!m_ringBuffers[ch]->mlock()) {
+ std::cerr << "WARNING: RecordableAudioFile::initialise: couldn't lock buffer into real memory, performance may be impaired" << std::endl;
+ }
+ }
+}
+
+RecordableAudioFile::~RecordableAudioFile()
+{
+ write();
+ m_audioFile->close();
+ delete m_audioFile;
+
+ for (size_t i = 0; i < m_ringBuffers.size(); ++i) {
+ delete m_ringBuffers[i];
+ }
+}
+
+size_t
+RecordableAudioFile::buffer(const sample_t *data, int channel, size_t frames)
+{
+ if (channel >= int(m_ringBuffers.size())) {
+ std::cerr << "RecordableAudioFile::buffer: No such channel as "
+ << channel << std::endl;
+ return 0;
+ }
+
+ size_t available = m_ringBuffers[channel]->getWriteSpace();
+
+ if (frames > available) {
+ std::cerr << "RecordableAudioFile::buffer: buffer maxed out!" << std::endl;
+ frames = available;
+ }
+
+#ifdef DEBUG_RECORDABLE
+ std::cerr << "RecordableAudioFile::buffer: buffering " << frames << " frames on channel " << channel << std::endl;
+#endif
+
+ m_ringBuffers[channel]->write(data, frames);
+ return frames;
+}
+
+void
+RecordableAudioFile::write()
+{
+ // Use a static buffer -- this obviously requires that write() is
+ // only called from a single thread
+ static size_t bufferSize = 0;
+ static sample_t *buffer = 0;
+ static char *encodeBuffer = 0;
+
+ unsigned int bits = m_audioFile->getBitsPerSample();
+
+ if (bits != 16 && bits != 32) {
+ std::cerr << "ERROR: RecordableAudioFile::write: file has " << bits
+ << " bits per sample; only 16 or 32 are supported" << std::endl;
+ return ;
+ }
+
+ unsigned int channels = m_audioFile->getChannels();
+ unsigned char b1, b2;
+
+ // We need the same amount of available data on every channel
+ size_t s = 0;
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ size_t available = m_ringBuffers[ch]->getReadSpace();
+#ifdef DEBUG_RECORDABLE
+
+ std::cerr << "RecordableAudioFile::write: " << available << " frames available to write on channel " << ch << std::endl;
+#endif
+
+ if (ch == 0 || available < s)
+ s = available;
+ }
+ if (s == 0)
+ return ;
+
+ size_t bufferReqd = channels * s;
+ if (bufferReqd > bufferSize) {
+ if (buffer) {
+ buffer = (sample_t *)realloc(buffer, bufferReqd * sizeof(sample_t));
+ encodeBuffer = (char *)realloc(encodeBuffer, bufferReqd * 4);
+ } else {
+ buffer = (sample_t *) malloc(bufferReqd * sizeof(sample_t));
+ encodeBuffer = (char *)malloc(bufferReqd * 4);
+ }
+ bufferSize = bufferReqd;
+ }
+
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ m_ringBuffers[ch]->read(buffer + ch * s, s);
+ }
+
+ // interleave and convert
+
+ if (bits == 16) {
+ size_t index = 0;
+ for (size_t i = 0; i < s; ++i) {
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ float sample = buffer[i + ch * s];
+ b2 = (unsigned char)((long)(sample * 32767.0) & 0xff);
+ b1 = (unsigned char)((long)(sample * 32767.0) >> 8);
+ encodeBuffer[index++] = b2;
+ encodeBuffer[index++] = b1;
+ }
+ }
+ } else {
+ char *encodePointer = encodeBuffer;
+ for (size_t i = 0; i < s; ++i) {
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ float sample = buffer[i + ch * s];
+ *(float *)encodePointer = sample;
+ encodePointer += sizeof(float);
+ }
+ }
+ }
+
+#ifdef DEBUG_RECORDABLE
+ std::cerr << "RecordableAudioFile::write: writing " << s << " frames at " << channels << " channels and " << bits << " bits to file" << std::endl;
+#endif
+
+ m_audioFile->appendSamples(encodeBuffer, s);
+}
+
+}
+
diff --git a/src/sound/RecordableAudioFile.h b/src/sound/RecordableAudioFile.h
new file mode 100644
index 0000000..06df6f0
--- /dev/null
+++ b/src/sound/RecordableAudioFile.h
@@ -0,0 +1,68 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RECORDABLE_AUDIO_FILE_H_
+#define _RECORDABLE_AUDIO_FILE_H_
+
+#include "RingBuffer.h"
+#include "AudioFile.h"
+
+#include <vector>
+
+namespace Rosegarden
+{
+
+// A wrapper class for writing out a recording file. We assume the
+// data is provided by a process thread and the writes are requested
+// by a disk thread.
+//
+class RecordableAudioFile
+{
+public:
+ typedef float sample_t;
+
+ typedef enum
+ {
+ IDLE,
+ RECORDING,
+ DEFUNCT
+ } RecordStatus;
+
+ RecordableAudioFile(AudioFile *audioFile, // should be already open for writing
+ size_t bufferSize);
+ ~RecordableAudioFile();
+
+ void setStatus(const RecordStatus &status) { m_status = status; }
+ RecordStatus getStatus() const { return m_status; }
+
+ size_t buffer(const sample_t *data, int channel, size_t frames);
+ void write();
+
+protected:
+ AudioFile *m_audioFile;
+ RecordStatus m_status;
+
+ std::vector<RingBuffer<sample_t> *> m_ringBuffers; // one per channel
+};
+
+}
+
+#endif
diff --git a/src/sound/RingBuffer.h b/src/sound/RingBuffer.h
new file mode 100644
index 0000000..0cc5dc6
--- /dev/null
+++ b/src/sound/RingBuffer.h
@@ -0,0 +1,572 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RINGBUFFER_H_
+#define _RINGBUFFER_H_
+
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include "Scavenger.h"
+
+//#define DEBUG_RINGBUFFER 1
+//#define DEBUG_RINGBUFFER_CREATE_DESTROY 1
+
+#ifdef DEBUG_RINGBUFFER
+#define DEBUG_RINGBUFFER_CREATE_DESTROY 1
+#endif
+
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+#include <iostream>
+static int __extant_ringbuffers = 0;
+#endif
+
+namespace Rosegarden {
+
+/**
+ * RingBuffer implements a lock-free ring buffer for one writer and N
+ * readers, that is to be used to store a sample type T.
+ *
+ * For efficiency, RingBuffer frequently initialises samples by
+ * writing zeroes into their memory space, so T should normally be a
+ * simple type that can safely be set to zero using memset.
+ */
+
+template <typename T, int N = 1>
+class RingBuffer
+{
+public:
+ /**
+ * Create a ring buffer with room to write n samples.
+ *
+ * Note that the internal storage size will actually be n+1
+ * samples, as one element is unavailable for administrative
+ * reasons. Since the ring buffer performs best if its size is a
+ * power of two, this means n should ideally be some power of two
+ * minus one.
+ */
+ RingBuffer(size_t n);
+
+ virtual ~RingBuffer();
+
+ /**
+ * Return the total capacity of the ring buffer in samples.
+ * (This is the argument n passed to the constructor.)
+ */
+ size_t getSize() const;
+
+ /**
+ * Resize the ring buffer. This also empties it. Actually swaps
+ * in a new, larger buffer; the old buffer is scavenged after a
+ * seemly delay. Should be called from the write thread.
+ */
+ void resize(size_t newSize);
+
+ /**
+ * Lock the ring buffer into physical memory. Returns true
+ * for success.
+ */
+ bool mlock();
+
+ /**
+ * Unlock the ring buffer from physical memory. Returns true for
+ * success.
+ */
+ bool munlock();
+
+ /**
+ * Reset read and write pointers, thus emptying the buffer.
+ * Should be called from the write thread.
+ */
+ void reset();
+
+ /**
+ * Return the amount of data available for reading by reader R, in
+ * samples.
+ */
+ size_t getReadSpace(int R = 0) const;
+
+ /**
+ * Return the amount of space available for writing, in samples.
+ */
+ size_t getWriteSpace() const;
+
+ /**
+ * Read n samples from the buffer, for reader R. If fewer than n
+ * are available, the remainder will be zeroed out. Returns the
+ * number of samples actually read.
+ */
+ size_t read(T *destination, size_t n, int R = 0);
+
+ /**
+ * Read n samples from the buffer, for reader R, adding them to
+ * the destination. If fewer than n are available, the remainder
+ * will be left alone. Returns the number of samples actually
+ * read.
+ */
+ size_t readAdding(T *destination, size_t n, int R = 0);
+
+ /**
+ * Read one sample from the buffer, for reader R. If no sample is
+ * available, this will silently return zero. Calling this
+ * repeatedly is obviously slower than calling read once, but it
+ * may be good enough if you don't want to allocate a buffer to
+ * read into.
+ */
+ T readOne(int R = 0);
+
+ /**
+ * Read n samples from the buffer, if available, for reader R,
+ * without advancing the read pointer -- i.e. a subsequent read()
+ * or skip() will be necessary to empty the buffer. If fewer than
+ * n are available, the remainder will be zeroed out. Returns the
+ * number of samples actually read.
+ */
+ size_t peek(T *destination, size_t n, int R = 0) const;
+
+ /**
+ * Read one sample from the buffer, if available, without
+ * advancing the read pointer -- i.e. a subsequent read() or
+ * skip() will be necessary to empty the buffer. Returns zero if
+ * no sample was available.
+ */
+ T peek(int R = 0) const;
+
+ /**
+ * Pretend to read n samples from the buffer, for reader R,
+ * without actually returning them (i.e. discard the next n
+ * samples). Returns the number of samples actually available for
+ * discarding.
+ */
+ size_t skip(size_t n, int R = 0);
+
+ /**
+ * Write n samples to the buffer. If insufficient space is
+ * available, not all samples may actually be written. Returns
+ * the number of samples actually written.
+ */
+ size_t write(const T *source, size_t n);
+
+ /**
+ * Write n zero-value samples to the buffer. If insufficient
+ * space is available, not all zeros may actually be written.
+ * Returns the number of zeroes actually written.
+ */
+ size_t zero(size_t n);
+
+protected:
+ T *m_buffer;
+ volatile size_t m_writer;
+ volatile size_t m_readers[N];
+ size_t m_size;
+ bool m_mlocked;
+
+ static Scavenger<ScavengerArrayWrapper<T> > m_scavenger;
+
+private:
+ RingBuffer(const RingBuffer &); // not provided
+ RingBuffer &operator=(const RingBuffer &); // not provided
+};
+
+template <typename T, int N>
+Scavenger<ScavengerArrayWrapper<T> > RingBuffer<T, N>::m_scavenger;
+
+template <typename T, int N>
+RingBuffer<T, N>::RingBuffer(size_t n) :
+ m_buffer(new T[n + 1]),
+ m_writer(0),
+ m_size(n + 1),
+ m_mlocked(false)
+{
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::RingBuffer(" << n << ") [now have " << (++__extant_ringbuffers) << "]" << std::endl;
+#endif
+
+ for (int i = 0; i < N; ++i) m_readers[i] = 0;
+
+ m_scavenger.scavenge();
+}
+
+template <typename T, int N>
+RingBuffer<T, N>::~RingBuffer()
+{
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::~RingBuffer [now have " << (--__extant_ringbuffers) << "]" << std::endl;
+#endif
+
+ if (m_mlocked) {
+ ::munlock((void *)m_buffer, m_size * sizeof(T));
+ }
+ delete[] m_buffer;
+
+ m_scavenger.scavenge();
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::getSize() const
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::getSize(): " << m_size-1 << std::endl;
+#endif
+
+ return m_size - 1;
+}
+
+template <typename T, int N>
+void
+RingBuffer<T, N>::resize(size_t newSize)
+{
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::resize(" << newSize << ")" << std::endl;
+#endif
+
+ m_scavenger.scavenge();
+
+ if (m_mlocked) {
+ ::munlock((void *)m_buffer, m_size * sizeof(T));
+ }
+
+ m_scavenger.claim(new ScavengerArrayWrapper<T>(m_buffer));
+
+ reset();
+ m_buffer = new T[newSize + 1];
+ m_size = newSize + 1;
+
+ if (m_mlocked) {
+ if (::mlock((void *)m_buffer, m_size * sizeof(T))) {
+ m_mlocked = false;
+ }
+ }
+}
+
+template <typename T, int N>
+bool
+RingBuffer<T, N>::mlock()
+{
+ if (::mlock((void *)m_buffer, m_size * sizeof(T))) return false;
+ m_mlocked = true;
+ return true;
+}
+
+template <typename T, int N>
+bool
+RingBuffer<T, N>::munlock()
+{
+ if (::munlock((void *)m_buffer, m_size * sizeof(T))) return false;
+ m_mlocked = false;
+ return true;
+}
+
+template <typename T, int N>
+void
+RingBuffer<T, N>::reset()
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::reset" << std::endl;
+#endif
+
+ m_writer = 0;
+ for (int i = 0; i < N; ++i) m_readers[i] = 0;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::getReadSpace(int R) const
+{
+ size_t writer = m_writer;
+ size_t reader = m_readers[R];
+ size_t space = 0;
+
+ if (writer > reader) space = writer - reader;
+ else space = ((writer + m_size) - reader) % m_size;
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::getReadSpace(" << R << "): " << space << std::endl;
+#endif
+
+ return space;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::getWriteSpace() const
+{
+ size_t space = 0;
+ for (int i = 0; i < N; ++i) {
+ size_t here = (m_readers[i] + m_size - m_writer - 1) % m_size;
+ if (i == 0 || here < space) space = here;
+ }
+
+#ifdef DEBUG_RINGBUFFER
+ size_t rs(getReadSpace()), rp(m_readers[0]);
+
+ std::cerr << "RingBuffer: write space " << space << ", read space "
+ << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl;
+ std::cerr << "RingBuffer: reader " << rp << ", writer " << m_writer << std::endl;
+#endif
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::getWriteSpace(): " << space << std::endl;
+#endif
+
+ return space;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::read(T *destination, size_t n, int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::read(dest, " << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ memset(destination + available, 0, (n - available) * sizeof(T));
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_readers[R];
+ if (here >= n) {
+ memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
+ } else {
+ memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
+ memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
+ }
+
+ m_readers[R] = (m_readers[R] + n) % m_size;
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::read: read " << n << ", reader now " << m_readers[R] << std::endl;
+#endif
+
+ return n;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::readAdding(T *destination, size_t n, int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::readAdding(dest, " << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_readers[R];
+
+ if (here >= n) {
+ for (size_t i = 0; i < n; ++i) {
+ destination[i] += (m_buffer + m_readers[R])[i];
+ }
+ } else {
+ for (size_t i = 0; i < here; ++i) {
+ destination[i] += (m_buffer + m_readers[R])[i];
+ }
+ for (size_t i = 0; i < (n - here); ++i) {
+ destination[i + here] += m_buffer[i];
+ }
+ }
+
+ m_readers[R] = (m_readers[R] + n) % m_size;
+ return n;
+}
+
+template <typename T, int N>
+T
+RingBuffer<T, N>::readOne(int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::readOne(" << R << ")" << std::endl;
+#endif
+
+ if (m_writer == m_readers[R]) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: No sample available"
+ << std::endl;
+#endif
+ T t;
+ memset(&t, 0, sizeof(T));
+ return t;
+ }
+ T value = m_buffer[m_readers[R]];
+ if (++m_readers[R] == m_size) m_readers[R] = 0;
+ return value;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::peek(T *destination, size_t n, int R) const
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek(dest, " << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ memset(destination + available, 0, (n - available) * sizeof(T));
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_readers[R];
+ if (here >= n) {
+ memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
+ } else {
+ memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
+ memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
+ }
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek: read " << n << std::endl;
+#endif
+
+ return n;
+}
+
+template <typename T, int N>
+T
+RingBuffer<T, N>::peek(int R) const
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek(" << R << ")" << std::endl;
+#endif
+
+ if (m_writer == m_readers[R]) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: No sample available"
+ << std::endl;
+#endif
+ T t;
+ memset(&t, 0, sizeof(T));
+ return t;
+ }
+ T value = m_buffer[m_readers[R]];
+ return value;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::skip(size_t n, int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::skip(" << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+ m_readers[R] = (m_readers[R] + n) % m_size;
+ return n;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::write(const T *source, size_t n)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::write(" << n << ")" << std::endl;
+#endif
+
+ size_t available = getWriteSpace();
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only room for " << available << " samples"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_writer;
+ if (here >= n) {
+ memcpy(m_buffer + m_writer, source, n * sizeof(T));
+ } else {
+ memcpy(m_buffer + m_writer, source, here * sizeof(T));
+ memcpy(m_buffer, source + here, (n - here) * sizeof(T));
+ }
+
+ m_writer = (m_writer + n) % m_size;
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::write: wrote " << n << ", writer now " << m_writer << std::endl;
+#endif
+
+ return n;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::zero(size_t n)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::zero(" << n << ")" << std::endl;
+#endif
+
+ size_t available = getWriteSpace();
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only room for " << available << " samples"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_writer;
+ if (here >= n) {
+ memset(m_buffer + m_writer, 0, n * sizeof(T));
+ } else {
+ memset(m_buffer + m_writer, 0, here * sizeof(T));
+ memset(m_buffer, 0, (n - here) * sizeof(T));
+ }
+
+ m_writer = (m_writer + n) % m_size;
+ return n;
+}
+
+}
+
+#endif // _RINGBUFFER_H_
diff --git a/src/sound/RosegardenMidiRecord.mcopclass b/src/sound/RosegardenMidiRecord.mcopclass
new file mode 100644
index 0000000..41593f1
--- /dev/null
+++ b/src/sound/RosegardenMidiRecord.mcopclass
@@ -0,0 +1,5 @@
+Interface=RosegardenMidiRecord, Arts::MidiPort, Arts::Object
+Library=libRosegardenSequencer.la
+Language=C++
+Author="Richard Bown <bownie@bownie.com>", "Guillaume Laurent <glaurent@telegraph-road.org>", "Chris Cannam <cannam@all-day-breakfast.com>"
+URL="http://home"
diff --git a/src/sound/RunnablePluginInstance.cpp b/src/sound/RunnablePluginInstance.cpp
new file mode 100644
index 0000000..820aaf9
--- /dev/null
+++ b/src/sound/RunnablePluginInstance.cpp
@@ -0,0 +1,42 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "RunnablePluginInstance.h"
+#include "PluginFactory.h"
+
+#include <iostream>
+
+namespace Rosegarden
+{
+
+RunnablePluginInstance::~RunnablePluginInstance()
+{
+// std::cerr << "RunnablePluginInstance::~RunnablePluginInstance" << std::endl;
+
+ if (m_factory) {
+// std::cerr << "Asking factory to release " << m_identifier << std::endl;
+
+ m_factory->releasePlugin(this, m_identifier);
+ }
+}
+
+}
+
diff --git a/src/sound/RunnablePluginInstance.h b/src/sound/RunnablePluginInstance.h
new file mode 100644
index 0000000..f15f146
--- /dev/null
+++ b/src/sound/RunnablePluginInstance.h
@@ -0,0 +1,114 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RUNNABLE_PLUGIN_INSTANCE_H_
+#define _RUNNABLE_PLUGIN_INSTANCE_H_
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+
+#include "RealTime.h"
+
+namespace Rosegarden
+{
+
+class PluginFactory;
+
+/**
+ * RunnablePluginInstance is a very trivial interface that an audio
+ * process can use to refer to an instance of a plugin without needing
+ * to know what type of plugin it is.
+ *
+ * The audio code calls run() on an instance that has been passed to
+ * it, and assumes that the passing code has already initialised the
+ * plugin, connected its inputs and outputs and so on, and that there
+ * is an understanding in place about the sizes of the buffers in use
+ * by the plugin. All of this depends on the subclass implementation.
+ */
+
+class RunnablePluginInstance
+{
+public:
+ typedef float sample_t;
+
+ virtual ~RunnablePluginInstance();
+
+ virtual bool isOK() const = 0;
+
+ virtual QString getIdentifier() const = 0;
+
+ /**
+ * Run for one block, starting at the given time. The start time
+ * may be of interest to synths etc that may have queued events
+ * waiting. Other plugins can ignore it.
+ */
+ virtual void run(const RealTime &blockStartTime) = 0;
+
+ virtual size_t getBufferSize() = 0;
+
+ virtual size_t getAudioInputCount() = 0;
+ virtual size_t getAudioOutputCount() = 0;
+
+ virtual sample_t **getAudioInputBuffers() = 0;
+ virtual sample_t **getAudioOutputBuffers() = 0;
+
+ virtual QStringList getPrograms() { return QStringList(); }
+ virtual QString getCurrentProgram() { return QString(); }
+ virtual QString getProgram(int /* bank */, int /* program */) { return QString(); }
+ virtual unsigned long getProgram(QString /* name */) { return 0; } // bank << 16 + program
+ virtual void selectProgram(QString) { }
+
+ virtual void setPortValue(unsigned int port, float value) = 0;
+ virtual float getPortValue(unsigned int port) = 0;
+
+ virtual QString configure(QString /* key */, QString /* value */) { return QString(); }
+
+ virtual void sendEvent(const RealTime & /* eventTime */,
+ const void * /* event */) { }
+
+ virtual bool isBypassed() const = 0;
+ virtual void setBypassed(bool value) = 0;
+
+ // This should be called after setup, but while not actually playing.
+ virtual size_t getLatency() = 0;
+
+ virtual void silence() = 0;
+ virtual void discardEvents() { }
+ virtual void setIdealChannelCount(size_t channels) = 0; // must also silence(); may also re-instantiate
+
+ void setFactory(PluginFactory *f) { m_factory = f; } // ew
+
+protected:
+ RunnablePluginInstance(PluginFactory *factory, QString identifier) :
+ m_factory(factory), m_identifier(identifier) { }
+
+ PluginFactory *m_factory;
+ QString m_identifier;
+
+ friend class PluginFactory;
+};
+
+typedef std::vector<RunnablePluginInstance *> RunnablePluginInstances;
+
+}
+
+#endif
diff --git a/src/sound/SF2PatchExtractor.cpp b/src/sound/SF2PatchExtractor.cpp
new file mode 100644
index 0000000..6ba8dc5
--- /dev/null
+++ b/src/sound/SF2PatchExtractor.cpp
@@ -0,0 +1,217 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "SF2PatchExtractor.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <map>
+#include <sys/types.h>
+
+namespace Rosegarden
+{
+
+using std::string;
+using std::cerr;
+using std::endl;
+using std::ifstream;
+using std::ios;
+
+
+struct Chunk
+{
+ char id[4];
+ u_int32_t size;
+
+ Chunk(ifstream *, bool idOnly = false);
+ bool isa(std::string s);
+};
+
+Chunk::Chunk(ifstream *file, bool idOnly)
+{
+ file->read((char *)this->id, 4);
+ size = 0;
+
+ if (idOnly)
+ return ;
+
+ unsigned char sz[4];
+ file->read((char *)sz, 4);
+ for (int i = 0; i < 4; ++i)
+ size += sz[i] << (i * 8);
+}
+
+bool
+Chunk::isa(string s)
+{
+ return string(id, 4) == s;
+}
+
+bool
+SF2PatchExtractor::isSF2File(string fileName)
+{
+ ifstream *file = new ifstream(fileName.c_str(), ios::in | ios::binary);
+ if (!file)
+ throw FileNotFoundException();
+
+ Chunk riffchunk(file);
+ if (!riffchunk.isa("RIFF")) {
+ file->close();
+ return false;
+ }
+
+ Chunk sfbkchunk(file, true);
+ if (!sfbkchunk.isa("sfbk")) {
+ file->close();
+ return false;
+ }
+
+ file->close();
+ return true;
+}
+
+SF2PatchExtractor::Device
+SF2PatchExtractor::read(string fileName)
+{
+ Device device;
+
+ ifstream *file = new ifstream(fileName.c_str(), ios::in | ios::binary);
+ if (!file)
+ throw FileNotFoundException();
+
+ Chunk riffchunk(file);
+ if (!riffchunk.isa("RIFF")) {
+ file->close();
+ throw WrongFileFormatException();
+ }
+
+ Chunk sfbkchunk(file, true);
+ if (!sfbkchunk.isa("sfbk")) {
+ file->close();
+ throw WrongFileFormatException();
+ }
+
+ while (!file->eof()) {
+
+ Chunk chunk(file);
+
+ if (!chunk.isa("LIST")) {
+ // cerr << "Skipping " << string(chunk.id, 4) << endl;
+ file->seekg(chunk.size, ios::cur);
+ continue;
+ }
+
+ Chunk listchunk(file, true);
+ if (!listchunk.isa("pdta")) {
+ // cerr << "Skipping " << string(id, 4) << endl;
+ file->seekg(chunk.size - 4, ios::cur);
+ continue;
+ }
+
+ int size = chunk.size - 4;
+ while (size > 0) {
+
+ Chunk pdtachunk(file);
+ size -= 8 + pdtachunk.size;
+ if (file->eof()) {
+ break;
+ }
+
+ if (!pdtachunk.isa("phdr")) { // preset header
+ // cerr << "Skipping " << string(pdtachunk.id, 4) << endl;
+ file->seekg(pdtachunk.size, ios::cur);
+ continue;
+ }
+
+ int presets = pdtachunk.size / 38;
+ for (int i = 0; i < presets; ++i) {
+
+ char name[21];
+ u_int16_t bank, program;
+
+ file->read((char *)name, 20);
+ name[20] = '\0';
+ file->read((char *)&program, 2);
+ file->read((char *)&bank, 2);
+
+ // cerr << "Read name as " << name << endl;
+
+ file->seekg(14, ios::cur);
+
+ if (i == presets - 1 &&
+ bank == 255 &&
+ program == 255 &&
+ string(name) == "EOP")
+ continue;
+
+ device[bank][program] = name;
+ }
+ }
+ }
+
+ file->close();
+ return device;
+}
+
+}
+
+
+#ifdef TEST_SF2_PATCH_EXTRACTOR
+
+int main(int argc, char **argv)
+{
+ using SF2PatchExtractor;
+
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " sf2filename" << std::endl;
+ return 2;
+ }
+
+ try {
+ SF2PatchExtractor::Device device =
+ SF2PatchExtractor::read(argv[1]);
+
+ std::cerr << "Done. Presets are:" << std::endl;
+
+ for (SF2PatchExtractor::Device::iterator di = device.begin();
+ di != device.end(); ++di) {
+
+ std::cerr << "Bank " << di->first << ":" << std::endl;
+
+ for (SF2PatchExtractor::Bank::iterator bi = di->second.begin();
+ bi != di->second.end();
+ ++bi) {
+
+ std::cerr << "Program " << bi->first << ": \"" << bi->second
+ << "\"" << std::endl;
+ }
+ }
+ } catch (SF2PatchExtractor::WrongFileFormatException) {
+ std::cerr << "Wrong file format" << std::endl;
+ } catch (SF2PatchExtractor::FileNotFoundException) {
+ std::cerr << "File not found or couldn't be opened" << std::endl;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/src/sound/SF2PatchExtractor.h b/src/sound/SF2PatchExtractor.h
new file mode 100644
index 0000000..a9d5453
--- /dev/null
+++ b/src/sound/SF2PatchExtractor.h
@@ -0,0 +1,58 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _SF2_PATCH_EXTRACTOR_H_
+#define _SF2_PATCH_EXTRACTOR_H_
+
+#include <string>
+#include <map>
+
+namespace Rosegarden {
+
+/**
+ * Trivial class to suck the patch map out of a .sf2 SoundFont file.
+ * Inspired by (but not based on) sftovkb by Takashi Iwai.
+ *
+ * SoundFont is a straightforward RIFF format so there's some
+ * redundancy between this and RIFFAudioFile -- we don't take any
+ * advantage of that, and this class is completely self-contained.
+ *
+ * Tolerates garbled files; will just suck all it can rather than
+ * throw an error, except if the file is not a SoundFont at all.
+ */
+
+class SF2PatchExtractor
+{
+public:
+ typedef std::map<int, std::string> Bank;
+ typedef std::map<int, Bank> Device;
+
+ struct FileNotFoundException { };
+ struct WrongFileFormatException { };
+
+ static bool isSF2File(std::string fileName);
+ static Device read(std::string fileName);
+};
+
+}
+
+#endif
+
diff --git a/src/sound/SampleWindow.h b/src/sound/SampleWindow.h
new file mode 100644
index 0000000..88400f7
--- /dev/null
+++ b/src/sound/SampleWindow.h
@@ -0,0 +1,192 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ 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.
+*/
+
+/*
+ This file is derived from
+
+ Sonic Visualiser
+ An audio file viewer and annotation editor.
+ Centre for Digital Music, Queen Mary, University of London.
+ This file copyright 2006 Chris Cannam.
+
+ 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 _SAMPLE_WINDOW_H_
+#define _SAMPLE_WINDOW_H_
+
+#include <cmath>
+#include <cstdlib>
+#include <iostream>
+#include <map>
+
+namespace Rosegarden
+{
+
+template <typename T>
+class SampleWindow
+{
+public:
+ enum Type {
+ Rectangular,
+ Bartlett,
+ Hamming,
+ Hanning,
+ Blackman,
+ Gaussian,
+ Parzen,
+ Nuttall,
+ BlackmanHarris
+ };
+
+ /**
+ * Construct a windower of the given type.
+ */
+ SampleWindow(Type type, size_t size) : m_type(type), m_size(size) { encache(); }
+ SampleWindow(const SampleWindow &w) : m_type(w.m_type), m_size(w.m_size) { encache(); }
+ SampleWindow &operator=(const SampleWindow &w) {
+ if (&w == this) return *this;
+ m_type = w.m_type;
+ m_size = w.m_size;
+ encache();
+ return *this;
+ }
+ virtual ~SampleWindow() { delete[] m_cache; }
+
+ void cut(T *src) const { cut(src, src); }
+ void cut(T *src, T *dst) const {
+ for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i];
+ }
+
+ T getArea() { return m_area; }
+ T getValue(size_t i) { return m_cache[i]; }
+
+ Type getType() const { return m_type; }
+ size_t getSize() const { return m_size; }
+
+protected:
+ Type m_type;
+ size_t m_size;
+ T *m_cache;
+ T m_area;
+
+ void encache();
+ void cosinewin(T *, T, T, T, T);
+};
+
+template <typename T>
+void SampleWindow<T>::encache()
+{
+ int n = int(m_size);
+ T *mult = new T[n];
+ int i;
+ for (i = 0; i < n; ++i) mult[i] = 1.0;
+
+ switch (m_type) {
+
+ case Rectangular:
+ for (i = 0; i < n; ++i) {
+ mult[i] *= 0.5;
+ }
+ break;
+
+ case Bartlett:
+ for (i = 0; i < n/2; ++i) {
+ mult[i] *= (i / T(n/2));
+ mult[i + n/2] *= (1.0 - (i / T(n/2)));
+ }
+ break;
+
+ case Hamming:
+ cosinewin(mult, 0.54, 0.46, 0.0, 0.0);
+ break;
+
+ case Hanning:
+ cosinewin(mult, 0.50, 0.50, 0.0, 0.0);
+ break;
+
+ case Blackman:
+ cosinewin(mult, 0.42, 0.50, 0.08, 0.0);
+ break;
+
+ case Gaussian:
+ for (i = 0; i < n; ++i) {
+ mult[i] *= pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2));
+ }
+ break;
+
+ case Parzen:
+ {
+ int N = n-1;
+ for (i = 0; i < N/4; ++i) {
+ T m = 2 * pow(1.0 - (T(N)/2 - i) / (T(N)/2), 3);
+ mult[i] *= m;
+ mult[N-i] *= m;
+ }
+ for (i = N/4; i <= N/2; ++i) {
+ int wn = i - N/2;
+ T m = 1.0 - 6 * pow(wn / (T(N)/2), 2) * (1.0 - abs(wn) / (T(N)/2));
+ mult[i] *= m;
+ mult[N-i] *= m;
+ }
+ break;
+ }
+
+ case Nuttall:
+ cosinewin(mult, 0.3635819, 0.4891775, 0.1365995, 0.0106411);
+ break;
+
+ case BlackmanHarris:
+ cosinewin(mult, 0.35875, 0.48829, 0.14128, 0.01168);
+ break;
+ }
+
+ m_cache = mult;
+
+ m_area = 0;
+ for (int i = 0; i < n; ++i) {
+ m_area += m_cache[i];
+ }
+ m_area /= n;
+}
+
+template <typename T>
+void SampleWindow<T>::cosinewin(T *mult, T a0, T a1, T a2, T a3)
+{
+ int n = int(m_size);
+ for (int i = 0; i < n; ++i) {
+ mult[i] *= (a0
+ - a1 * cos(2 * M_PI * i / n)
+ + a2 * cos(4 * M_PI * i / n)
+ - a3 * cos(6 * M_PI * i / n));
+ }
+}
+
+}
+
+#endif
diff --git a/src/sound/Scavenger.h b/src/sound/Scavenger.h
new file mode 100644
index 0000000..b27e848
--- /dev/null
+++ b/src/sound/Scavenger.h
@@ -0,0 +1,211 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _SCAVENGER_H_
+#define _SCAVENGER_H_
+
+#include <vector>
+#include <list>
+#include <sys/time.h>
+#include <pthread.h>
+#include <iostream>
+
+namespace Rosegarden
+{
+
+/**
+ * A very simple class that facilitates running things like plugins
+ * without locking, by collecting unwanted objects and deleting them
+ * after a delay so as to be sure nobody's in the middle of using
+ * them. Requires scavenge() to be called regularly from a non-RT
+ * thread.
+ *
+ * This is currently not at all suitable for large numbers of objects
+ * -- it's just a quick hack for use with things like plugins.
+ */
+
+template <typename T>
+class Scavenger
+{
+public:
+ Scavenger(int sec = 2, int defaultObjectListSize = 200);
+ ~Scavenger();
+
+ /**
+ * Call from an RT thread etc., to pass ownership of t to us for
+ * later disposal. Only one thread should be calling this on any
+ * given scavenger.
+ *
+ * This is only lock-free so long as a slot is available in the
+ * object list; otherwise it takes a lock and allocates memory.
+ * Scavengers should always be used with an object list size
+ * sufficient to ensure that enough slots are always available in
+ * normal use.
+ */
+ void claim(T *t);
+
+ /**
+ * Call from a non-RT thread.
+ * Only one thread should be calling this on any given scavenger.
+ */
+ void scavenge();
+
+protected:
+ typedef std::pair<T *, int> ObjectTimePair;
+ typedef std::vector<ObjectTimePair> ObjectTimeList;
+ ObjectTimeList m_objects;
+ int m_sec;
+
+ typedef std::list<T *> ObjectList;
+ ObjectList m_excess;
+ int m_lastExcess;
+ pthread_mutex_t m_excessMutex;
+ void pushExcess(T *);
+ void clearExcess(int);
+
+ unsigned int m_claimed;
+ unsigned int m_scavenged;
+};
+
+/**
+ * A wrapper to permit arrays to be scavenged.
+ */
+
+template <typename T>
+class ScavengerArrayWrapper
+{
+public:
+ ScavengerArrayWrapper(T *array) : m_array(array) { }
+ ~ScavengerArrayWrapper() { delete[] m_array; }
+
+private:
+ T *m_array;
+};
+
+
+template <typename T>
+Scavenger<T>::Scavenger(int sec, int defaultObjectListSize) :
+ m_objects(ObjectTimeList(defaultObjectListSize)),
+ m_sec(sec),
+ m_lastExcess(0),
+ m_claimed(0),
+ m_scavenged(0)
+{
+ pthread_mutex_init(&m_excessMutex, NULL);
+}
+
+template <typename T>
+Scavenger<T>::~Scavenger()
+{
+ if (m_scavenged < m_claimed) {
+ for (size_t i = 0; i < m_objects.size(); ++i) {
+ ObjectTimePair &pair = m_objects[i];
+ if (pair.first != 0) {
+ T *ot = pair.first;
+ pair.first = 0;
+ delete ot;
+ ++m_scavenged;
+ }
+ }
+ }
+
+ clearExcess(0);
+
+ pthread_mutex_destroy(&m_excessMutex);
+}
+
+template <typename T>
+void
+Scavenger<T>::claim(T *t)
+{
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ int sec = tv.tv_sec;
+
+ for (size_t i = 0; i < m_objects.size(); ++i) {
+ ObjectTimePair &pair = m_objects[i];
+ if (pair.first == 0) {
+ pair.second = sec;
+ pair.first = t;
+ ++m_claimed;
+ return;
+ }
+ }
+
+ std::cerr << "WARNING: Scavenger::claim(" << t << "): run out of slots, "
+ << "using non-RT-safe method" << std::endl;
+ pushExcess(t);
+}
+
+template <typename T>
+void
+Scavenger<T>::scavenge()
+{
+ if (m_scavenged >= m_claimed) return;
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ int sec = tv.tv_sec;
+
+ for (size_t i = 0; i < m_objects.size(); ++i) {
+ ObjectTimePair &pair = m_objects[i];
+ if (pair.first != 0 && pair.second + m_sec < sec) {
+ T *ot = pair.first;
+ pair.first = 0;
+ delete ot;
+ ++m_scavenged;
+ }
+ }
+
+ if (sec > m_lastExcess + m_sec) {
+ clearExcess(sec);
+ }
+}
+
+template <typename T>
+void
+Scavenger<T>::pushExcess(T *t)
+{
+ pthread_mutex_lock(&m_excessMutex);
+ m_excess.push_back(t);
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ m_lastExcess = tv.tv_sec;
+ pthread_mutex_unlock(&m_excessMutex);
+}
+
+template <typename T>
+void
+Scavenger<T>::clearExcess(int sec)
+{
+ pthread_mutex_lock(&m_excessMutex);
+ for (typename ObjectList::iterator i = m_excess.begin();
+ i != m_excess.end(); ++i) {
+ delete *i;
+ }
+ m_excess.clear();
+ m_lastExcess = sec;
+ pthread_mutex_unlock(&m_excessMutex);
+}
+
+}
+
+#endif
diff --git a/src/sound/SequencerDataBlock.cpp b/src/sound/SequencerDataBlock.cpp
new file mode 100644
index 0000000..bc5e80b
--- /dev/null
+++ b/src/sound/SequencerDataBlock.cpp
@@ -0,0 +1,361 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "SequencerDataBlock.h"
+#include "MappedComposition.h"
+
+namespace Rosegarden
+{
+
+SequencerDataBlock::SequencerDataBlock(bool initialise)
+{
+ if (initialise)
+ clearTemporaries();
+}
+
+bool
+SequencerDataBlock::getVisual(MappedEvent &ev) const
+{
+ static int eventIndex = 0;
+
+ if (!m_haveVisualEvent) {
+ return false;
+ } else {
+ int thisEventIndex = m_visualEventIndex;
+ if (thisEventIndex == eventIndex)
+ return false;
+ ev = *((MappedEvent *) & m_visualEvent);
+ eventIndex = thisEventIndex;
+ return true;
+ }
+}
+
+void
+SequencerDataBlock::setVisual(const MappedEvent *ev)
+{
+ m_haveVisualEvent = false;
+ if (ev) {
+ *((MappedEvent *)&m_visualEvent) = *ev;
+ ++m_visualEventIndex;
+ m_haveVisualEvent = true;
+ }
+}
+
+int
+SequencerDataBlock::getRecordedEvents(MappedComposition &mC) const
+{
+ static int readIndex = -1;
+
+ if (readIndex == -1) {
+ readIndex = m_recordEventIndex;
+ return 0;
+ }
+
+ int currentIndex = m_recordEventIndex;
+ int count = 0;
+
+ MappedEvent *recordBuffer = (MappedEvent *)m_recordBuffer;
+
+ while (readIndex != currentIndex) {
+ mC.insert(new MappedEvent(recordBuffer[readIndex]));
+ if (++readIndex == SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE)
+ readIndex = 0;
+ ++count;
+ }
+
+ return count;
+}
+
+void
+SequencerDataBlock::addRecordedEvents(MappedComposition *mC)
+{
+ // ringbuffer
+ int index = m_recordEventIndex;
+ MappedEvent *recordBuffer = (MappedEvent *)m_recordBuffer;
+
+ for (MappedComposition::iterator i = mC->begin(); i != mC->end(); ++i) {
+ recordBuffer[index] = **i;
+ if (++index == SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE)
+ index = 0;
+ }
+
+ m_recordEventIndex = index;
+}
+
+int
+SequencerDataBlock::instrumentToIndex(InstrumentId id) const
+{
+ int i;
+
+ for (i = 0; i < m_knownInstrumentCount; ++i) {
+ if (m_knownInstruments[i] == id)
+ return i;
+ }
+
+ return -1;
+}
+
+int
+SequencerDataBlock::instrumentToIndexCreating(InstrumentId id)
+{
+ int i;
+
+ for (i = 0; i < m_knownInstrumentCount; ++i) {
+ if (m_knownInstruments[i] == id)
+ return i;
+ }
+
+ if (i == SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS) {
+ std::cerr << "ERROR: SequencerDataBlock::instrumentToIndexCreating("
+ << id << "): out of instrument index space" << std::endl;
+ return -1;
+ }
+
+ m_knownInstruments[i] = id;
+ ++m_knownInstrumentCount;
+ return i;
+}
+
+bool
+SequencerDataBlock::getInstrumentLevel(InstrumentId id,
+ LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_levelUpdateIndices[index];
+ info = m_levels[index];
+
+ /*
+ std::cout << "SequencerDataBlock::getInstrumentLevel - "
+ << "id = " << id
+ << ", level = " << info.level << std::endl;
+ */
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+bool
+SequencerDataBlock::getInstrumentLevelForMixer(InstrumentId id,
+ LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_levelUpdateIndices[index];
+ info = m_levels[index];
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+void
+SequencerDataBlock::setInstrumentLevel(InstrumentId id, const LevelInfo &info)
+{
+ int index = instrumentToIndexCreating(id);
+ if (index < 0)
+ return ;
+
+ m_levels[index] = info;
+ ++m_levelUpdateIndices[index];
+}
+
+bool
+SequencerDataBlock::getInstrumentRecordLevel(InstrumentId id, LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_recordLevelUpdateIndices[index];
+ info = m_recordLevels[index];
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+bool
+SequencerDataBlock::getInstrumentRecordLevelForMixer(InstrumentId id, LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_recordLevelUpdateIndices[index];
+ info = m_recordLevels[index];
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+void
+SequencerDataBlock::setInstrumentRecordLevel(InstrumentId id, const LevelInfo &info)
+{
+ int index = instrumentToIndexCreating(id);
+ if (index < 0)
+ return ;
+
+ m_recordLevels[index] = info;
+ ++m_recordLevelUpdateIndices[index];
+}
+
+void
+SequencerDataBlock::setTrackLevel(TrackId id, const LevelInfo &info)
+{
+ if (m_controlBlock) {
+ setInstrumentLevel(m_controlBlock->getInstrumentForTrack(id), info);
+ }
+}
+
+bool
+SequencerDataBlock::getTrackLevel(TrackId id, LevelInfo &info) const
+{
+ info.level = info.levelRight = 0;
+
+ if (m_controlBlock) {
+ return getInstrumentLevel(m_controlBlock->getInstrumentForTrack(id),
+ info);
+ }
+
+ return false;
+}
+
+bool
+SequencerDataBlock::getSubmasterLevel(int submaster, LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS];
+
+ if (submaster < 0 || submaster > SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_submasterLevelUpdateIndices[submaster];
+ info = m_submasterLevels[submaster];
+
+ if (lastUpdateIndex[submaster] != currentUpdateIndex) {
+ lastUpdateIndex[submaster] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+void
+SequencerDataBlock::setSubmasterLevel(int submaster, const LevelInfo &info)
+{
+ if (submaster < 0 || submaster > SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS) {
+ return ;
+ }
+
+ m_submasterLevels[submaster] = info;
+ ++m_submasterLevelUpdateIndices[submaster];
+}
+
+bool
+SequencerDataBlock::getMasterLevel(LevelInfo &level) const
+{
+ static int lastUpdateIndex = 0;
+
+ int currentIndex = m_masterLevelUpdateIndex;
+ level = m_masterLevel;
+
+ if (lastUpdateIndex != currentIndex) {
+ lastUpdateIndex = currentIndex;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void
+SequencerDataBlock::setMasterLevel(const LevelInfo &info)
+{
+ m_masterLevel = info;
+ ++m_masterLevelUpdateIndex;
+}
+
+void
+SequencerDataBlock::clearTemporaries()
+{
+ m_controlBlock = 0;
+ m_positionSec = 0;
+ m_positionNsec = 0;
+ m_visualEventIndex = 0;
+ *((MappedEvent *)&m_visualEvent) = MappedEvent();
+ m_haveVisualEvent = false;
+ m_recordEventIndex = 0;
+ //!!! m_recordLevel.level = 0;
+ //!!! m_recordLevel.levelRight = 0;
+ memset(m_knownInstruments, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS * sizeof(InstrumentId));
+ m_knownInstrumentCount = 0;
+ memset(m_levelUpdateIndices, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS * sizeof(int));
+ memset(m_levels, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS * sizeof(LevelInfo));
+ memset(m_submasterLevelUpdateIndices, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS * sizeof(int));
+ memset(m_submasterLevels, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS * sizeof(LevelInfo));
+ m_masterLevelUpdateIndex = 0;
+ m_masterLevel.level = 0;
+ m_masterLevel.levelRight = 0;
+
+}
+
+}
+
diff --git a/src/sound/SequencerDataBlock.h b/src/sound/SequencerDataBlock.h
new file mode 100644
index 0000000..2cfdefe
--- /dev/null
+++ b/src/sound/SequencerDataBlock.h
@@ -0,0 +1,140 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _SEQUENCER_DATA_BLOCK_H_
+#define _SEQUENCER_DATA_BLOCK_H_
+
+#include "ControlBlock.h"
+#include "RealTime.h"
+#include "MappedEvent.h"
+
+namespace Rosegarden
+{
+
+/**
+ * ONLY PUT PLAIN DATA HERE - NO POINTERS EVER
+ * (and this struct mustn't have a constructor)
+ */
+struct LevelInfo
+{
+ int level;
+ int levelRight; // if stereo audio
+};
+
+class MappedComposition;
+
+
+#define SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS 512 // can't be a symbol
+#define SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS 64 // can't be a symbol
+#define SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE 1024 // MIDI events
+
+class SequencerDataBlock
+{
+public:
+ /**
+ * Constructor only initialises memory if initialise is true
+ */
+ SequencerDataBlock(bool initialise);
+
+ RealTime getPositionPointer() const {
+ return RealTime(m_positionSec, m_positionNsec);
+ }
+ void setPositionPointer(const RealTime &rt) {
+ m_positionSec = rt.sec;
+ m_positionNsec = rt.nsec;
+ }
+
+ bool getVisual(MappedEvent &ev) const;
+ void setVisual(const MappedEvent *ev);
+
+ int getRecordedEvents(MappedComposition &) const;
+ void addRecordedEvents(MappedComposition *);
+
+ bool getTrackLevel(TrackId track, LevelInfo &) const;
+ void setTrackLevel(TrackId track, const LevelInfo &);
+
+ // Two of these to rather hamfistedly get around the fact
+ // we need to fetch this value twice - once from IPB,
+ // and again for the Mixer.
+ //
+ bool getInstrumentLevel(InstrumentId id, LevelInfo &) const;
+ bool getInstrumentLevelForMixer(InstrumentId id, LevelInfo &) const;
+
+ void setInstrumentLevel(InstrumentId id, const LevelInfo &);
+
+ bool getInstrumentRecordLevel(InstrumentId id, LevelInfo &) const;
+ bool getInstrumentRecordLevelForMixer(InstrumentId id, LevelInfo &) const;
+
+ void setInstrumentRecordLevel(InstrumentId id, const LevelInfo &);
+
+ bool getSubmasterLevel(int submaster, LevelInfo &) const;
+ void setSubmasterLevel(int submaster, const LevelInfo &);
+
+ bool getMasterLevel(LevelInfo &) const;
+ void setMasterLevel(const LevelInfo &);
+
+ void setControlBlock(ControlBlock *cb) { m_controlBlock = cb; }
+ ControlBlock *getControlBlock() { return m_controlBlock; }
+
+ // Reset the temporaries on (for example) GUI restart
+ //
+ void clearTemporaries();
+
+protected:
+ int instrumentToIndex(InstrumentId id) const;
+ int instrumentToIndexCreating(InstrumentId id);
+ ControlBlock *m_controlBlock;
+
+ // Two ints rather than a RealTime, as the RealTime default ctor
+ // initialises the space & so can't be used from the GUI's
+ // placement-new ctor (which has no write access and doesn't want
+ // it anyway). Likewise we use char[] instead of MappedEvents
+
+ int m_positionSec;
+ int m_positionNsec;
+
+ int m_visualEventIndex;
+ bool m_haveVisualEvent;
+ char m_visualEvent[sizeof(MappedEvent)];
+
+ int m_recordEventIndex;
+ char m_recordBuffer[sizeof(MappedEvent) *
+ SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE];
+
+ InstrumentId m_knownInstruments[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+ int m_knownInstrumentCount;
+
+ int m_levelUpdateIndices[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+ LevelInfo m_levels[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int m_recordLevelUpdateIndices[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+ LevelInfo m_recordLevels[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int m_submasterLevelUpdateIndices[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS];
+ LevelInfo m_submasterLevels[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS];
+
+ int m_masterLevelUpdateIndex;
+ LevelInfo m_masterLevel;
+};
+
+}
+
+#endif
diff --git a/src/sound/SoundDriver.cpp b/src/sound/SoundDriver.cpp
new file mode 100644
index 0000000..aab641c
--- /dev/null
+++ b/src/sound/SoundDriver.cpp
@@ -0,0 +1,391 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <stdlib.h>
+
+#include "SoundDriver.h"
+#include "WAVAudioFile.h"
+#include "MappedStudio.h"
+#include "AudioPlayQueue.h"
+
+#include <unistd.h>
+#include <sys/time.h>
+#include <pthread.h> // for mutex
+
+//#define DEBUG_SOUND_DRIVER 1
+
+namespace Rosegarden
+{
+
+// ---------- SoundDriver -----------
+//
+
+
+SoundDriver::SoundDriver(MappedStudio *studio, const std::string &name):
+ m_name(name),
+ m_driverStatus(NO_DRIVER),
+ m_playStartPosition(0, 0),
+ m_startPlayback(false),
+ m_playing(false),
+ m_midiRecordDevice(0),
+ m_recordStatus(RECORD_OFF),
+ m_midiRunningId(MidiInstrumentBase),
+ m_audioRunningId(AudioInstrumentBase),
+ // m_audioQueueScavenger(4, 50),
+ m_audioQueue(0),
+ m_lowLatencyMode(true),
+ m_audioRecFileFormat(RIFFAudioFile::FLOAT),
+ m_studio(studio),
+ m_sequencerDataBlock(0),
+ m_externalTransport(0),
+ m_mmcStatus(TRANSPORT_OFF),
+ m_mtcStatus(TRANSPORT_OFF),
+ m_mmcId(0), // default MMC id of 0
+ m_midiClockEnabled(false),
+ m_midiClockInterval(0, 0),
+ m_midiClockSendTime(RealTime::zeroTime),
+ m_midiSongPositionPointer(0)
+{
+ m_audioQueue = new AudioPlayQueue();
+}
+
+
+SoundDriver::~SoundDriver()
+{
+ std::cout << "SoundDriver::~SoundDriver (exiting)" << std::endl;
+ delete m_audioQueue;
+}
+
+MappedInstrument*
+SoundDriver::getMappedInstrument(InstrumentId id)
+{
+ std::vector<MappedInstrument*>::const_iterator it;
+
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
+ if ((*it)->getId() == id)
+ return (*it);
+ }
+
+ return 0;
+}
+
+void
+SoundDriver::initialiseAudioQueue(const std::vector<MappedEvent> &events)
+{
+ AudioPlayQueue *newQueue = new AudioPlayQueue();
+
+ for (std::vector<MappedEvent>::const_iterator i = events.begin();
+ i != events.end(); ++i) {
+
+ // 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 *audioFile = getAudioFile(i->getAudioID());
+
+ if (audioFile) {
+ MappedAudioFader *fader =
+ dynamic_cast<MappedAudioFader*>
+ (getMappedStudio()->getAudioFader(i->getInstrument()));
+
+ if (!fader) {
+ std::cerr << "WARNING: SoundDriver::initialiseAudioQueue: no fader for audio instrument " << i->getInstrument() << std::endl;
+ continue;
+ }
+
+ unsigned int channels = fader->getPropertyList(
+ MappedAudioFader::Channels)[0].toInt();
+
+ //#define DEBUG_PLAYING_AUDIO
+#ifdef DEBUG_PLAYING_AUDIO
+
+ std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << i->getEventTime() << ", time now " << getSequencerTime() << ", start marker " << i->getAudioStartMarker() << ", duration " << i->getDuration() << ", instrument " << i->getInstrument() << " channels " << channels << std::endl;
+#endif
+
+ RealTime bufferLength = getAudioReadBufferLength();
+ int bufferFrames = RealTime::realTime2Frame
+ (bufferLength, getSampleRate());
+
+ PlayableAudioFile *paf = 0;
+
+ try {
+ paf = new PlayableAudioFile(i->getInstrument(),
+ audioFile,
+ i->getEventTime(),
+ i->getAudioStartMarker(),
+ i->getDuration(),
+ bufferFrames,
+ getSmallFileSize() * 1024,
+ channels,
+ getSampleRate());
+ } catch (...) {
+ continue;
+ }
+
+ paf->setRuntimeSegmentId(i->getRuntimeSegmentId());
+
+ if (i->isAutoFading()) {
+ paf->setAutoFade(true);
+ paf->setFadeInTime(i->getFadeInTime());
+ paf->setFadeOutTime(i->getFadeInTime());
+
+ //#define DEBUG_AUTOFADING
+#ifdef DEBUG_AUTOFADING
+
+ std::cout << "SoundDriver::initialiseAudioQueue - "
+ << "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
+
+ newQueue->addScheduled(paf);
+ } else {
+ std::cerr << "SoundDriver::initialiseAudioQueue - "
+ << "can't find audio file reference for id " << i->getAudioID()
+ << std::endl;
+
+ std::cerr << "SoundDriver::initialiseAudioQueue - "
+ << "try reloading the current Rosegarden file"
+ << std::endl;
+ }
+ }
+
+ std::cout << "SoundDriver::initialiseAudioQueue -- new queue has "
+ << newQueue->size() << " files"
+ << std::endl;
+
+ if (newQueue->empty()) {
+ if (m_audioQueue->empty()) {
+ delete newQueue;
+ return ;
+ }
+ }
+
+ AudioPlayQueue *oldQueue = m_audioQueue;
+ m_audioQueue = newQueue;
+ if (oldQueue)
+ m_audioQueueScavenger.claim(oldQueue);
+}
+
+void
+SoundDriver::clearAudioQueue()
+{
+ std::cout << "SoundDriver::clearAudioQueue" << std::endl;
+
+ if (m_audioQueue->empty())
+ return ;
+
+ AudioPlayQueue *newQueue = new AudioPlayQueue();
+ AudioPlayQueue *oldQueue = m_audioQueue;
+ m_audioQueue = newQueue;
+ if (oldQueue)
+ m_audioQueueScavenger.claim(oldQueue);
+}
+void
+SoundDriver::cancelAudioFile(MappedEvent *mE)
+{
+ std::cout << "SoundDriver::cancelAudioFile" << std::endl;
+
+ if (!m_audioQueue)
+ return ;
+
+ // For now we only permit cancelling unscheduled files.
+
+ const AudioPlayQueue::FileList &files = m_audioQueue->getAllUnscheduledFiles();
+ for (AudioPlayQueue::FileList::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ PlayableAudioFile *file = *fi;
+ if (mE->getRuntimeSegmentId() == -1) {
+
+ // ERROR? The comparison between file->getAudioFile()->getId() of type unsigned int
+ // and mE->getAudioID() of type int.
+ if (file->getInstrument() == mE->getInstrument() &&
+ int(file->getAudioFile()->getId() == mE->getAudioID())) {
+ file->cancel();
+ }
+ } else {
+ if (file->getRuntimeSegmentId() == mE->getRuntimeSegmentId() &&
+ file->getStartTime() == mE->getEventTime()) {
+ file->cancel();
+ }
+ }
+ }
+}
+
+const AudioPlayQueue *
+SoundDriver::getAudioQueue() const
+{
+ return m_audioQueue;
+}
+
+
+void
+SoundDriver::setMappedInstrument(MappedInstrument *mI)
+{
+ std::vector<MappedInstrument*>::iterator it;
+
+ // If we match then change existing entry
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
+ if ((*it)->getId() == mI->getId()) {
+ (*it)->setChannel(mI->getChannel());
+ (*it)->setType(mI->getType());
+ delete mI;
+ return ;
+ }
+ }
+
+ // else create a new one
+ m_instruments.push_back(mI);
+
+ std::cout << "SoundDriver: setMappedInstrument() : "
+ << "type = " << mI->getType() << " : "
+ << "channel = " << (int)(mI->getChannel()) << " : "
+ << "id = " << mI->getId() << std::endl;
+
+}
+
+unsigned int
+SoundDriver::getDevices()
+{
+ return m_devices.size();
+}
+
+MappedDevice
+SoundDriver::getMappedDevice(DeviceId id)
+{
+ MappedDevice retDevice;
+ std::vector<MappedInstrument*>::iterator it;
+
+ std::vector<MappedDevice*>::iterator dIt = m_devices.begin();
+ for (; dIt != m_devices.end(); dIt++) {
+ if ((*dIt)->getId() == id)
+ retDevice = **dIt;
+ }
+
+ // If we match then change existing entry
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
+ if ((*it)->getDevice() == id)
+ retDevice.push_back(*it);
+ }
+
+#ifdef DEBUG_SOUND_DRIVER
+ std::cout << "SoundDriver::getMappedDevice(" << id << ") - "
+ << "name = \"" << retDevice.getName()
+ << "\" type = " << retDevice.getType()
+ << " direction = " << retDevice.getDirection()
+ << " connection = \"" << retDevice.getConnection() << "\""
+ << " recording = " << retDevice.isRecording()
+ << std::endl;
+#endif
+
+ return retDevice;
+}
+
+
+
+bool
+SoundDriver::addAudioFile(const std::string &fileName, unsigned int id)
+{
+ AudioFile *ins = 0;
+
+ try {
+ ins = new WAVAudioFile(id, fileName, fileName);
+ ins->open();
+ m_audioFiles.push_back(ins);
+
+ // std::cout << "Sequencer::addAudioFile() = \"" << fileName << "\"" << std::endl;
+
+ return true;
+
+ } catch (SoundFile::BadSoundFileException e) {
+ std::cerr << "SoundDriver::addAudioFile: Failed to add audio file " << fileName << ": " << e.getMessage() << std::endl;
+ delete ins;
+ return false;
+ }
+}
+
+bool
+SoundDriver::removeAudioFile(unsigned int id)
+{
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++) {
+ if ((*it)->getId() == id) {
+ std::cout << "Sequencer::removeAudioFile() = \"" <<
+ (*it)->getFilename() << "\"" << std::endl;
+
+ delete (*it);
+ m_audioFiles.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+AudioFile*
+SoundDriver::getAudioFile(unsigned int id)
+{
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++) {
+ if ((*it)->getId() == id)
+ return *it;
+ }
+
+ return 0;
+}
+
+void
+SoundDriver::clearAudioFiles()
+{
+ // std::cout << "SoundDriver::clearAudioFiles() - clearing down audio files"
+ // << std::endl;
+
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++)
+ delete(*it);
+
+ m_audioFiles.erase(m_audioFiles.begin(), m_audioFiles.end());
+}
+
+void
+SoundDriver::sleep(const RealTime &rt)
+{
+ // The usleep man page says it's deprecated and we should use
+ // nanosleep. And that's what we did. But it seems quite a few
+ // people don't have nanosleep, so we're reverting to usleep.
+
+ unsigned long usec = rt.sec * 1000000 + rt.usec();
+ usleep(usec);
+}
+
+
+}
+
diff --git a/src/sound/SoundDriver.h b/src/sound/SoundDriver.h
new file mode 100644
index 0000000..fabbaef
--- /dev/null
+++ b/src/sound/SoundDriver.h
@@ -0,0 +1,529 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <string>
+#include <vector>
+#include <list>
+#include <qstringlist.h>
+
+#include "Device.h"
+#include "MappedComposition.h"
+#include "MappedInstrument.h"
+#include "MappedDevice.h"
+#include "SequencerDataBlock.h"
+#include "PlayableAudioFile.h"
+#include "Scavenger.h"
+#include "RIFFAudioFile.h" // for SubFormat enum
+
+// Abstract base to support SoundDrivers, such as ALSA.
+//
+// This base class provides the generic driver support for
+// these drivers with the Sequencer class owning an instance
+// of a sub class of this class and directing it and required
+// by the rosegardensequencer itself.
+//
+//
+
+#ifndef _SOUNDDRIVER_H_
+#define _SOUNDDRIVER_H_
+
+namespace Rosegarden
+{
+
+// Current recording status - whether we're monitoring anything
+// or recording.
+//
+typedef enum
+{
+ RECORD_OFF,
+ RECORD_ON,
+} RecordStatus;
+
+
+// Status of a SoundDriver - whether we're got an audio and
+// MIDI subsystem or not. This is reported right up to the
+// gui.
+//
+typedef enum
+{
+ NO_DRIVER = 0x00, // Nothing's OK
+ AUDIO_OK = 0x01, // AUDIO's OK
+ MIDI_OK = 0x02, // MIDI's OK
+ VERSION_OK = 0x04 // GUI and sequencer versions match
+} SoundDriverStatus;
+
+
+// Used for MMC and MTC, not for JACK transport
+//
+typedef enum
+{
+ TRANSPORT_OFF,
+ TRANSPORT_MASTER,
+ TRANSPORT_SLAVE
+} TransportSyncStatus;
+
+
+// The NoteOffQueue holds a time ordered set of
+// pending MIDI NOTE OFF events.
+//
+class NoteOffEvent
+{
+public:
+ NoteOffEvent() {;}
+ NoteOffEvent(const RealTime &realTime,
+ unsigned int pitch,
+ MidiByte channel,
+ InstrumentId instrument):
+ m_realTime(realTime),
+ m_pitch(pitch),
+ m_channel(channel),
+ m_instrument(instrument) {;}
+ ~NoteOffEvent() {;}
+
+ struct NoteOffEventCmp
+ {
+ bool operator()(NoteOffEvent *nO1, NoteOffEvent *nO2)
+ {
+ return nO1->getRealTime() < nO2->getRealTime();
+ }
+ };
+
+ void setRealTime(const RealTime &time) { m_realTime = time; }
+ RealTime getRealTime() const { return m_realTime; }
+
+ MidiByte getPitch() const { return m_pitch; }
+ MidiByte getChannel() const { return m_channel; }
+ InstrumentId getInstrument() const { return m_instrument; }
+
+private:
+ RealTime m_realTime;
+ MidiByte m_pitch;
+ MidiByte m_channel;
+ InstrumentId m_instrument;
+
+};
+
+
+// The queue itself
+//
+class NoteOffQueue : public std::multiset<NoteOffEvent *,
+ NoteOffEvent::NoteOffEventCmp>
+{
+public:
+ NoteOffQueue() {;}
+ ~NoteOffQueue() {;}
+private:
+};
+
+
+class MappedStudio;
+class ExternalTransport;
+class AudioPlayQueue;
+
+typedef std::vector<PlayableAudioFile *> PlayableAudioFileList;
+
+// The abstract SoundDriver
+//
+//
+class SoundDriver
+{
+public:
+ SoundDriver(MappedStudio *studio, const std::string &name);
+ virtual ~SoundDriver();
+
+ virtual bool initialise() = 0;
+ virtual void shutdown() { }
+
+ virtual void initialisePlayback(const RealTime &position) = 0;
+ virtual void stopPlayback() = 0;
+ virtual void punchOut() = 0; // stop recording, continue playing
+ virtual void resetPlayback(const RealTime &oldPosition, const RealTime &position) = 0;
+ virtual void allNotesOff() = 0;
+
+ virtual RealTime getSequencerTime() = 0;
+
+ virtual MappedComposition *getMappedComposition() = 0;
+
+ virtual void startClocks() { }
+ virtual void stopClocks() { }
+
+ // Process some asynchronous events
+ //
+ virtual void processEventsOut(const MappedComposition &mC) = 0;
+
+ // Process some scheduled events on the output queue. The
+ // slice times are here so that the driver can interleave
+ // note-off events as appropriate.
+ //
+ virtual void processEventsOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd) = 0;
+
+ // Activate a recording state. armedInstruments and audioFileNames
+ // can be NULL if no audio tracks recording.
+ //
+ virtual bool record(RecordStatus recordStatus,
+ const std::vector<InstrumentId> *armedInstruments = 0,
+ const std::vector<QString> *audioFileNames = 0) = 0;
+
+ // Process anything that's pending
+ //
+ virtual void processPending() = 0;
+
+ // Get the driver's operating sample rate
+ //
+ virtual unsigned int getSampleRate() const = 0;
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId id,
+ QString identifier,
+ int position) = 0;
+
+ virtual void removePluginInstance(InstrumentId id,
+ int position) = 0;
+
+ // Clear down and remove all plugin instances
+ //
+ virtual void removePluginInstances() = 0;
+
+ virtual void setPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber,
+ float value) = 0;
+
+ virtual float getPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber) = 0;
+
+ virtual void setPluginInstanceBypass(InstrumentId id,
+ int position,
+ bool value) = 0;
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId id,
+ int position) = 0;
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position) = 0;
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position,
+ int bank,
+ int program) = 0;
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString name) = 0;
+
+ virtual void setPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString program) = 0;
+
+ virtual QString configurePlugin(InstrumentId id,
+ int position,
+ QString key,
+ QString value) = 0;
+
+ virtual void setAudioBussLevels(int bussId,
+ float dB,
+ float pan) = 0;
+
+ virtual void setAudioInstrumentLevels(InstrumentId id,
+ float dB,
+ float pan) = 0;
+
+ // Poll for new clients (for new Devices/Instruments)
+ //
+ virtual bool checkForNewClients() = 0;
+
+ // Set a loop position at the driver (used for transport)
+ //
+ virtual void setLoop(const RealTime &loopStart, const RealTime &loopEnd)
+ = 0;
+
+ virtual void sleep(const RealTime &rt);
+
+ virtual QString getStatusLog() { return ""; }
+
+ // Mapped Instruments
+ //
+ void setMappedInstrument(MappedInstrument *mI);
+ MappedInstrument* getMappedInstrument(InstrumentId id);
+
+ // Return the current status of the driver
+ //
+ unsigned int getStatus() const { return m_driverStatus; }
+
+ // Are we playing?
+ //
+ bool isPlaying() const { return m_playing; }
+
+ // Are we counting? By default a subclass probably wants to
+ // return true, if it doesn't know better.
+ //
+ virtual bool areClocksRunning() const = 0;
+
+ RealTime getStartPosition() const { return m_playStartPosition; }
+ RecordStatus getRecordStatus() const { return m_recordStatus; }
+
+ // Return a MappedDevice full of the Instrument mappings
+ // that the driver has discovered. The gui can then use
+ // this list (complete with names) to generate its proper
+ // Instruments under the MidiDevice and AudioDevice.
+ //
+ MappedDevice getMappedDevice(DeviceId id);
+
+ // Return the number of devices we've found
+ //
+ unsigned int getDevices();
+
+ virtual bool canReconnect(Device::DeviceType) { return false; }
+
+ virtual DeviceId addDevice(Device::DeviceType,
+ MidiDevice::DeviceDirection) {
+ return Device::NO_DEVICE;
+ }
+ virtual void removeDevice(DeviceId) { }
+ virtual void renameDevice(DeviceId, QString) { }
+
+ virtual unsigned int getConnections(Device::DeviceType,
+ MidiDevice::DeviceDirection) { return 0; }
+ virtual QString getConnection(Device::DeviceType,
+ MidiDevice::DeviceDirection,
+ unsigned int) { return ""; }
+ virtual void setConnection(DeviceId, QString) { }
+ virtual void setPlausibleConnection(DeviceId id, QString c) { setConnection(id, c); }
+
+ virtual unsigned int getTimers() { return 0; }
+ virtual QString getTimer(unsigned int) { return ""; }
+ virtual QString getCurrentTimer() { return ""; }
+ virtual void setCurrentTimer(QString) { }
+
+ virtual void getAudioInstrumentNumbers(InstrumentId &, int &) = 0;
+ virtual void getSoftSynthInstrumentNumbers(InstrumentId &, int &) = 0;
+
+ // Plugin management -- SoundDrivers should maintain a plugin
+ // scavenger which the audio process code can use for defunct
+ // plugins. Ownership of plugin is passed to the SoundDriver.
+ //
+ virtual void claimUnwantedPlugin(void *plugin) = 0;
+
+ // This causes all scavenged plugins to be destroyed. It
+ // should only be called in non-RT contexts.
+ //
+ virtual void scavengePlugins() = 0;
+
+ // Handle audio file references
+ //
+ void clearAudioFiles();
+ bool addAudioFile(const std::string &fileName, unsigned int id);
+ bool removeAudioFile(unsigned int id);
+
+ void initialiseAudioQueue(const std::vector<MappedEvent> &audioEvents);
+ void clearAudioQueue();
+ const AudioPlayQueue *getAudioQueue() const;
+
+ RIFFAudioFile::SubFormat getAudioRecFileFormat() const { return m_audioRecFileFormat; }
+
+
+ // Latencies
+ //
+ virtual RealTime getAudioPlayLatency() { return RealTime::zeroTime; }
+ virtual RealTime getAudioRecordLatency() { return RealTime::zeroTime; }
+ virtual RealTime getInstrumentPlayLatency(InstrumentId) { return RealTime::zeroTime; }
+ virtual RealTime getMaximumPlayLatency() { return RealTime::zeroTime; }
+
+ // Buffer sizes
+ //
+ void setAudioBufferSizes(RealTime mix, RealTime read, RealTime write,
+ int smallFileSize) {
+ m_audioMixBufferLength = mix;
+ m_audioReadBufferLength = read;
+ m_audioWriteBufferLength = write;
+ m_smallFileSize = smallFileSize;
+ }
+
+ RealTime getAudioMixBufferLength() { return m_audioMixBufferLength; }
+ RealTime getAudioReadBufferLength() { return m_audioReadBufferLength; }
+ RealTime getAudioWriteBufferLength() { return m_audioWriteBufferLength; }
+ int getSmallFileSize() { return m_smallFileSize; }
+
+ void setLowLatencyMode(bool ll) { m_lowLatencyMode = ll; }
+ bool getLowLatencyMode() const { return m_lowLatencyMode; }
+
+ // Cancel the playback of an audio file - either by instrument and audio file id
+ // or by audio segment id.
+ //
+ void cancelAudioFile(MappedEvent *mE);
+
+ // Studio linkage
+ //
+ MappedStudio* getMappedStudio() { return m_studio; }
+ void setMappedStudio(MappedStudio *studio) { m_studio = studio; }
+
+ // Modify MIDI record device
+ //
+ void setMidiRecordDevice(DeviceId id) { m_midiRecordDevice = id; }
+ DeviceId getMIDIRecordDevice() const { return m_midiRecordDevice; }
+
+ // MIDI Realtime Sync setting
+ //
+ TransportSyncStatus getMIDISyncStatus() const { return m_midiSyncStatus; }
+ void setMIDISyncStatus(TransportSyncStatus status) { m_midiSyncStatus = status; }
+
+ // MMC master/slave setting
+ //
+ TransportSyncStatus getMMCStatus() const { return m_mmcStatus; }
+ void setMMCStatus(TransportSyncStatus status) { m_mmcStatus = status; }
+
+ // MTC master/slave setting
+ //
+ TransportSyncStatus getMTCStatus() const { return m_mtcStatus; }
+ void setMTCStatus(TransportSyncStatus status) { m_mtcStatus = status; }
+
+ // MMC Id
+ //
+ int getMMCId() const { return ((int)(m_mmcId)); }
+ void setMMCId(int id) { m_mmcId = (MidiByte)(id); }
+
+ // Set MIDI clock interval - allow redefinition above to ensure
+ // we handle this reset correctly.
+ //
+ virtual void setMIDIClockInterval(RealTime interval)
+ { m_midiClockInterval = interval; }
+
+ // Get and set the mapper which may optionally be used to
+ // store recording levels etc for communication back to the GUI.
+ // (If a subclass wants this and finds it's not there, it should
+ // simply continue without.)
+ //
+ SequencerDataBlock *getSequencerDataBlock() { return m_sequencerDataBlock; }
+ void setSequencerDataBlock(SequencerDataBlock *d) { m_sequencerDataBlock = d; }
+
+ ExternalTransport *getExternalTransportControl() const {
+ return m_externalTransport;
+ }
+ void setExternalTransportControl(ExternalTransport *transport) {
+ m_externalTransport = transport;
+ }
+
+ // Do any bits and bobs of work that need to be done continuously
+ // (this is called repeatedly whether playing or not).
+ //
+ virtual void runTasks() { }
+
+ // Report a failure back to the GUI - ideally. Default does nothing.
+ //
+ virtual void reportFailure(MappedEvent::FailureCode) { }
+
+protected:
+ // Helper functions to be implemented by subclasses
+ //
+ virtual void processMidiOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd) = 0;
+ virtual void generateInstruments() = 0;
+
+ // Audio
+ //
+ AudioFile* getAudioFile(unsigned int id);
+
+ std::string m_name;
+ unsigned int m_driverStatus;
+ RealTime m_playStartPosition;
+ bool m_startPlayback;
+ bool m_playing;
+
+ // MIDI Note-off handling
+ //
+ NoteOffQueue m_noteOffQueue;
+
+ // This is our driver's own list of MappedInstruments and MappedDevices.
+ // These are uncoupled at this level - the Instruments and Devices float
+ // free and only index each other - the Devices hold information only like
+ // name, id and if the device is duplex capable.
+ //
+ typedef std::vector<MappedInstrument*> MappedInstrumentList;
+ MappedInstrumentList m_instruments;
+
+ typedef std::vector<MappedDevice*> MappedDeviceList;
+ MappedDeviceList m_devices;
+
+ DeviceId m_midiRecordDevice;
+
+ MappedComposition m_recordComposition;
+ MappedComposition m_returnComposition;
+ RecordStatus m_recordStatus;
+
+
+ InstrumentId m_midiRunningId;
+ InstrumentId m_audioRunningId;
+
+ // Subclass _MUST_ scavenge this regularly:
+ Scavenger<AudioPlayQueue> m_audioQueueScavenger;
+ AudioPlayQueue *m_audioQueue;
+
+ // A list of AudioFiles that we can play.
+ //
+ std::vector<AudioFile*> m_audioFiles;
+
+ RealTime m_audioMixBufferLength;
+ RealTime m_audioReadBufferLength;
+ RealTime m_audioWriteBufferLength;
+ int m_smallFileSize;
+ bool m_lowLatencyMode;
+
+ RIFFAudioFile::SubFormat m_audioRecFileFormat;
+
+ // Virtual studio hook
+ //
+ MappedStudio *m_studio;
+
+ // Sequencer data block for communication back to GUI
+ //
+ SequencerDataBlock *m_sequencerDataBlock;
+
+ // Controller to make externally originated transport requests on
+ //
+ ExternalTransport *m_externalTransport;
+
+ // MMC and MTC status and ID
+ //
+ TransportSyncStatus m_midiSyncStatus;
+ TransportSyncStatus m_mmcStatus;
+ TransportSyncStatus m_mtcStatus;
+ MidiByte m_mmcId; // device id
+
+ // MIDI clock interval
+ //
+ bool m_midiClockEnabled;
+ RealTime m_midiClockInterval;
+ RealTime m_midiClockSendTime;
+
+ // MIDI Song Position pointer
+ //
+ long m_midiSongPositionPointer;
+
+};
+
+}
+
+#endif // _SOUNDDRIVER_H_
+
diff --git a/src/sound/SoundDriverFactory.cpp b/src/sound/SoundDriverFactory.cpp
new file mode 100644
index 0000000..d081a4e
--- /dev/null
+++ b/src/sound/SoundDriverFactory.cpp
@@ -0,0 +1,66 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "DummyDriver.h"
+
+#ifdef HAVE_ALSA
+#include "AlsaDriver.h"
+#endif
+
+#include "SoundDriverFactory.h"
+
+namespace Rosegarden
+{
+
+SoundDriver *
+SoundDriverFactory::createDriver(MappedStudio *studio)
+{
+ SoundDriver *driver = 0;
+ bool initialised = false;
+#ifdef NO_SOUND
+
+ driver = new DummyDriver(studio);
+#else
+#ifdef HAVE_ALSA
+
+ driver = new AlsaDriver(studio);
+#endif
+#endif
+
+ initialised = driver->initialise();
+
+ if ( ! initialised ) {
+ driver->shutdown();
+ delete driver;
+
+ // if the driver couldn't be initialised, then
+ // fall to the DummyDriver as a last chance,
+ // so GUI can still be used for notation.
+ //
+ driver = new DummyDriver(studio);
+ driver->initialise();
+ }
+ return driver;
+}
+
+
+}
+
+
diff --git a/src/sound/SoundDriverFactory.h b/src/sound/SoundDriverFactory.h
new file mode 100644
index 0000000..56bc889
--- /dev/null
+++ b/src/sound/SoundDriverFactory.h
@@ -0,0 +1,37 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef SOUND_DRIVER_FACTORY_H
+#define SOUND_DRIVER_FACTORY_H
+
+namespace Rosegarden {
+
+class SoundDriver;
+
+class SoundDriverFactory
+{
+public:
+ static SoundDriver *createDriver(MappedStudio *studio);
+};
+
+}
+
+#endif
+
diff --git a/src/sound/SoundFile.cpp b/src/sound/SoundFile.cpp
new file mode 100644
index 0000000..b87cef0
--- /dev/null
+++ b/src/sound/SoundFile.cpp
@@ -0,0 +1,295 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SoundFile.h"
+#include "Profiler.h"
+
+
+//#define DEBUG_SOUNDFILE 1
+
+namespace Rosegarden
+
+{
+
+SoundFile::SoundFile(const std::string &fileName):
+ m_fileName(fileName),
+ m_readChunkPtr( -1),
+ m_readChunkSize(4096), // 4k blocks
+ m_inFile(0),
+ m_outFile(0),
+ m_loseBuffer(false),
+ m_fileSize(0)
+{}
+
+// Tidies up for any dervied classes
+//
+SoundFile::~SoundFile()
+{
+ if (m_inFile) {
+ m_inFile->close();
+ delete m_inFile;
+ }
+
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+}
+
+// Read in a specified number of bytes and return them
+// as a string.
+//
+std::string
+SoundFile::getBytes(std::ifstream *file, unsigned int numberOfBytes)
+{
+ if (file->eof()) {
+ // Reset the input stream so it's operational again
+ //
+ file->clear();
+
+ throw(BadSoundFileException(m_fileName, "SoundFile::getBytes() - EOF encountered"));
+ }
+
+ if (!(*file)) {
+ std::cerr << "SoundFile::getBytes() - stream is not well";
+ }
+
+
+ std::string rS;
+ char *fileBytes = new char[numberOfBytes];
+
+ file->read(fileBytes, numberOfBytes);
+
+ for (int i = 0; i < file->gcount(); i++)
+ rS += (unsigned char)fileBytes[i];
+
+#ifdef DEBUG_SOUNDFILE
+ // complain but return
+ //
+ if (rS.length() < numberOfBytes)
+ std::cerr << "SoundFile::getBytes() - couldn't get all bytes ("
+ << rS.length() << " from " << numberOfBytes << ")"
+ << std::endl;
+#endif
+
+ // clear down
+ delete [] fileBytes;
+
+ return rS;
+}
+
+// Read a specified number of bytes into a buffer.
+//
+size_t
+SoundFile::getBytes(std::ifstream *file, char *buf, size_t n)
+{
+ if (!(*file)) {
+ std::cerr << "SoundFile::getBytes() - stream is not well";
+ return 0;
+ }
+
+ if (file->eof()) {
+ file->clear();
+ return 0;
+ }
+
+ file->read(buf, n);
+ return file->gcount();
+}
+
+// A buffered read based on the current file handle.
+//
+std::string
+SoundFile::getBytes(unsigned int numberOfBytes)
+{
+ if (m_inFile == 0)
+ throw(BadSoundFileException(m_fileName, "SoundFile::getBytes - no open file handle"));
+
+ if (m_inFile->eof()) {
+ // Reset the input stream so it's operational again
+ //
+ m_inFile->clear();
+
+ throw(BadSoundFileException(m_fileName, "SoundFile::getBytes() - EOF encountered"));
+ }
+
+
+ // If this flag is set we dump the buffer and re-read it -
+ // should be set if specialised class is scanning about
+ // when we're doing buffered reads
+ //
+ if (m_loseBuffer) {
+ m_readChunkPtr = -1;
+ m_loseBuffer = false;
+ }
+
+ std::string rS;
+ char *fileBytes = new char[m_readChunkSize];
+ int oldLength;
+
+ while (rS.length() < numberOfBytes && !m_inFile->eof()) {
+ if (m_readChunkPtr == -1) {
+ // clear buffer
+ m_readBuffer = "";
+
+ // reset read pointer
+ m_readChunkPtr = 0;
+
+ // Try to read the whole chunk
+ //
+ m_inFile->read(fileBytes, m_readChunkSize);
+
+ // file->gcount holds the number of bytes we've actually read
+ // so copy them across into our string
+ //
+ for (int i = 0; i < m_inFile->gcount(); i++)
+ m_readBuffer += (unsigned char)fileBytes[i];
+ }
+
+ // Can we fulfill our request at this pass? If so read the
+ // bytes across and we'll exit at the end of this loop.
+ // m_readChunkPtr keeps our position for next time.
+ //
+ if (numberOfBytes - rS.length() <= m_readBuffer.length() -
+ m_readChunkPtr) {
+ oldLength = rS.length();
+
+ rS += m_readBuffer.substr(m_readChunkPtr,
+ numberOfBytes - oldLength);
+
+ m_readChunkPtr += rS.length() - oldLength;
+ } else {
+ // Fill all we can this time and reset the m_readChunkPtr
+ // so that we fetch another chunk of bytes from the file.
+ //
+ rS += m_readBuffer.substr(m_readChunkPtr,
+ m_readChunkSize - m_readChunkPtr);
+ m_readChunkPtr = -1;
+ }
+
+ // If we're EOF here we must've read and copied across everything
+ // we can do. Reset and break out.
+ //
+ if (m_inFile->eof()) {
+ m_inFile->clear();
+ break;
+ }
+
+ }
+
+#ifdef DEBUG_SOUNDFILE
+ // complain but return
+ //
+ if (rS.length() < numberOfBytes)
+ std::cerr << "SoundFile::getBytes() buffered - couldn't get all bytes ("
+ << rS.length() << " from " << numberOfBytes << ")"
+ << std::endl;
+#endif
+
+ delete [] fileBytes;
+
+ // Reset and return if EOF
+ //
+ if (m_inFile->eof())
+ m_inFile->clear();
+
+ return rS;
+}
+
+
+// Write out a sequence of FileBytes to the stream
+//
+void
+SoundFile::putBytes(std::ofstream *file,
+ const std::string oS)
+{
+ for (unsigned int i = 0; i < oS.length(); i++)
+ *file << (FileByte) oS[i];
+}
+
+void
+SoundFile::putBytes(std::ofstream *file, const char *buffer, size_t n)
+{
+ file->write(buffer, n);
+}
+
+
+// Clip off any path from the filename
+std::string
+SoundFile::getShortFilename() const
+{
+ std::string rS = m_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 little endian binary std::string into an integer
+//
+int
+SoundFile::getIntegerFromLittleEndian(const std::string &s)
+{
+ int r = 0;
+
+ for (unsigned int i = 0; i < s.length(); i++) {
+ r += (int)(((FileByte)s[i]) << (i * 8));
+ }
+
+ return r;
+}
+
+
+// Turn a value into a little endian string of "length"
+//
+std::string
+SoundFile::getLittleEndianFromInteger(unsigned int value, unsigned int length)
+{
+ std::string r = "";
+
+ do {
+ r += (unsigned char)((long)((value >> (8 * r.length())) & 0xff));
+ } while (r.length() < length);
+
+ return r;
+}
+
+int
+SoundFile::getIntegerFromBigEndian(const std::string &s)
+{
+ return 0;
+}
+
+std::string
+SoundFile::getBigEndianFromInteger(unsigned int value, unsigned int length)
+{
+ std::string r;
+
+ return r;
+}
+
+
+}
+
diff --git a/src/sound/SoundFile.h b/src/sound/SoundFile.h
new file mode 100644
index 0000000..b048226
--- /dev/null
+++ b/src/sound/SoundFile.h
@@ -0,0 +1,155 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /*
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _SOUNDFILE_H_
+#define _SOUNDFILE_H_
+
+// SoundFile is an abstract base class defining behaviour for both
+// MidiFiles and AudioFiles. The getBytes routine is buffered into
+// suitably sized chunks to prevent excessive file reads.
+//
+//
+
+#include <iostream>
+#include <fstream>
+#include <string>
+
+#include "Exception.h"
+
+namespace Rosegarden
+{
+
+
+// Constants related to RIFF/WAV files
+//
+const std::string AUDIO_RIFF_ID = "RIFF";
+const std::string AUDIO_WAVE_ID = "WAVE";
+const std::string AUDIO_FORMAT_ID = "fmt "; // Always four bytes
+
+const std::string AUDIO_BWF_ID = "bext"; // BWF chunk id
+const std::string AUDIO_BWF_PEAK_ID = "levl"; // BWF peak chunk id
+
+
+const float SAMPLE_MAX_8BIT = (float)(0xff);
+const float SAMPLE_MAX_16BIT = (float)(0xffff/2);
+const float SAMPLE_MAX_24BIT = (float)(0xffffff/2);
+
+
+
+typedef unsigned char FileByte;
+
+class SoundFile
+{
+public:
+ SoundFile(const std::string &fileName);
+ virtual ~SoundFile();
+
+ class BadSoundFileException : public Exception
+ {
+ public:
+ BadSoundFileException(std::string path) :
+ Exception("Bad sound file " + path), m_path(path) { }
+ BadSoundFileException(std::string path, std::string message) :
+ Exception("Bad sound file " + path + ": " + message), m_path(path) { }
+ BadSoundFileException(std::string path, std::string file, int line) :
+ Exception("Bad sound file " + path, file, line), m_path(path) { }
+
+ ~BadSoundFileException() throw() { }
+
+ std::string getPath() const { return m_path; }
+
+ private:
+ std::string m_path;
+ };
+
+ // All files should be able open, write and close
+ virtual bool open() = 0;
+ virtual bool write() = 0;
+ virtual void close() = 0;
+
+ std::string getShortFilename() const;
+ std::string getFilename() const { return m_fileName; }
+ void setFilename(const std::string &fileName) { m_fileName = fileName; }
+
+ // Useful methods that operate on our file data
+ //
+ int getIntegerFromLittleEndian(const std::string &s);
+ std::string getLittleEndianFromInteger(unsigned int value,
+ unsigned int length);
+
+ int getIntegerFromBigEndian(const std::string &s);
+ std::string getBigEndianFromInteger(unsigned int value,
+ unsigned int length);
+
+ // Buffered read - allow this to be public
+ //
+ std::string getBytes(unsigned int numberOfBytes);
+
+ // Return file size
+ //
+ unsigned int getSize() const { return m_fileSize; }
+
+ void resetStream() { m_inFile->seekg(0); m_inFile->clear(); }
+
+ // check EOF status
+ //
+ bool isEof() const
+ { if (m_inFile) return m_inFile->eof(); else return true; }
+
+protected:
+ std::string m_fileName;
+
+ // get some bytes from an input stream - unbuffered as we can
+ // modify the file stream
+ std::string getBytes(std::ifstream *file, unsigned int numberOfBytes);
+
+ // Get n bytes from an input stream and write them into buffer.
+ // Return the actual number of bytes read.
+ size_t getBytes(std::ifstream *file, char *buffer, size_t n);
+
+ // write some bytes to an output stream
+ void putBytes(std::ofstream *file, const std::string outputString);
+
+ // write some bytes to an output stream
+ void putBytes(std::ofstream *file, const char *buffer, size_t n);
+
+ // Read buffering - define chunk size and buffer file reading
+ //
+ int m_readChunkPtr;
+ int m_readChunkSize;
+ std::string m_readBuffer;
+
+ std::ifstream *m_inFile;
+ std::ofstream *m_outFile;
+
+ bool m_loseBuffer; // do we need to dump the read buffer
+ // and re-fill it?
+
+ unsigned int m_fileSize;
+
+};
+
+}
+
+
+#endif // _SOUNDFILE_H_
+
+
diff --git a/src/sound/WAVAudioFile.cpp b/src/sound/WAVAudioFile.cpp
new file mode 100644
index 0000000..4e3b3bd
--- /dev/null
+++ b/src/sound/WAVAudioFile.cpp
@@ -0,0 +1,255 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "WAVAudioFile.h"
+#include "RealTime.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_DECODE 1
+
+namespace Rosegarden
+{
+
+WAVAudioFile::WAVAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName):
+ RIFFAudioFile(id, name, fileName)
+{
+ m_type = WAV;
+}
+
+WAVAudioFile::WAVAudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bytesPerSecond = 6000,
+ unsigned int bytesPerFrame = 2,
+ unsigned int bitsPerSample = 16):
+ RIFFAudioFile(fileName, channels, sampleRate, bytesPerSecond, bytesPerFrame, bitsPerSample)
+{
+ m_type = WAV;
+}
+
+WAVAudioFile::~WAVAudioFile()
+{}
+
+bool
+WAVAudioFile::open()
+{
+ // if already open
+ if (m_inFile && (*m_inFile))
+ return true;
+
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!(*m_inFile)) {
+ m_type = UNKNOWN;
+ return false;
+ }
+
+ // Get the file size and store it for comparison later
+ m_fileSize = m_fileInfo->size();
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException e) {
+ std::cerr << "ERROR: WAVAudioFile::open(): parseHeader: " << e.getMessage() << endl;
+ return false;
+ }
+
+ return true;
+}
+
+// Open the file for writing, write out the header and move
+// to the data chunk to accept samples. We fill in all the
+// totals when we close().
+//
+bool
+WAVAudioFile::write()
+{
+ // close if we're open
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+ // open for writing
+ m_outFile = new std::ofstream(m_fileName.c_str(),
+ std::ios::out | std::ios::binary);
+
+ if (!(*m_outFile))
+ return false;
+
+ // write out format header chunk and prepare for sample writing
+ //
+ writeFormatChunk();
+
+ return true;
+}
+
+void
+WAVAudioFile::close()
+{
+ if (m_outFile == 0)
+ return ;
+
+ m_outFile->seekp(0, std::ios::end);
+ unsigned int totalSize = m_outFile->tellp();
+
+ // seek to first length position
+ m_outFile->seekp(4, std::ios::beg);
+
+ // write complete file size minus 8 bytes to here
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 8, 4));
+
+ // reseek from start forward 40
+ m_outFile->seekp(40, std::ios::beg);
+
+ // write the data chunk size to end
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 44, 4));
+
+ m_outFile->close();
+
+ delete m_outFile;
+ m_outFile = 0;
+}
+
+// Set the AudioFile meta data according to WAV file format specification.
+//
+void
+WAVAudioFile::parseHeader()
+{
+ // Read the format chunk and populate the file data. A plain WAV
+ // file only has this chunk. Exceptions tumble through.
+ //
+ readFormatChunk();
+
+}
+
+std::streampos
+WAVAudioFile::getDataOffset()
+{
+ return 0;
+}
+
+bool
+WAVAudioFile::decode(const unsigned char *ubuf,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t nframes,
+ std::vector<float *> &target,
+ bool adding)
+{
+ size_t sourceChannels = getChannels();
+ size_t sourceSampleRate = getSampleRate();
+ size_t fileFrames = sourceBytes / getBytesPerFrame();
+
+ int bitsPerSample = getBitsPerSample();
+ if (bitsPerSample != 8 &&
+ bitsPerSample != 16 &&
+ bitsPerSample != 24 &&
+ bitsPerSample != 32) { // 32-bit is IEEE-float (enforced in RIFFAudioFile)
+ std::cerr << "WAVAudioFile::decode: unsupported " <<
+ bitsPerSample << "-bit sample size" << std::endl;
+ return false;
+ }
+
+#ifdef DEBUG_DECODE
+ std::cerr << "WAVAudioFile::decode: " << sourceBytes << " bytes -> " << nframes << " frames, SSR " << getSampleRate() << ", TSR " << targetSampleRate << ", sch " << getChannels() << ", tch " << targetChannels << std::endl;
+#endif
+
+ // If we're reading a stereo file onto a mono target, we mix the
+ // two channels. If we're reading mono to stereo, we duplicate
+ // the mono channel. Otherwise if the numbers of channels differ,
+ // we just copy across the ones that do match and zero the rest.
+
+ bool reduceToMono = (targetChannels == 1 && sourceChannels == 2);
+
+ for (size_t ch = 0; ch < sourceChannels; ++ch) {
+
+ if (!reduceToMono || ch == 0) {
+ if (ch >= targetChannels)
+ break;
+ if (!adding)
+ memset(target[ch], 0, nframes * sizeof(float));
+ }
+
+ int tch = ch; // target channel for this data
+ if (reduceToMono && ch == 1) {
+ tch = 0;
+ }
+
+ float ratio = 1.0;
+ if (sourceSampleRate != targetSampleRate) {
+ ratio = float(sourceSampleRate) / float(targetSampleRate);
+ }
+
+ for (size_t i = 0; i < nframes; ++i) {
+
+ size_t j = i;
+ if (sourceSampleRate != targetSampleRate) {
+ j = size_t(i * ratio);
+ }
+ if (j >= fileFrames)
+ j = fileFrames - 1;
+
+ float sample = convertBytesToSample
+ (&ubuf[(bitsPerSample / 8) * (ch + j * sourceChannels)]);
+
+ target[tch][i] += sample;
+ }
+ }
+
+ // Now deal with any excess target channels
+
+ for (int ch = sourceChannels; ch < targetChannels; ++ch) {
+ if (ch == 1 && targetChannels == 2) {
+ // copy mono to stereo
+ if (!adding) {
+ memcpy(target[ch], target[ch - 1], nframes * sizeof(float));
+ } else {
+ for (size_t i = 0; i < nframes; ++i) {
+ target[ch][i] += target[ch - 1][i];
+ }
+ }
+ } else {
+ if (!adding) {
+ memset(target[ch], 0, nframes * sizeof(float));
+ }
+ }
+ }
+
+ return true;
+}
+
+
+}
diff --git a/src/sound/WAVAudioFile.h b/src/sound/WAVAudioFile.h
new file mode 100644
index 0000000..ec57ec6
--- /dev/null
+++ b/src/sound/WAVAudioFile.h
@@ -0,0 +1,93 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+// Specialisation of a RIFF file - the WAV defines a format chunk
+// holding audio file meta data and a data chunk with interleaved
+// sample bytes.
+//
+
+#include "RIFFAudioFile.h"
+
+
+#ifndef _WAVAUDIOFILE_H_
+#define _WAVAUDIOFILE_H_
+
+namespace Rosegarden
+{
+
+class WAVAudioFile : public RIFFAudioFile
+{
+public:
+ WAVAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName);
+
+ WAVAudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerSample,
+ unsigned int bitsPerSample);
+
+ ~WAVAudioFile();
+
+ // Override these methods for the WAV
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ // 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<float *> &targetData,
+ bool addToResultBuffers = false);
+
+ // Get all header information
+ //
+ void parseHeader();
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset();
+
+ // Peak file name
+ //
+ virtual std::string getPeakFilename()
+ { return (m_fileName + std::string(".pk")); }
+
+
+protected:
+
+};
+
+}
+
+
+#endif // _WAVAUDIOFILE_H_