diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/sound | |
download | rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip |
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/sound')
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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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(¶m, 0, sizeof(struct sched_param)); + param.sched_priority = priority; + + if (pthread_attr_setschedparam(&attr, ¶m)) { + 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 ¤tTime) +{ + // 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 ¤tTime) +{ + // 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 ¤tTime) +{ + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 ¤tTime); + + /** + * 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 ¤tTime); + + /** + * 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 ¤tTime); + +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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 ¤tTime) +{ +#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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 ¤tTime); + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>", "Guillaume Laurent <[email protected]>", "Chris Cannam <[email protected]>" +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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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_ |