From f6f4414c989e55a867045a2575252b8b78289942 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 12 Oct 2009 01:09:35 -0700 Subject: [PATCH] NOT WORKING! Fx Mixer uses job threads The FxMixer now uses job threads to accomplish its mixing. It's theoretically efficient, but there is a horrible thread bug preventing the code from working. I've spent 5 hours debugging and need some external help! --- include/FxMixer.h | 49 ++++++++------ include/mixer.h | 21 ++++-- src/core/FxMixer.cpp | 149 ++++++++++++++++++++++++++++++------------- 3 files changed, 147 insertions(+), 72 deletions(-) diff --git a/include/FxMixer.h b/include/FxMixer.h index b9f11ca3e..d4e4df6a3 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -33,31 +33,38 @@ -struct FxChannel +class FxChannel : public ThreadableJob { - FxChannel( Model * _parent ); - ~FxChannel(); + public: + FxChannel( Model * _parent ); + ~FxChannel(); - EffectChain m_fxChain; + EffectChain m_fxChain; - // set to true if any effect in the channel is enabled and running - bool m_stillRunning; + // set to true if any effect in the channel is enabled and running + bool m_stillRunning; - float m_peakLeft; - float m_peakRight; - sampleFrame * m_buffer; - BoolModel m_muteModel; - FloatModel m_volumeModel; - QString m_name; - QMutex m_lock; + float m_peakLeft; + float m_peakRight; + sampleFrame * m_buffer; + BoolModel m_muteModel; + FloatModel m_volumeModel; + QString m_name; + QMutex m_lock; + int m_channelIndex; // what channel index are we - // pointers to other channels that this one sends to - QVector m_sends; - QVector m_sendAmount; + // pointers to other channels that this one sends to + QVector m_sends; + QVector m_sendAmount; - // pointers to other channels that send to this one - QVector m_receives; -} ; + // pointers to other channels that send to this one + QVector m_receives; + + virtual bool requiresProcessing() const { return true; } + + private: + virtual void doProcessing( sampleFrame * _working_buffer ); +}; @@ -128,9 +135,11 @@ private: // the fx channels in the mixer. index 0 is always master. QVector m_fxChannels; - + // make sure we have at least num channels void allocateChannelsTo(int num); + void addChannelLeaf( int _ch, sampleFrame * _buf ); + friend class mixerWorkerThread; friend class FxMixerView; diff --git a/include/mixer.h b/include/mixer.h index 81be6af7f..cb6b65aaa 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -69,21 +69,31 @@ class MixerWorkerThread; class ThreadableJob { public: + + enum ProcessingState + { + Unstarted, + Queued, + InProgress, + Done + }; + ThreadableJob() : - m_done( false ) + m_state( ThreadableJob::Unstarted ) { } void reset() { - m_done = false; + m_state = ThreadableJob::Unstarted; } bool process( sampleFrame * _working_buffer ) { - if( m_done.fetchAndStoreOrdered( true ) == false ) + if( m_state.testAndSetOrdered( Queued, InProgress ) ) { doProcessing( _working_buffer ); + m_state = Done; return true; } return false; @@ -91,12 +101,11 @@ public: virtual bool requiresProcessing() const = 0; + QAtomicInt m_state; private: virtual void doProcessing( sampleFrame * _working_buffer ) = 0; - QAtomicInt m_done; - } ; @@ -575,7 +584,7 @@ public: { if( _job->requiresProcessing() ) { - _job->reset(); + _job->m_state = ThreadableJob::Queued; s_jobQueue.items[s_jobQueue.queueSize.fetchAndAddOrdered(1)] = _job; } } diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 844f8fd7d..66c65537a 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -59,6 +59,75 @@ FxChannel::~FxChannel() +void FxChannel::doProcessing(sampleFrame * _buf) +{ + FxMixer * fxm = engine::fxMixer(); + + const fpp_t fpp = engine::getMixer()->framesPerPeriod(); + if( _buf == NULL ) + { + _buf = m_buffer; + } + + if( ! m_muteModel.value() ) + { + // do mixer sends. loop through the channels that send to this one + for( int i = 0; i < m_receives.size(); ++i) + { + fx_ch_t senderIndex = m_receives[i]; + FxChannel * sender = fxm->effectChannel(senderIndex); + + // mix it with this one + float amt = fxm->channelSendModel(senderIndex, + m_channelIndex)->value(); + sampleFrame * ch_buf = sender->m_buffer; + const float v = sender->m_volumeModel.value(); + for( f_cnt_t f = 0; f < fpp; ++f ) + { + _buf[f][0] += ch_buf[f][0] * v * amt; + _buf[f][1] += ch_buf[f][1] * v * amt; + } + } + + const float v = m_volumeModel.value(); + + m_fxChain.startRunning(); + m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp); + m_peakLeft = engine::getMixer()->peakValueLeft( _buf, fpp ) * v; + m_peakRight = engine::getMixer()->peakValueRight( _buf, fpp ) * v; + } + else + { + m_peakLeft = m_peakRight = 0.0f; + } + + m_state = ThreadableJob::Done; + + // check if any of its parents are now able to be processed + for(int i=0; ieffectChannel(m_sends[i]); + if( parent->m_state == ThreadableJob::Unstarted ) + { + bool everyLeafDone = true; + for( int j=0; jm_receives.size(); ++j ) + { + if( fxm->effectChannel(parent->m_receives[j])->m_state != + ThreadableJob::Done ) + { + everyLeafDone = false; + break; + } + } + if( everyLeafDone ) + { + MixerWorkerThread::addJob(parent); + } + } + } + +} @@ -363,51 +432,8 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) { - const fpp_t fpp = engine::getMixer()->framesPerPeriod(); - FxChannel * thisCh = m_fxChannels[_ch]; - if( _buf == NULL ) - { - _buf = thisCh->m_buffer; - } + m_fxChannels[_ch]->process(_buf); - if( ! thisCh->m_muteModel.value() ) - { - // do mixer sends. loop through the channels that send to this one - for( int i = 0; i < thisCh->m_receives.size(); ++i) - { - fx_ch_t senderIndex = thisCh->m_receives[i]; - FxChannel * sender = m_fxChannels[senderIndex]; - - // compute the sending channel - processChannel( senderIndex ); - - // mix it with this one - float amt = channelSendModel(senderIndex, _ch)->value(); - sampleFrame * ch_buf = sender->m_buffer; - const float v = sender->m_volumeModel.value(); - for( f_cnt_t f = 0; f < fpp; ++f ) - { - _buf[f][0] += ch_buf[f][0] * v * amt; - _buf[f][1] += ch_buf[f][1] * v * amt; - } - } - - - - const float v = thisCh->m_volumeModel.value(); - - thisCh->m_fxChain.startRunning(); - thisCh->m_stillRunning = thisCh-> - m_fxChain.processAudioBuffer( _buf, fpp); - thisCh->m_peakLeft = - engine::getMixer()->peakValueLeft( _buf, fpp ) * v; - thisCh->m_peakRight = - engine::getMixer()->peakValueRight( _buf, fpp ) * v; - } - else - { - thisCh->m_peakLeft = thisCh->m_peakRight = 0.0f; - } } @@ -421,13 +447,42 @@ void FxMixer::prepareMasterMix() +void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) +{ + FxChannel * thisCh = m_fxChannels[_ch]; + + // remember what channel number we are, 'cause we need it later + thisCh->m_channelIndex = _ch; + + int numDeps = thisCh->m_receives.size(); + if( numDeps > 0 ) + { + for(int i=0; im_receives[i], _buf ); + } + } + else + { + // add this channel to job list + MixerWorkerThread::addJob( thisCh ); + } + +} + + void FxMixer::masterMix( sampleFrame * _buf ) { const int fpp = engine::getMixer()->framesPerPeriod(); memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); - processChannel( 0, _buf ); + // recursively loop through channel dependency chain + // and add all channels to job list that have no dependencies + // when the channel completes it will check its parent to see if it needs + // to be processed. + addChannelLeaf( 0, _buf ); + MixerWorkerThread::startAndWaitForJobs(); const float v = m_fxChannels[0]->m_volumeModel.value(); for( f_cnt_t f = 0; f < engine::getMixer()->framesPerPeriod(); ++f ) @@ -439,11 +494,13 @@ void FxMixer::masterMix( sampleFrame * _buf ) m_fxChannels[0]->m_peakLeft *= engine::getMixer()->masterGain(); m_fxChannels[0]->m_peakRight *= engine::getMixer()->masterGain(); - // clear all channel buffers + // clear all channel buffers and + // reset channel process state for( int i = 0; i < numChannels(); ++i) { engine::getMixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer, engine::getMixer()->framesPerPeriod() ); + m_fxChannels[i]->reset(); } }