You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2464 lines
68 KiB
2464 lines
68 KiB
15 years ago
|
// -*- 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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|