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/AudioProcess.cpp | |
download | rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip |
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/sound/AudioProcess.cpp')
-rw-r--r-- | src/sound/AudioProcess.cpp | 2463 |
1 files changed, 2463 insertions, 0 deletions
diff --git a/src/sound/AudioProcess.cpp b/src/sound/AudioProcess.cpp new file mode 100644 index 0000000..9b44e13 --- /dev/null +++ b/src/sound/AudioProcess.cpp @@ -0,0 +1,2463 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <bownie@bownie.com> + + The moral right of the authors to claim authorship of this work + has been asserted. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioProcess.h" + +#include "RunnablePluginInstance.h" +#include "PlayableAudioFile.h" +#include "RecordableAudioFile.h" +#include "WAVAudioFile.h" +#include "MappedStudio.h" +#include "Profiler.h" +#include "AudioLevel.h" +#include "AudioPlayQueue.h" +#include "PluginFactory.h" + +#include <sys/time.h> +#include <pthread.h> + +#include <cmath> + +//#define DEBUG_THREAD_CREATE_DESTROY 1 +//#define DEBUG_BUSS_MIXER 1 +//#define DEBUG_MIXER 1 +//#define DEBUG_MIXER_LIGHTWEIGHT 1 +//#define DEBUG_LOCKS 1 +//#define DEBUG_READER 1 +//#define DEBUG_WRITER 1 + +namespace Rosegarden +{ + +/* Branch-free optimizer-resistant denormal killer courtesy of Simon + Jenkins on LAD: */ + +static inline float flushToZero(volatile float f) +{ + f += 9.8607615E-32f; + return f - 9.8607615E-32f; +} + +static inline void denormalKill(float *buffer, int size) +{ + for (int i = 0; i < size; ++i) { + buffer[i] = flushToZero(buffer[i]); + } +} + +AudioThread::AudioThread(std::string name, + SoundDriver *driver, + unsigned int sampleRate) : + m_name(name), + m_driver(driver), + m_sampleRate(sampleRate), + m_thread(0), + m_running(false), + m_exiting(false) +{ +#ifdef DEBUG_THREAD_CREATE_DESTROY + std::cerr << "AudioThread::AudioThread() [" << m_name << "]" << std::endl; +#endif + + pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER; + memcpy(&m_lock, &initialisingMutex, sizeof(pthread_mutex_t)); + + pthread_cond_t initialisingCondition = PTHREAD_COND_INITIALIZER; + memcpy(&m_condition, &initialisingCondition, sizeof(pthread_cond_t)); +} + +AudioThread::~AudioThread() +{ +#ifdef DEBUG_THREAD_CREATE_DESTROY + std::cerr << "AudioThread::~AudioThread() [" << m_name << "]" << std::endl; +#endif + + if (m_thread) { + pthread_mutex_destroy(&m_lock); + m_thread = 0; + } + +#ifdef DEBUG_THREAD_CREATE_DESTROY + std::cerr << "AudioThread::~AudioThread() exiting" << std::endl; +#endif +} + +void +AudioThread::run() +{ +#ifdef DEBUG_THREAD_CREATE_DESTROY + std::cerr << m_name << "::run()" << std::endl; +#endif + + pthread_attr_t attr; + pthread_attr_init(&attr); + + int priority = getPriority(); + + if (priority > 0) { + + if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) { + + std::cerr << m_name << "::run: WARNING: couldn't set FIFO scheduling " + << "on new thread" << std::endl; + pthread_attr_init(&attr); // reset to safety + + } else { + + struct sched_param param; + memset(¶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(); + } +} + + +} + |