From 45c4b7b824de19fbdc3bbbc3907dd6bb81151483 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Sep 2009 17:53:59 -0700 Subject: [PATCH 01/34] Mixer sends work in the backend The backend code of mixer uses mixer sends to compute effects. --- include/FxMixer.h | 20 +++++- src/core/FxMixer.cpp | 147 +++++++++++++++++++++++++++++++------------ src/core/mixer.cpp | 27 ++------ 3 files changed, 128 insertions(+), 66 deletions(-) diff --git a/include/FxMixer.h b/include/FxMixer.h index 2a6bd84e0..12d4af42a 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -40,8 +40,10 @@ struct FxChannel ~FxChannel(); EffectChain m_fxChain; - bool m_used; + + // 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; @@ -50,6 +52,11 @@ struct FxChannel QString m_name; QMutex m_lock; + // pointers to other channels that this one sends to + QVector m_sends; + + // pointers to other channels that send to this one + QVector m_receives; } ; @@ -86,11 +93,20 @@ public: return NULL; } + // make the output of channel fromChannel go to the input of channel toChannel + void createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel); + + // delete the connection made by createChannelSend + void deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel); + + // does fromChannel send its output to the input of toChannel? + bool channelSendsTo(fx_ch_t fromChannel, fx_ch_t toChannel); + + private: FxChannel * m_fxChannels[NumFxChannels+1]; // +1 = master - friend class mixerWorkerThread; friend class FxMixerView; diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index c7cfff902..ce8351cad 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -33,7 +33,6 @@ FxChannel::FxChannel( Model * _parent ) : m_fxChain( NULL ), - m_used( false ), m_stillRunning( false ), m_peakLeft( 0.0f ), m_peakRight( 0.0f ), @@ -64,10 +63,19 @@ FxMixer::FxMixer() : JournallingObject(), Model( NULL ) { - for( int i = 0; i < NumFxChannels+1; ++i ) + // create master channel + m_fxChannels[0] = new FxChannel(this); + + // create the rest of the channels + for( int i = 1; i < NumFxChannels+1; ++i ) { + // create new channel m_fxChannels[i] = new FxChannel( this ); + + // send the channel into master + createChannelSend(i, 0); } + // reset name etc. clear(); } @@ -85,6 +93,64 @@ FxMixer::~FxMixer() +void FxMixer::createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) +{ + // first make sure the send doesn't already exist + if( ! channelSendsTo(fromChannel, toChannel) ) + { + // add to from's sends + m_fxChannels[fromChannel]->m_sends.push_back(toChannel); + + // add to to's receives + m_fxChannels[toChannel]->m_receives.push_back(fromChannel); + } +} + + + +// delete the connection made by createChannelSend +void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) +{ + // delete the send + FxChannel * from = m_fxChannels[fromChannel]; + FxChannel * to = m_fxChannels[toChannel]; + + // find and delete the send entry + for(int i=0; im_sends.size(); ++i) { + if( from->m_sends[i] == toChannel ) + { + // delete this index + from->m_sends.remove(i); + break; + } + } + + // find and delete the receive entry + for(int i=0; im_receives.size(); ++i) + { + if( to->m_receives[i] == fromChannel ) + { + // delete this index + to->m_receives.remove(i); + break; + } + } +} + + + +// does fromChannel send its output to the input of toChannel? +bool FxMixer::channelSendsTo(fx_ch_t fromChannel, fx_ch_t toChannel) +{ + FxChannel * from = m_fxChannels[fromChannel]; + for(int i=0; im_sends.size(); ++i){ + if( from->m_sends[i] == toChannel ) + return true; + } + return false; +} + + void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) { @@ -93,7 +159,6 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) m_fxChannels[_ch]->m_lock.lock(); CPU::bufMix( m_fxChannels[_ch]->m_buffer, _buf, engine::getMixer()->framesPerPeriod() ); - m_fxChannels[_ch]->m_used = true; m_fxChannels[_ch]->m_lock.unlock(); } } @@ -103,32 +168,49 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) { - if( m_fxChannels[_ch]->m_muteModel.value() == false && - ( m_fxChannels[_ch]->m_used || - m_fxChannels[_ch]->m_stillRunning || - _ch == 0 ) ) + const fpp_t fpp = engine::getMixer()->framesPerPeriod(); + FxChannel * thisCh = m_fxChannels[_ch]; + 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 + 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; + _buf[f][1] += ch_buf[f][1] * v; + } + engine::getMixer()->clearAudioBuffer( ch_buf, + engine::getMixer()->framesPerPeriod() ); + } + + if( _buf == NULL ) { - _buf = m_fxChannels[_ch]->m_buffer; + _buf = thisCh->m_buffer; } - const fpp_t f = engine::getMixer()->framesPerPeriod(); - m_fxChannels[_ch]->m_fxChain.startRunning(); - m_fxChannels[_ch]->m_stillRunning = - m_fxChannels[_ch]->m_fxChain.processAudioBuffer( - _buf, f ); - m_fxChannels[_ch]->m_peakLeft = - engine::getMixer()->peakValueLeft( _buf, f ) * - m_fxChannels[_ch]->m_volumeModel.value(); - m_fxChannels[_ch]->m_peakRight = - engine::getMixer()->peakValueRight( _buf, f ) * - m_fxChannels[_ch]->m_volumeModel.value(); - m_fxChannels[_ch]->m_used = true; + 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 { - m_fxChannels[_ch]->m_peakLeft = - m_fxChannels[_ch]->m_peakRight = 0.0f; + thisCh->m_peakLeft = thisCh->m_peakRight = 0.0f; } } @@ -149,31 +231,14 @@ void FxMixer::masterMix( sampleFrame * _buf ) const int fpp = engine::getMixer()->framesPerPeriod(); memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); - for( int i = 1; i < NumFxChannels+1; ++i ) - { - if( m_fxChannels[i]->m_used ) - { - sampleFrame * ch_buf = m_fxChannels[i]->m_buffer; - const float v = m_fxChannels[i]->m_volumeModel.value(); - for( f_cnt_t f = 0; f < fpp; ++f ) - { - _buf[f][0] += ch_buf[f][0] * v; - _buf[f][1] += ch_buf[f][1] * v; - } - engine::getMixer()->clearAudioBuffer( ch_buf, - engine::getMixer()->framesPerPeriod() ); - m_fxChannels[i]->m_used = false; - } - } - processChannel( 0, _buf ); - if( m_fxChannels[0]->m_muteModel.value() ) + /*if( m_fxChannels[0]->m_muteModel.value() ) { engine::getMixer()->clearAudioBuffer( _buf, engine::getMixer()->framesPerPeriod() ); return; - } + }*/ const float v = m_fxChannels[0]->m_volumeModel.value(); for( f_cnt_t f = 0; f < engine::getMixer()->framesPerPeriod(); ++f ) diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index b82d6886f..35fdb62cd 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -143,16 +143,6 @@ public: private: virtual void run() { -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_PTHREAD_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( m_workerNum, &mask ); - pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); -#endif -#endif -#endif QMutex m; while( m_quit == false ) { @@ -618,15 +608,17 @@ sampleFrameA * mixer::renderNextBuffer() // STAGE 3: process effects in FX mixer - FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, + /*FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, MixerWorkerThread::EffectChannel,1); START_JOBS(); - WAIT_FOR_JOBS(); + WAIT_FOR_JOBS();*/ // STAGE 4: do master mix in FX mixer engine::fxMixer()->masterMix( m_writeBuf ); + WAIT_FOR_JOBS(); + unlock(); @@ -1137,17 +1129,6 @@ void mixer::fifoWriter::finish() void mixer::fifoWriter::run() { -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_PTHREAD_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( 0, &mask ); - pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); -#endif -#endif -#endif - const fpp_t frames = m_mixer->framesPerPeriod(); while( m_writing ) { From 89d5be7855cc0dc9b621a5aca987960a8ee754af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Sep 2009 09:41:57 -0700 Subject: [PATCH 02/34] Used FloatModels as the backend for mixer sends Can add new channels in the mixer, and sends are implemented. Instruments are hardcoded at 10. FL Import is hardcoded at 64. --- data/themes/default/send_bg_arrow.png | Bin 0 -> 267 bytes include/FxMixer.h | 30 ++-- include/FxMixerView.h | 12 +- include/classic_style.h | 2 +- include/cusis_style.h | 2 +- include/lmms_style.h | 2 +- plugins/flp_import/FlpImport.cpp | 14 +- src/core/FxMixer.cpp | 130 +++++++++------ src/core/mixer.cpp | 9 - src/gui/FxMixerView.cpp | 231 +++++++++++++++----------- src/gui/classic_style.cpp | 12 +- src/gui/cusis_style.cpp | 2 +- src/tracks/InstrumentTrack.cpp | 2 +- 13 files changed, 263 insertions(+), 185 deletions(-) create mode 100644 data/themes/default/send_bg_arrow.png diff --git a/data/themes/default/send_bg_arrow.png b/data/themes/default/send_bg_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..fde514da62ab29e40dd44632fa9757edc65ea2bf GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^GC*v>!3HGX8O+!Mq!^2X+?^QKos)S9lbCYGe8D3oWGWGJ|M`UZqI@`(c#rFptIhD02GdvhT#gQ9@T#a1hhrhnN^ z6J9q9r|YM%30?VLcJ%XR!L+5@BC}?1jO;tj$b<#dExmd%wr>8#%YrW!aH%zNtx7W5 m;Us&8>3@Lu?N?uVKQPJ7l)NGl%6T1VBZH@_pUXO@geCy^?oqG+ literal 0 HcmV?d00001 diff --git a/include/FxMixer.h b/include/FxMixer.h index 12d4af42a..116dbcde0 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -31,7 +31,6 @@ #include "JournallingObject.h" -const int NumFxChannels = 64; struct FxChannel @@ -54,6 +53,7 @@ struct FxChannel // 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; @@ -86,26 +86,36 @@ public: FxChannel * effectChannel( int _ch ) { - if( _ch >= 0 && _ch <= NumFxChannels ) - { - return m_fxChannels[_ch]; - } - return NULL; + return m_fxChannels[_ch]; } // make the output of channel fromChannel go to the input of channel toChannel - void createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel); + // it is safe to call even if the send already exists + void createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel, + float amount = 1.0f); // delete the connection made by createChannelSend void deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel); - // does fromChannel send its output to the input of toChannel? - bool channelSendsTo(fx_ch_t fromChannel, fx_ch_t toChannel); + // return the FloatModel of fromChannel sending its output to the input of + // toChannel. NULL if there is no send. + FloatModel * channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel); + // add a new channel to the Fx Mixer. + // returns the index of the channel that was just added + int createChannel(); + // reset a channel's name, fx, sends, etc + void clearChannel(fx_ch_t channelIndex); + + inline fx_ch_t numChannels() const + { + return m_fxChannels.size(); + } private: - FxChannel * m_fxChannels[NumFxChannels+1]; // +1 = master + // the fx channels in the mixer. index 0 is always master. + QVector m_fxChannels; friend class mixerWorkerThread; friend class FxMixerView; diff --git a/include/FxMixerView.h b/include/FxMixerView.h index dab34caaf..297ab5061 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -26,6 +26,8 @@ #define _FX_MIXER_VIEW_H #include +#include +#include #include "FxMixer.h" #include "ModelView.h" @@ -61,7 +63,7 @@ public: private slots: void updateFaders(); - + void addNewChannel(); private: struct FxChannelView @@ -72,13 +74,15 @@ private: fader * m_fader; } ; - FxChannelView m_fxChannelViews[NumFxChannels+1]; + QVector m_fxChannelViews; QStackedLayout * m_fxRacksLayout; - QStackedLayout * m_fxLineBanks; - QButtonGroup * m_bankButtons; FxLine * m_currentFxLine; + QScrollArea * channelArea; + QHBoxLayout * chLayout; + + void addFxLine(int i, QWidget * parent, QLayout * layout); } ; #endif diff --git a/include/classic_style.h b/include/classic_style.h index ccd1b5482..629960133 100644 --- a/include/classic_style.h +++ b/include/classic_style.h @@ -59,7 +59,7 @@ public: // LMMS Stuff virtual void drawFxLine(QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active); + const QString & _name, bool _active, bool _sendToThis); virtual void drawTrackContentBackground(QPainter * _painter, const QSize & _size, const int _pixelsPerTact); diff --git a/include/cusis_style.h b/include/cusis_style.h index 3e359cfd1..f000b520f 100644 --- a/include/cusis_style.h +++ b/include/cusis_style.h @@ -66,7 +66,7 @@ public: virtual void unpolish( QWidget * widget ); virtual void drawFxLine( QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active ); + const QString & _name, bool _active, bool _sendToThis ); virtual void drawTrackContentBackground( QPainter * _painter, const QSize & _size, const int _pixelsPerTact ); diff --git a/include/lmms_style.h b/include/lmms_style.h index eb6b0635c..3018e27c0 100644 --- a/include/lmms_style.h +++ b/include/lmms_style.h @@ -91,7 +91,7 @@ public: virtual void drawFxLine(QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active) = 0; + const QString & _name, bool _active, bool _sendToThis) = 0; virtual void drawTrackContentBackground(QPainter * _painter, const QSize & _size, const int _pixelsPerTact) = 0; diff --git a/plugins/flp_import/FlpImport.cpp b/plugins/flp_import/FlpImport.cpp index 1c36b5c4b..b243c9427 100644 --- a/plugins/flp_import/FlpImport.cpp +++ b/plugins/flp_import/FlpImport.cpp @@ -104,7 +104,7 @@ extern QString outstring; } - +const int NumFLFxChannels = 64; static void dump_mem( const void * buffer, uint n_bytes ) { @@ -542,7 +542,7 @@ struct FL_Project int currentPattern; int activeEditPattern; - FL_EffectChannel effectChannels[NumFxChannels+1]; + FL_EffectChannel effectChannels[NumFLFxChannels+1]; int currentEffectChannel; QString projectNotes; @@ -1022,7 +1022,7 @@ bool FlpImport::tryFLPImport( trackContainer * _tc ) break; case FLP_EffectChannelMuted: -if( p.currentEffectChannel <= NumFxChannels ) +if( p.currentEffectChannel <= NumFLFxChannels ) { p.effectChannels[p.currentEffectChannel].isMuted = ( data & 0x08 ) > 0 ? false : true; @@ -1274,7 +1274,7 @@ if( p.currentEffectChannel <= NumFxChannels ) case FLP_Text_EffectChanName: ++p.currentEffectChannel; - if( p.currentEffectChannel <= NumFxChannels ) + if( p.currentEffectChannel <= NumFLFxChannels ) { p.effectChannels[p.currentEffectChannel].name = text; } @@ -1497,7 +1497,7 @@ if( p.currentEffectChannel <= NumFxChannels ) const int param = pi[i*3+1] & 0xffff; const int ch = ( pi[i*3+1] >> 22 ) & 0x7f; - if( ch < 0 || ch > NumFxChannels ) + if( ch < 0 || ch > NumFLFxChannels ) { continue; } @@ -1797,7 +1797,7 @@ p->putValue( jt->pos, value, false ); } } - for( int fx_ch = 0; fx_ch <= NumFxChannels ; ++fx_ch ) + for( int fx_ch = 0; fx_ch <= NumFLFxChannels ; ++fx_ch ) { FxChannel * ch = engine::fxMixer()->effectChannel( fx_ch ); if( !ch ) @@ -1857,7 +1857,7 @@ p->putValue( jt->pos, value, false ); break; } if( effName.isEmpty() || it->fxChannel < 0 || - it->fxChannel > NumFxChannels ) + it->fxChannel > NumFLFxChannels ) { continue; } diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index ce8351cad..53c1152a7 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -61,49 +61,64 @@ FxChannel::~FxChannel() FxMixer::FxMixer() : JournallingObject(), - Model( NULL ) + Model( NULL ), + m_fxChannels() { // create master channel - m_fxChannels[0] = new FxChannel(this); - - // create the rest of the channels - for( int i = 1; i < NumFxChannels+1; ++i ) - { - // create new channel - m_fxChannels[i] = new FxChannel( this ); - - // send the channel into master - createChannelSend(i, 0); - } - - // reset name etc. - clear(); + createChannel(); } - FxMixer::~FxMixer() { - for( int i = 0; i < NumFxChannels+1; ++i ) + for( int i = 0; i < m_fxChannels.size(); ++i ) { + delete m_fxChannels[i]->m_sendAmount[i]; delete m_fxChannels[i]; } } -void FxMixer::createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) +int FxMixer::createChannel() { - // first make sure the send doesn't already exist - if( ! channelSendsTo(fromChannel, toChannel) ) - { - // add to from's sends - m_fxChannels[fromChannel]->m_sends.push_back(toChannel); + // create new channel + m_fxChannels.push_back(new FxChannel( this )); - // add to to's receives - m_fxChannels[toChannel]->m_receives.push_back(fromChannel); + // reset channel state + int index = m_fxChannels.size() - 1; + clearChannel(index); + + return index; +} + + + +void FxMixer::createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel, + float amount) +{ + // find the existing connection + FxChannel * from = m_fxChannels[fromChannel]; + for(int i=0; im_sends.size(); ++i){ + if( from->m_sends[i] == toChannel ) + { + // simply adjust the amount + from->m_sendAmount[i]->setValue(amount); + return; + } } + + // connection does not exist. create a new one + + // add to from's sends + from->m_sends.push_back(toChannel); + from->m_sendAmount.push_back(new FloatModel(amount, 0, 1, 0.001, NULL, + tr("Amount to send"))); + + // add to to's receives + m_fxChannels[toChannel]->m_receives.push_back(fromChannel); + } @@ -120,6 +135,8 @@ void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) if( from->m_sends[i] == toChannel ) { // delete this index + delete from->m_sendAmount[i]; + from->m_sendAmount.remove(i); from->m_sends.remove(i); break; } @@ -139,15 +156,15 @@ void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) -// does fromChannel send its output to the input of toChannel? -bool FxMixer::channelSendsTo(fx_ch_t fromChannel, fx_ch_t toChannel) +// how much does fromChannel send its output to the input of toChannel? +FloatModel * FxMixer::channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel) { FxChannel * from = m_fxChannels[fromChannel]; for(int i=0; im_sends.size(); ++i){ if( from->m_sends[i] == toChannel ) - return true; + return from->m_sendAmount[i]; } - return false; + return NULL; } @@ -182,12 +199,13 @@ void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) 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; - _buf[f][1] += ch_buf[f][1] * v; + _buf[f][0] += ch_buf[f][0] * v * amt; + _buf[f][1] += ch_buf[f][1] * v * amt; } engine::getMixer()->clearAudioBuffer( ch_buf, engine::getMixer()->framesPerPeriod() ); @@ -233,13 +251,6 @@ void FxMixer::masterMix( sampleFrame * _buf ) processChannel( 0, _buf ); - /*if( m_fxChannels[0]->m_muteModel.value() ) - { - engine::getMixer()->clearAudioBuffer( _buf, - engine::getMixer()->framesPerPeriod() ); - return; - }*/ - const float v = m_fxChannels[0]->m_volumeModel.value(); for( f_cnt_t f = 0; f < engine::getMixer()->framesPerPeriod(); ++f ) { @@ -256,25 +267,46 @@ void FxMixer::masterMix( sampleFrame * _buf ) void FxMixer::clear() { - for( int i = 0; i <= NumFxChannels; ++i ) + for( int i = 0; i < m_fxChannels.size(); ++i ) { - m_fxChannels[i]->m_fxChain.clear(); - m_fxChannels[i]->m_volumeModel.setValue( 1.0f ); - m_fxChannels[i]->m_muteModel.setValue( false ); - m_fxChannels[i]->m_name = ( i == 0 ) ? - tr( "Master" ) : tr( "FX %1" ).arg( i ); - m_fxChannels[i]->m_volumeModel.setDisplayName( - m_fxChannels[i]->m_name ); - + clearChannel(i); } } +void FxMixer::clearChannel(fx_ch_t index) +{ + FxChannel * ch = m_fxChannels[index]; + ch->m_fxChain.clear(); + ch->m_volumeModel.setValue( 1.0f ); + ch->m_muteModel.setValue( false ); + ch->m_name = ( index == 0 ) ? tr( "Master" ) : tr( "FX %1" ).arg( index ); + ch->m_volumeModel.setDisplayName(ch->m_name ); + // send only to master + if( index > 0) + { + // delete existing sends + for( int i=0; im_sends.size(); ++i) + { + deleteChannelSend(index, ch->m_sends[i]); + } + + // add send to master + createChannelSend(index, 0); + } + + // delete receives + for( int i=0; im_receives.size(); ++i) + { + deleteChannelSend(ch->m_receives[i], index); + } + +} void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - for( int i = 0; i <= NumFxChannels; ++i ) + for( int i = 0; i < m_fxChannels.size(); ++i ) { QDomElement fxch = _doc.createElement( QString( "fxchannel" ) ); _this.appendChild( fxch ); @@ -295,7 +327,7 @@ void FxMixer::loadSettings( const QDomElement & _this ) { clear(); QDomNode node = _this.firstChild(); - for( int i = 0; i <= NumFxChannels; ++i ) + for( int i = 0; i <= 64; ++i ) // TODO make this work { QDomElement fxch = node.toElement(); int num = fxch.attribute( "num" ).toInt(); diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index 35fdb62cd..819ef0be2 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -61,10 +61,6 @@ #endif -static QVector __fx_channel_jobs( NumFxChannels ); - - - class MixerWorkerThread : public QThread { public: @@ -284,11 +280,6 @@ mixer::mixer() : clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] ); } - for( int i = 1; i < NumFxChannels+1; ++i ) - { - __fx_channel_jobs[i-1] = (fx_ch_t) i; - } - // just rendering? if( !engine::hasGUI() ) { diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index bde8e5a04..3be23bcfe 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -22,6 +22,8 @@ * */ +#include + #include #include #include @@ -31,9 +33,12 @@ #include #include #include +#include + #include "FxMixerView.h" #include "fader.h" +#include "knob.h" #include "EffectRackView.h" #include "engine.h" #include "embed.h" @@ -44,26 +49,40 @@ #include "pixmap_button.h" +class SendIndicator : public QWidget +{ + public: + SendIndicator( QWidget * _parent ) : + QWidget( _parent ) + { + setFixedSize(23, 16); + } + +}; class FxLine : public QWidget { public: - FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name ) : + FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name, + int _channelIndex) : QWidget( _parent ), + m_channelIndex( _channelIndex ), m_mv( _mv ), m_name( _name ) { - setFixedSize( 32, 232 ); + setFixedSize( 32, 287 ); setAttribute( Qt::WA_OpaquePaintEvent, true ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); } virtual void paintEvent( QPaintEvent * ) { + bool sendToThis = engine::fxMixer()->channelSendModel( + m_mv->currentFxLine()->m_channelIndex, m_channelIndex) != NULL; QPainter painter; painter.begin( this ); - engine::getLmmsStyle()->drawFxLine( &painter, - this, m_name, m_mv->currentFxLine() == this ); + engine::getLmmsStyle()->drawFxLine( &painter, this, m_name, + m_mv->currentFxLine() == this, sendToThis ); painter.end(); } @@ -87,7 +106,8 @@ public: } } - + knob * m_sendKnob; + int m_channelIndex; private: FxMixerView * m_mv; QString & m_name; @@ -114,10 +134,6 @@ FxMixerView::FxMixerView() : setWindowTitle( tr( "FX-Mixer" ) ); setWindowIcon( embed::getIconPixmap( "fx_mixer" ) ); - m_fxLineBanks = new QStackedLayout; - m_fxLineBanks->setSpacing( 0 ); - m_fxLineBanks->setMargin( 1 ); - m_fxRacksLayout = new QStackedLayout; m_fxRacksLayout->setSpacing( 0 ); m_fxRacksLayout->setMargin( 0 ); @@ -128,93 +144,40 @@ FxMixerView::FxMixerView() : ml->setSpacing( 0 ); ml->addSpacing( 6 ); + m_fxChannelViews.resize(m->numChannels()); + channelArea = new QScrollArea(this); + chLayout = new QHBoxLayout(channelArea); - QHBoxLayout * banks[NumFxChannels/16]; - for( int i = 0; i < NumFxChannels/16; ++i ) + // add master channel + FxChannelView * masterView = &m_fxChannelViews[0]; + addFxLine(0, this, ml); + ml->addSpacing(5); + QSize fxLineSize = masterView->m_fxLine->size(); + + chLayout->setSizeConstraint(QLayout::SetMinimumSize); + channelArea->setWidgetResizable(true); + + // add mixer channels + for( int i = 1; i < m_fxChannelViews.size(); ++i ) { - QWidget * w = new QWidget( this ); - banks[i] = new QHBoxLayout( w ); - banks[i]->setMargin( 5 ); - banks[i]->setSpacing( 1 ); - m_fxLineBanks->addWidget( w ); + addFxLine(i, channelArea, chLayout); } + // add the scrolling section to the main layout + ml->addLayout(chLayout); - for( int i = 0; i < NumFxChannels+1; ++i ) - { - FxChannelView * cv = &m_fxChannelViews[i]; - if( i == 0 ) - { - cv->m_fxLine = new FxLine( NULL, this, - m->m_fxChannels[i]->m_name ); - ml->addWidget( cv->m_fxLine ); - ml->addSpacing( 10 ); - } - else - { - const int bank = (i-1) / 16; - cv->m_fxLine = new FxLine( NULL, this, - m->m_fxChannels[i]->m_name ); - banks[bank]->addWidget( cv->m_fxLine ); - } - lcdSpinBox * l = new lcdSpinBox( 2, cv->m_fxLine ); - l->model()->setRange( i, i ); - l->model()->setValue( i ); - l->move( 2, 4 ); - l->setMarginWidth( 1 ); + // show the add new effect channel button + QPushButton * newChannelBtn = new QPushButton("new", this ); + newChannelBtn->setFont(QFont("sans-serif", 10, 1, false)); + newChannelBtn->setFixedSize(fxLineSize); + connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); + ml->addWidget( newChannelBtn ); - - cv->m_fader = new fader( &m->m_fxChannels[i]->m_volumeModel, - tr( "FX Fader %1" ).arg( i ), - cv->m_fxLine ); - cv->m_fader->move( 15-cv->m_fader->width()/2, - cv->m_fxLine->height()- - cv->m_fader->height()-5 ); - - cv->m_muteBtn = new pixmapButton( cv->m_fxLine, tr( "Mute" ) ); - cv->m_muteBtn->setModel( &m->m_fxChannels[i]->m_muteModel ); - cv->m_muteBtn->setActiveGraphic( - embed::getIconPixmap( "led_off" ) ); - cv->m_muteBtn->setInactiveGraphic( - embed::getIconPixmap( "led_green" ) ); - cv->m_muteBtn->setCheckable( true ); - cv->m_muteBtn->move( 9, cv->m_fader->y()-16); - toolTip::add( cv->m_muteBtn, tr( "Mute this FX channel" ) ); - - cv->m_rackView = new EffectRackView( - &m->m_fxChannels[i]->m_fxChain, this ); - m_fxRacksLayout->addWidget( cv->m_rackView ); - if( i == 0 ) - { - QVBoxLayout * l = new QVBoxLayout; - l->addSpacing( 10 ); - QButtonGroup * g = new QButtonGroup( this ); - m_bankButtons = g; - g->setExclusive( true ); - for( int j = 0; j < 4; ++j ) - { - QToolButton * btn = new QToolButton; - btn->setText( QString( 'A'+j ) ); - btn->setCheckable( true ); - btn->setSizePolicy( QSizePolicy::Preferred, - QSizePolicy::Expanding ); - l->addWidget( btn ); - g->addButton( btn, j ); - btn->setChecked( j == 0); - } - l->addSpacing( 10 ); - ml->addLayout( l ); - connect( g, SIGNAL( buttonClicked( int ) ), - m_fxLineBanks, SLOT( setCurrentIndex( int ) ) ); - } - } - - ml->addLayout( m_fxLineBanks ); ml->addLayout( m_fxRacksLayout ); + setLayout( ml ); updateGeometry(); - m_fxLineBanks->setCurrentIndex( 0 ); setCurrentFxLine( m_fxChannelViews[0].m_fxLine ); // timer for updating faders @@ -226,10 +189,9 @@ FxMixerView::FxMixerView() : QMdiSubWindow * subWin = engine::mainWindow()->workspace()->addSubWindow( this ); Qt::WindowFlags flags = subWin->windowFlags(); - flags |= Qt::MSWindowsFixedSizeDialogHint; flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); - subWin->layout()->setSizeConstraint(QLayout::SetFixedSize); + subWin->layout()->setSizeConstraint(QLayout::SetMinimumSize); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); @@ -239,6 +201,54 @@ FxMixerView::FxMixerView() : } +void FxMixerView::addFxLine(int i, QWidget * parent, QLayout * layout) +{ + FxMixer * m = engine::fxMixer(); + + FxChannelView * cv = &m_fxChannelViews[i]; + + cv->m_fxLine = new FxLine( parent, this, + m->m_fxChannels[i]->m_name, i ); + layout->addWidget(cv->m_fxLine); + + // mixer sends knob + cv->m_fxLine->m_sendKnob = new knob(0, cv->m_fxLine, + tr("Channel send amount")); + cv->m_fxLine->m_sendKnob->move(0, 22); + cv->m_fxLine->m_sendKnob->setVisible(false); + + // send light indicator + + + // channel number + lcdSpinBox * l = new lcdSpinBox( 2, cv->m_fxLine ); + l->model()->setRange( i, i ); + l->model()->setValue( i ); + l->move( 2, 58 ); + l->setMarginWidth( 1 ); + + + cv->m_fader = new fader( &m->m_fxChannels[i]->m_volumeModel, + tr( "FX Fader %1" ).arg( i ), + cv->m_fxLine ); + cv->m_fader->move( 15-cv->m_fader->width()/2, + cv->m_fxLine->height()- + cv->m_fader->height()-5 ); + + cv->m_muteBtn = new pixmapButton( cv->m_fxLine, tr( "Mute" ) ); + cv->m_muteBtn->setModel( &m->m_fxChannels[i]->m_muteModel ); + cv->m_muteBtn->setActiveGraphic( + embed::getIconPixmap( "led_off" ) ); + cv->m_muteBtn->setInactiveGraphic( + embed::getIconPixmap( "led_green" ) ); + cv->m_muteBtn->setCheckable( true ); + cv->m_muteBtn->move( 9, cv->m_fader->y()-16); + toolTip::add( cv->m_muteBtn, tr( "Mute this FX channel" ) ); + + cv->m_rackView = new EffectRackView( + &m->m_fxChannels[i]->m_fxChain, this ); + m_fxRacksLayout->addWidget( cv->m_rackView ); +} FxMixerView::~FxMixerView() @@ -247,6 +257,18 @@ FxMixerView::~FxMixerView() +void FxMixerView::addNewChannel() +{ + // add new fx mixer channel and redraw the form. + FxMixer * mix = engine::fxMixer(); + + int newChannelIndex = mix->createChannel(); + m_fxChannelViews.push_back(FxChannelView()); + + addFxLine(newChannelIndex, channelArea, chLayout); +} + + void FxMixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { @@ -266,13 +288,29 @@ void FxMixerView::loadSettings( const QDomElement & _this ) void FxMixerView::setCurrentFxLine( FxLine * _line ) { + FxMixer * mix = engine::fxMixer(); + + // select m_currentFxLine = _line; - for( int i = 0; i < NumFxChannels+1; ++i ) + m_fxRacksLayout->setCurrentIndex( _line->m_channelIndex ); + + // set up send knob + for(int i = 0; i < m_fxChannelViews.size(); ++i) { - if( m_fxChannelViews[i].m_fxLine == _line ) + // does current channel send to this channel? + FloatModel * sendModel = mix->channelSendModel(_line->m_channelIndex, i); + if( sendModel == NULL ) { - m_fxRacksLayout->setCurrentIndex( i ); + // does not send, hide send knob + m_fxChannelViews[i].m_fxLine->m_sendKnob->setVisible(false); } + else + { + // it does send, show knob and connect + m_fxChannelViews[i].m_fxLine->m_sendKnob->setVisible(true); + m_fxChannelViews[i].m_fxLine->m_sendKnob->setModel(sendModel); + } + m_fxChannelViews[i].m_fxLine->update(); } } @@ -281,12 +319,7 @@ void FxMixerView::setCurrentFxLine( FxLine * _line ) void FxMixerView::setCurrentFxLine( int _line ) { - if ( _line >= 0 && _line < NumFxChannels+1 ) - { - setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); - - m_bankButtons->button( (_line-1) / 16 )->click(); - } + setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); } @@ -294,7 +327,7 @@ void FxMixerView::setCurrentFxLine( int _line ) void FxMixerView::clear() { - for( int i = 0; i <= NumFxChannels; ++i ) + for( int i = 0; i < m_fxChannelViews.size(); ++i ) { m_fxChannelViews[i].m_rackView->clearViews(); } @@ -306,7 +339,7 @@ void FxMixerView::clear() void FxMixerView::updateFaders() { FxMixer * m = engine::fxMixer(); - for( int i = 0; i < NumFxChannels+1; ++i ) + for( int i = 0; i < m_fxChannelViews.size(); ++i ) { const float opl = m_fxChannelViews[i].m_fader->getPeak_L(); const float opr = m_fxChannelViews[i].m_fader->getPeak_R(); diff --git a/src/gui/classic_style.cpp b/src/gui/classic_style.cpp index 602fcdf78..9160c9028 100644 --- a/src/gui/classic_style.cpp +++ b/src/gui/classic_style.cpp @@ -279,7 +279,7 @@ int ClassicStyle::pixelMetric( PixelMetric _metric, void ClassicStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active ) + const QString & _name, bool _active, bool _sendToThis ) { int width = _fxLine->rect().width(); int height = _fxLine->rect().height(); @@ -293,10 +293,18 @@ void ClassicStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine, p->setPen( QColor( 20, 24, 32 ) ); p->drawRect( 0, 0, width-1, height-1 ); + // draw the mixer send background + if( _sendToThis ) + { + p->drawPixmap(2, 0, 28, 56, + embed::getIconPixmap("send_bg_arrow", 28, 56)); + } + + // draw the channel name p->rotate( -90 ); p->setPen( _active ? QColor( 0, 255, 0 ) : Qt::white ); p->setFont( pointSizeF( _fxLine->font(), 7.5f ) ); - p->drawText( -90, 20, _name ); + p->drawText( -145, 20, _name ); } void ClassicStyle::drawTrackContentBackground(QPainter * _painter, diff --git a/src/gui/cusis_style.cpp b/src/gui/cusis_style.cpp index 87a739a27..a78d14762 100644 --- a/src/gui/cusis_style.cpp +++ b/src/gui/cusis_style.cpp @@ -881,7 +881,7 @@ int CusisStyle::pixelMetric( PixelMetric _metric, const QStyleOption * _option, void CusisStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active ) + const QString & _name, bool _active, bool _sendToThis ) { int width = _fxLine->rect().width(); int height = _fxLine->rect().height(); diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index cbddc519e..f56d6487b 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -103,7 +103,7 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) : this, tr( "Panning" ) ), m_pitchModel( 0, -100, 100, 1, this, tr( "Pitch" ) ), m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ), - m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ), + m_effectChannelModel( 0, 0, 10, this, tr( "FX channel" ) ), // change this so it's a combo box, all the channels and then new. m_instrument( NULL ), m_soundShaping( this ), m_arpeggiator( this ), From 289f004c281bc835ad0b593da16fe36ab42051b5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Sep 2009 17:43:28 -0700 Subject: [PATCH 03/34] Pretty interface in default theme for fx sends Created a user interface so you can add and remove FX sends as well as tweak how much you are sending. The gui does not yet prevent the user from creating endless render loops. Additionally, there seems to be some issues to work out regarding rendering with multiple sends. --- data/themes/default/mixer_send_off.png | Bin 0 -> 467 bytes data/themes/default/mixer_send_on.png | Bin 0 -> 471 bytes include/FxLine.h | 38 ++++ include/FxMixerView.h | 70 +++++--- include/SendButtonIndicator.h | 32 ++++ src/core/FxMixer.cpp | 15 +- src/gui/FxLine.cpp | 77 +++++++++ src/gui/FxMixerView.cpp | 231 ++++++++----------------- src/gui/SendButtonIndicator.cpp | 53 ++++++ 9 files changed, 324 insertions(+), 192 deletions(-) create mode 100644 data/themes/default/mixer_send_off.png create mode 100644 data/themes/default/mixer_send_on.png create mode 100644 include/FxLine.h create mode 100644 include/SendButtonIndicator.h create mode 100644 src/gui/FxLine.cpp create mode 100644 src/gui/SendButtonIndicator.cpp diff --git a/data/themes/default/mixer_send_off.png b/data/themes/default/mixer_send_off.png new file mode 100644 index 0000000000000000000000000000000000000000..6f426c36f9599a1fd8dbef8de6a5577d8bd17a86 GIT binary patch literal 467 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6G!3HG%>OYYHQjEnx?oJHr&dIz4a@dl*-CY>| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&aw9`+JXUsv{pv8=Oo-U3d7N_4%_Vs2;6ltAbUXgxcsrj+STZmA#r#x`+VakQr%=69w}#N>KWdkoG$usOe!NJm`PLE-Y4eAX_o^#) zJU<}%Tzq=`8c(k5D{T8hgc2^izxdWfvfrNZ#8d7m-md+HR^958ToxOiTB@|OhB4vU z)S#m#y5Ak`5&v(|sw-B&;AQZ1^>bP0 Hl+XkKQoFyV literal 0 HcmV?d00001 diff --git a/data/themes/default/mixer_send_on.png b/data/themes/default/mixer_send_on.png new file mode 100644 index 0000000000000000000000000000000000000000..6861c7acdf717cd4e6b164956346fa7413447423 GIT binary patch literal 471 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6G!3HG%>OYYHQjEnx?oJHr&dIz4a@dl*-CY>| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&aw9`+JXUsv{wc6x=<11Hv2mfEF_vdAc};Se#DYvmx@kJ##OEFf;GL6%QjA zxtW=n4{tlXUvgUh@3SBN39IC7_j#K3;pD%rMFMKmL{6L%cyj32gUNrVCY}z-V*`R6 zQF?rAY__IpZD$J1*b*7lIvy5Z|Cf}Z{U9M=a(YriN{Wl2fb5#vzuOOzd3YmbCL~-8NJ^Tq>yP;1M`!m--mCf1etospCLf+?{(r}`jvqdH zLg4l3V+B92#k|p^cO=b!v%Y|bc^{t+Pxkje$1*LIt7aG* z8yme?ZERRjyvg8=-9PsH4HB6~#vclejcR^1&HSElaM9{xJq9w*jO6^-+9c$|M5Zr} zK62v3i3iE&PZ=a%)fZ{5-L&7Rz)9J}yo+~HnaOW{{lkpCf(e%sr~M281~G%DtDnm{ Hr-UW|EQGns literal 0 HcmV?d00001 diff --git a/include/FxLine.h b/include/FxLine.h new file mode 100644 index 000000000..1700ebba3 --- /dev/null +++ b/include/FxLine.h @@ -0,0 +1,38 @@ +#ifndef FXLINE_H +#define FXLINE_H + +#include +#include + +#include "knob.h" +#include "SendButtonIndicator.h" + +class FxMixerView; +class SendButtonIndicator; + +class FxLine : public QWidget +{ + Q_OBJECT +public: + FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex); + + virtual void paintEvent( QPaintEvent * ); + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseDoubleClickEvent( QMouseEvent * ); + + inline int channelIndex() { return m_channelIndex; } + + knob * m_sendKnob; + SendButtonIndicator * m_sendBtn; + + +private: + FxMixerView * m_mv; + + + int m_channelIndex; + +} ; + + +#endif // FXLINE_H diff --git a/include/FxMixerView.h b/include/FxMixerView.h index 297ab5061..461379728 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -29,60 +29,74 @@ #include #include +#include "FxLine.h" #include "FxMixer.h" #include "ModelView.h" +#include "engine.h" +#include "fader.h" +#include "pixmap_button.h" +#include "tooltip.h" +#include "embed.h" +#include "EffectRackView.h" class QStackedLayout; class QButtonGroup; -class fader; class FxLine; -class EffectRackView; -class pixmapButton; - class FxMixerView : public QWidget, public ModelView, public SerializingObjectHook { Q_OBJECT public: - FxMixerView(); - virtual ~FxMixerView(); - - virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); - virtual void loadSettings( const QDomElement & _this ); - - FxLine * currentFxLine() - { - return m_currentFxLine; - } - void setCurrentFxLine( FxLine * _line ); - void setCurrentFxLine( int _line ); - - void clear(); - - -private slots: - void updateFaders(); - void addNewChannel(); - -private: struct FxChannelView { + FxChannelView(QWidget * _parent, FxMixerView * _mv, int _chIndex ); + FxLine * m_fxLine; EffectRackView * m_rackView; pixmapButton * m_muteBtn; fader * m_fader; } ; - QVector m_fxChannelViews; + + FxMixerView(); + virtual ~FxMixerView(); + + virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); + virtual void loadSettings( const QDomElement & _this ); + + inline FxLine * currentFxLine() + { + return m_currentFxLine; + } + + inline FxChannelView * channelView(int index) + { + return m_fxChannelViews[index]; + } + + void setCurrentFxLine( FxLine * _line ); + void setCurrentFxLine( int _line ); + + void clear(); + + + // display the send button and knob correctly + void updateFxLine(int i); + +private slots: + void updateFaders(); + void addNewChannel(); + +private: + + QVector m_fxChannelViews; QStackedLayout * m_fxRacksLayout; FxLine * m_currentFxLine; QScrollArea * channelArea; QHBoxLayout * chLayout; - - void addFxLine(int i, QWidget * parent, QLayout * layout); } ; #endif diff --git a/include/SendButtonIndicator.h b/include/SendButtonIndicator.h new file mode 100644 index 000000000..f2c7ef44d --- /dev/null +++ b/include/SendButtonIndicator.h @@ -0,0 +1,32 @@ +#ifndef SENDBUTTONINDICATOR_H +#define SENDBUTTONINDICATOR_H + +#include +#include +#include + +#include "FxLine.h" +#include "FxMixerView.h" + +class FxLine; +class FxMixerView; + +class SendButtonIndicator : public QLabel { + public: + SendButtonIndicator( QWidget * _parent, FxLine * _owner, + FxMixerView * _mv); + + virtual void mousePressEvent( QMouseEvent * e ); + void updateLightStatus(); + + private: + + FxLine * m_parent; + FxMixerView * m_mv; + QPixmap qpmOn; + QPixmap qpmOff; + + FloatModel * getSendModel(); +}; + +#endif // SENDBUTTONINDICATOR_H diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 53c1152a7..b2dd852e2 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -74,7 +74,10 @@ FxMixer::~FxMixer() { for( int i = 0; i < m_fxChannels.size(); ++i ) { - delete m_fxChannels[i]->m_sendAmount[i]; + for( int j = 0; j < m_fxChannels[i]->m_sendAmount.size(); ++j) + { + delete m_fxChannels[i]->m_sendAmount[j]; + } delete m_fxChannels[i]; } } @@ -187,6 +190,11 @@ 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; + } + if( ! thisCh->m_muteModel.value() ) { // do mixer sends. loop through the channels that send to this one @@ -212,10 +220,7 @@ void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) } - if( _buf == NULL ) - { - _buf = thisCh->m_buffer; - } + const float v = thisCh->m_volumeModel.value(); thisCh->m_fxChain.startRunning(); diff --git a/src/gui/FxLine.cpp b/src/gui/FxLine.cpp new file mode 100644 index 000000000..fe92ba66b --- /dev/null +++ b/src/gui/FxLine.cpp @@ -0,0 +1,77 @@ +#include "FxLine.h" + +#include +#include +#include +#include + +#include "FxMixer.h" +#include "FxMixerView.h" +#include "embed.h" +#include "engine.h" +#include "lcd_spinbox.h" +#include "SendButtonIndicator.h" + +FxLine::FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex) : + QWidget( _parent ), + m_mv( _mv ), + m_channelIndex( _channelIndex ) +{ + setFixedSize( 32, 287 ); + setAttribute( Qt::WA_OpaquePaintEvent, true ); + setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); + + // mixer sends knob + m_sendKnob = new knob(0, this, tr("Channel send amount")); + m_sendKnob->move(0, 22); + m_sendKnob->setVisible(false); + + // send button indicator + m_sendBtn = new SendButtonIndicator(this, this, m_mv); + m_sendBtn->setPixmap(embed::getIconPixmap("mixer_send_off", 23, 16)); + m_sendBtn->move(4,4); + + // channel number + lcdSpinBox * l = new lcdSpinBox( 2, this ); + l->model()->setRange( m_channelIndex, m_channelIndex ); + l->model()->setValue( m_channelIndex ); + l->move( 2, 58 ); + l->setMarginWidth( 1 ); +} + + +void FxLine::paintEvent( QPaintEvent * ) +{ + FxMixer * mix = engine::fxMixer(); + bool sendToThis = mix->channelSendModel( + m_mv->currentFxLine()->m_channelIndex, m_channelIndex) != NULL; + QPainter painter; + painter.begin( this ); + engine::getLmmsStyle()->drawFxLine( &painter, this, + mix->effectChannel(m_channelIndex)->m_name, + m_mv->currentFxLine() == this, sendToThis ); + painter.end(); +} + +void FxLine::mousePressEvent( QMouseEvent * ) +{ + m_mv->setCurrentFxLine( this ); +} + +void FxLine::mouseDoubleClickEvent( QMouseEvent * ) +{ + bool ok; + FxMixer * mix = engine::fxMixer(); + QString new_name = QInputDialog::getText( this, + FxMixerView::tr( "Rename FX channel" ), + FxMixerView::tr( "Enter the new name for this " + "FX channel" ), + QLineEdit::Normal, mix->effectChannel(m_channelIndex)->m_name, &ok ); + if( ok && !new_name.isEmpty() ) + { + mix->effectChannel(m_channelIndex)->m_name = new_name; + update(); + } +} + +#include "moc_FxLine.cxx" diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 3be23bcfe..cf2d5e72a 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include @@ -35,87 +36,13 @@ #include #include - #include "FxMixerView.h" -#include "fader.h" #include "knob.h" -#include "EffectRackView.h" #include "engine.h" #include "embed.h" #include "MainWindow.h" #include "lcd_spinbox.h" #include "gui_templates.h" -#include "tooltip.h" -#include "pixmap_button.h" - - -class SendIndicator : public QWidget -{ - public: - SendIndicator( QWidget * _parent ) : - QWidget( _parent ) - { - setFixedSize(23, 16); - } - -}; - -class FxLine : public QWidget -{ -public: - FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name, - int _channelIndex) : - QWidget( _parent ), - m_channelIndex( _channelIndex ), - m_mv( _mv ), - m_name( _name ) - { - setFixedSize( 32, 287 ); - setAttribute( Qt::WA_OpaquePaintEvent, true ); - setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); - } - - virtual void paintEvent( QPaintEvent * ) - { - bool sendToThis = engine::fxMixer()->channelSendModel( - m_mv->currentFxLine()->m_channelIndex, m_channelIndex) != NULL; - QPainter painter; - painter.begin( this ); - engine::getLmmsStyle()->drawFxLine( &painter, this, m_name, - m_mv->currentFxLine() == this, sendToThis ); - painter.end(); - } - - virtual void mousePressEvent( QMouseEvent * ) - { - m_mv->setCurrentFxLine( this ); - } - - virtual void mouseDoubleClickEvent( QMouseEvent * ) - { - bool ok; - QString new_name = QInputDialog::getText( this, - FxMixerView::tr( "Rename FX channel" ), - FxMixerView::tr( "Enter the new name for this " - "FX channel" ), - QLineEdit::Normal, m_name, &ok ); - if( ok && !new_name.isEmpty() ) - { - m_name = new_name; - update(); - } - } - - knob * m_sendKnob; - int m_channelIndex; -private: - FxMixerView * m_mv; - QString & m_name; - -} ; - - - FxMixerView::FxMixerView() : QWidget(), @@ -144,13 +71,16 @@ FxMixerView::FxMixerView() : ml->setSpacing( 0 ); ml->addSpacing( 6 ); - m_fxChannelViews.resize(m->numChannels()); channelArea = new QScrollArea(this); chLayout = new QHBoxLayout(channelArea); // add master channel - FxChannelView * masterView = &m_fxChannelViews[0]; - addFxLine(0, this, ml); + m_fxChannelViews.resize(m->numChannels()); + m_fxChannelViews[0] = new FxChannelView(this, this, 0); + FxChannelView * masterView = m_fxChannelViews[0]; + m_fxRacksLayout->addWidget( masterView->m_rackView ); + + ml->addWidget(masterView->m_fxLine); ml->addSpacing(5); QSize fxLineSize = masterView->m_fxLine->size(); @@ -160,7 +90,9 @@ FxMixerView::FxMixerView() : // add mixer channels for( int i = 1; i < m_fxChannelViews.size(); ++i ) { - addFxLine(i, channelArea, chLayout); + m_fxChannelViews[i] = new FxChannelView(channelArea, this, i); + chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); + m_fxRacksLayout->addWidget( m_fxChannelViews[i]->m_rackView ); } // add the scrolling section to the main layout ml->addLayout(chLayout); @@ -178,7 +110,7 @@ FxMixerView::FxMixerView() : setLayout( ml ); updateGeometry(); - setCurrentFxLine( m_fxChannelViews[0].m_fxLine ); + setCurrentFxLine( m_fxChannelViews[0]->m_fxLine ); // timer for updating faders connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), @@ -200,57 +132,6 @@ FxMixerView::FxMixerView() : setModel( m ); } - -void FxMixerView::addFxLine(int i, QWidget * parent, QLayout * layout) -{ - FxMixer * m = engine::fxMixer(); - - FxChannelView * cv = &m_fxChannelViews[i]; - - cv->m_fxLine = new FxLine( parent, this, - m->m_fxChannels[i]->m_name, i ); - layout->addWidget(cv->m_fxLine); - - // mixer sends knob - cv->m_fxLine->m_sendKnob = new knob(0, cv->m_fxLine, - tr("Channel send amount")); - cv->m_fxLine->m_sendKnob->move(0, 22); - cv->m_fxLine->m_sendKnob->setVisible(false); - - // send light indicator - - - // channel number - lcdSpinBox * l = new lcdSpinBox( 2, cv->m_fxLine ); - l->model()->setRange( i, i ); - l->model()->setValue( i ); - l->move( 2, 58 ); - l->setMarginWidth( 1 ); - - - cv->m_fader = new fader( &m->m_fxChannels[i]->m_volumeModel, - tr( "FX Fader %1" ).arg( i ), - cv->m_fxLine ); - cv->m_fader->move( 15-cv->m_fader->width()/2, - cv->m_fxLine->height()- - cv->m_fader->height()-5 ); - - cv->m_muteBtn = new pixmapButton( cv->m_fxLine, tr( "Mute" ) ); - cv->m_muteBtn->setModel( &m->m_fxChannels[i]->m_muteModel ); - cv->m_muteBtn->setActiveGraphic( - embed::getIconPixmap( "led_off" ) ); - cv->m_muteBtn->setInactiveGraphic( - embed::getIconPixmap( "led_green" ) ); - cv->m_muteBtn->setCheckable( true ); - cv->m_muteBtn->move( 9, cv->m_fader->y()-16); - toolTip::add( cv->m_muteBtn, tr( "Mute this FX channel" ) ); - - cv->m_rackView = new EffectRackView( - &m->m_fxChannels[i]->m_fxChain, this ); - m_fxRacksLayout->addWidget( cv->m_rackView ); -} - - FxMixerView::~FxMixerView() { } @@ -263,9 +144,10 @@ void FxMixerView::addNewChannel() FxMixer * mix = engine::fxMixer(); int newChannelIndex = mix->createChannel(); - m_fxChannelViews.push_back(FxChannelView()); - - addFxLine(newChannelIndex, channelArea, chLayout); + m_fxChannelViews.push_back(new FxChannelView(channelArea, this, + newChannelIndex)); + chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); + m_fxRacksLayout->addWidget( m_fxChannelViews[newChannelIndex]->m_rackView ); } @@ -284,42 +166,73 @@ void FxMixerView::loadSettings( const QDomElement & _this ) } +FxMixerView::FxChannelView::FxChannelView(QWidget * _parent, FxMixerView * _mv, + int _chIndex ) +{ + m_fxLine = new FxLine(_parent, _mv, _chIndex); + + FxMixer * m = engine::fxMixer(); + m_fader = new fader( &m->effectChannel(_chIndex)->m_volumeModel, + tr( "FX Fader %1" ).arg( _chIndex ), m_fxLine ); + m_fader->move( 15-m_fader->width()/2, + m_fxLine->height()- + m_fader->height()-5 ); + + m_muteBtn = new pixmapButton( m_fxLine, tr( "Mute" ) ); + m_muteBtn->setModel( &m->effectChannel(_chIndex)->m_muteModel ); + m_muteBtn->setActiveGraphic( + embed::getIconPixmap( "led_off" ) ); + m_muteBtn->setInactiveGraphic( + embed::getIconPixmap( "led_green" ) ); + m_muteBtn->setCheckable( true ); + m_muteBtn->move( 9, m_fader->y()-16); + toolTip::add( m_muteBtn, tr( "Mute this FX channel" ) ); + + m_rackView = new EffectRackView( + &m->m_fxChannels[_chIndex]->m_fxChain, _mv ); +} void FxMixerView::setCurrentFxLine( FxLine * _line ) { - FxMixer * mix = engine::fxMixer(); - // select m_currentFxLine = _line; - m_fxRacksLayout->setCurrentIndex( _line->m_channelIndex ); + m_fxRacksLayout->setCurrentIndex( _line->channelIndex() ); // set up send knob for(int i = 0; i < m_fxChannelViews.size(); ++i) { - // does current channel send to this channel? - FloatModel * sendModel = mix->channelSendModel(_line->m_channelIndex, i); - if( sendModel == NULL ) - { - // does not send, hide send knob - m_fxChannelViews[i].m_fxLine->m_sendKnob->setVisible(false); - } - else - { - // it does send, show knob and connect - m_fxChannelViews[i].m_fxLine->m_sendKnob->setVisible(true); - m_fxChannelViews[i].m_fxLine->m_sendKnob->setModel(sendModel); - } - - m_fxChannelViews[i].m_fxLine->update(); + updateFxLine(i); } } +void FxMixerView::updateFxLine(int i) +{ + FxMixer * mix = engine::fxMixer(); + + // does current channel send to this channel? + FloatModel * sendModel = mix->channelSendModel(m_currentFxLine->channelIndex(), i); + if( sendModel == NULL ) + { + // does not send, hide send knob + m_fxChannelViews[i]->m_fxLine->m_sendKnob->setVisible(false); + } + else + { + // it does send, show knob and connect + m_fxChannelViews[i]->m_fxLine->m_sendKnob->setVisible(true); + m_fxChannelViews[i]->m_fxLine->m_sendKnob->setModel(sendModel); + } + + + m_fxChannelViews[i]->m_fxLine->update(); + m_fxChannelViews[i]->m_fxLine->m_sendBtn->updateLightStatus(); +} void FxMixerView::setCurrentFxLine( int _line ) { - setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); + setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); } @@ -329,7 +242,7 @@ void FxMixerView::clear() { for( int i = 0; i < m_fxChannelViews.size(); ++i ) { - m_fxChannelViews[i].m_rackView->clearViews(); + m_fxChannelViews[i]->m_rackView->clearViews(); } } @@ -341,26 +254,26 @@ void FxMixerView::updateFaders() FxMixer * m = engine::fxMixer(); for( int i = 0; i < m_fxChannelViews.size(); ++i ) { - const float opl = m_fxChannelViews[i].m_fader->getPeak_L(); - const float opr = m_fxChannelViews[i].m_fader->getPeak_R(); + const float opl = m_fxChannelViews[i]->m_fader->getPeak_L(); + const float opr = m_fxChannelViews[i]->m_fader->getPeak_R(); const float fall_off = 1.2; if( m->m_fxChannels[i]->m_peakLeft > opl ) { - m_fxChannelViews[i].m_fader->setPeak_L( + m_fxChannelViews[i]->m_fader->setPeak_L( m->m_fxChannels[i]->m_peakLeft ); } else { - m_fxChannelViews[i].m_fader->setPeak_L( opl/fall_off ); + m_fxChannelViews[i]->m_fader->setPeak_L( opl/fall_off ); } if( m->m_fxChannels[i]->m_peakRight > opr ) { - m_fxChannelViews[i].m_fader->setPeak_R( + m_fxChannelViews[i]->m_fader->setPeak_R( m->m_fxChannels[i]->m_peakRight ); } else { - m_fxChannelViews[i].m_fader->setPeak_R( opr/fall_off ); + m_fxChannelViews[i]->m_fader->setPeak_R( opr/fall_off ); } } } diff --git a/src/gui/SendButtonIndicator.cpp b/src/gui/SendButtonIndicator.cpp new file mode 100644 index 000000000..a932f136a --- /dev/null +++ b/src/gui/SendButtonIndicator.cpp @@ -0,0 +1,53 @@ +#include "SendButtonIndicator.h" + +#include "engine.h" +#include "FxMixer.h" +#include "Model.h" + +SendButtonIndicator:: SendButtonIndicator( QWidget * _parent, FxLine * _owner, + FxMixerView * _mv) : + QLabel( _parent ), + m_parent( _owner ), + m_mv( _mv ) +{ + qpmOff = embed::getIconPixmap("mixer_send_off", 23, 16); + qpmOn = embed::getIconPixmap("mixer_send_on", 23, 16); + + // don't do any initializing yet, because the FxMixerView and FxLine + // that were passed to this constructor are not done with their constructors + // yet. + +} + +void SendButtonIndicator::mousePressEvent( QMouseEvent * e ) +{ + FxMixer * mix = engine::fxMixer(); + int from = m_mv->currentFxLine()->channelIndex(); + int to = m_parent->channelIndex(); + FloatModel * sendModel = mix->channelSendModel(from, to); + if( sendModel == NULL ) + { + // not sending. create a mixer send. + mix->createChannelSend( from, to ); + } + else + { + // sending. delete the mixer send. + mix->deleteChannelSend( from, to ); + } + + m_mv->updateFxLine(m_parent->channelIndex()); + updateLightStatus(); +} + +FloatModel * SendButtonIndicator::getSendModel() +{ + FxMixer * mix = engine::fxMixer(); + return mix->channelSendModel( + m_mv->currentFxLine()->channelIndex(), m_parent->channelIndex()); +} + +void SendButtonIndicator::updateLightStatus() +{ + setPixmap( getSendModel() == NULL ? qpmOff : qpmOn ); +} From ce7891b7bd62ade0843eef924307626a82a2de02 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Sep 2009 20:26:47 -0700 Subject: [PATCH 04/34] Fix mixer sends rendering in the backend Fixed: Buffers was cleared too early resulting in some combinations of sends not working. --- src/core/FxMixer.cpp | 2 -- src/core/mixer.cpp | 13 +++++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index b2dd852e2..03aa9b2da 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -215,8 +215,6 @@ void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) _buf[f][0] += ch_buf[f][0] * v * amt; _buf[f][1] += ch_buf[f][1] * v * amt; } - engine::getMixer()->clearAudioBuffer( ch_buf, - engine::getMixer()->framesPerPeriod() ); } diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index 819ef0be2..b33e4c49f 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -503,6 +503,8 @@ sampleFrameA * mixer::renderNextBuffer() MicroTimer timer; static song::playPos last_metro_pos = -1; + FxMixer * fxm = engine::fxMixer(); + song::playPos p = engine::getSong()->getPlayPos( song::Mode_PlayPattern ); if( engine::getSong()->playMode() == song::Mode_PlayPattern && @@ -556,7 +558,7 @@ sampleFrameA * mixer::renderNextBuffer() clearAudioBuffer( m_writeBuf, m_framesPerPeriod ); // prepare master mix (clear internal buffers etc.) - engine::fxMixer()->prepareMasterMix(); + fxm->prepareMasterMix(); // create play-handles for new notes, samples etc. engine::getSong()->processNextBuffer(); @@ -606,10 +608,17 @@ sampleFrameA * mixer::renderNextBuffer() // STAGE 4: do master mix in FX mixer - engine::fxMixer()->masterMix( m_writeBuf ); + fxm->masterMix( m_writeBuf ); WAIT_FOR_JOBS(); + // clear all channel buffers + for( int i = 0; i < fxm->numChannels(); ++i) + { + engine::getMixer()->clearAudioBuffer( fxm->effectChannel(i)->m_buffer, + engine::getMixer()->framesPerPeriod() ); + } + unlock(); From 504a03f2cf1a1a5f571dd5cdad361073210d9fb1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Sep 2009 19:16:19 -0700 Subject: [PATCH 05/34] prevent infinite mixing loops Prevent infinite mixing loops by disabling the send button for a channel line when clicking it would cause an infinite loop. --- include/FxMixer.h | 4 ++++ src/core/FxMixer.cpp | 20 ++++++++++++++++++++ src/gui/FxMixerView.cpp | 23 +++++++++++++++-------- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/include/FxMixer.h b/include/FxMixer.h index 116dbcde0..b53045015 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -97,6 +97,10 @@ public: // delete the connection made by createChannelSend void deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel); + // determine if adding a send from sendFrom to + // sendTo would result in an infinite mixer loop. + bool isInfiniteLoop(fx_ch_t fromChannel, fx_ch_t toChannel); + // return the FloatModel of fromChannel sending its output to the input of // toChannel. NULL if there is no send. FloatModel * channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel); diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 03aa9b2da..8306b28c4 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -158,6 +158,26 @@ void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) } +bool FxMixer::isInfiniteLoop(fx_ch_t sendFrom, fx_ch_t sendTo) { + // can't send master to anything + if( sendFrom == 0 ) return true; + + // can't send channel to itself + if( sendFrom == sendTo ) return true; + + // follow sendTo's outputs recursively looking for something that sends + // to sendFrom + for(int i=0; im_sends.size(); ++i) + { + if( isInfiniteLoop( sendFrom, m_fxChannels[sendTo]->m_sends[i] ) ) + { + return true; + } + } + + return false; +} + // how much does fromChannel send its output to the input of toChannel? FloatModel * FxMixer::channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index cf2d5e72a..f1d591fc8 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -148,6 +148,8 @@ void FxMixerView::addNewChannel() newChannelIndex)); chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); m_fxRacksLayout->addWidget( m_fxChannelViews[newChannelIndex]->m_rackView ); + + updateFxLine(newChannelIndex); } @@ -207,29 +209,34 @@ void FxMixerView::setCurrentFxLine( FxLine * _line ) } -void FxMixerView::updateFxLine(int i) +void FxMixerView::updateFxLine(int index) { FxMixer * mix = engine::fxMixer(); // does current channel send to this channel? - FloatModel * sendModel = mix->channelSendModel(m_currentFxLine->channelIndex(), i); + int selIndex = m_currentFxLine->channelIndex(); + FxLine * thisLine = m_fxChannelViews[index]->m_fxLine; + FloatModel * sendModel = mix->channelSendModel(selIndex, index); if( sendModel == NULL ) { // does not send, hide send knob - m_fxChannelViews[i]->m_fxLine->m_sendKnob->setVisible(false); + thisLine->m_sendKnob->setVisible(false); } else { // it does send, show knob and connect - m_fxChannelViews[i]->m_fxLine->m_sendKnob->setVisible(true); - m_fxChannelViews[i]->m_fxLine->m_sendKnob->setModel(sendModel); + thisLine->m_sendKnob->setVisible(true); + thisLine->m_sendKnob->setModel(sendModel); } - - m_fxChannelViews[i]->m_fxLine->update(); - m_fxChannelViews[i]->m_fxLine->m_sendBtn->updateLightStatus(); + // disable the send button if it would cause an infinite loop + thisLine->m_sendBtn->setVisible(! mix->isInfiniteLoop(selIndex, index)); + thisLine->m_sendBtn->updateLightStatus(); + thisLine->update(); } + + void FxMixerView::setCurrentFxLine( int _line ) { setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); From d68d53b83a8158a3c46360dfbf0c2102504a5782 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Sep 2009 20:54:30 -0700 Subject: [PATCH 06/34] Scrollbar for the fx mixer channels Still need to fix up the rest of the fx mixer --- include/FxMixerView.h | 1 + src/gui/FxMixerView.cpp | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/include/FxMixerView.h b/include/FxMixerView.h index 461379728..f752672c9 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -97,6 +97,7 @@ private: QScrollArea * channelArea; QHBoxLayout * chLayout; + QWidget * m_channelAreaWidget; } ; #endif diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index f1d591fc8..f41c9bba4 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -56,7 +56,9 @@ FxMixerView::FxMixerView() : //pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) ); //setPalette( pal ); setAutoFillBackground( true ); - setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum ); + setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); + setFixedSize(600, 300); + setWindowTitle( tr( "FX-Mixer" ) ); setWindowIcon( embed::getIconPixmap( "fx_mixer" ) ); @@ -64,6 +66,7 @@ FxMixerView::FxMixerView() : m_fxRacksLayout = new QStackedLayout; m_fxRacksLayout->setSpacing( 0 ); m_fxRacksLayout->setMargin( 0 ); + //m_fxRacksLayout->setAlignment(Qt::AlignRight); // main-layout QHBoxLayout * ml = new QHBoxLayout; @@ -71,8 +74,8 @@ FxMixerView::FxMixerView() : ml->setSpacing( 0 ); ml->addSpacing( 6 ); - channelArea = new QScrollArea(this); - chLayout = new QHBoxLayout(channelArea); + m_channelAreaWidget = new QWidget; + chLayout = new QHBoxLayout(m_channelAreaWidget); // add master channel m_fxChannelViews.resize(m->numChannels()); @@ -85,17 +88,20 @@ FxMixerView::FxMixerView() : QSize fxLineSize = masterView->m_fxLine->size(); chLayout->setSizeConstraint(QLayout::SetMinimumSize); - channelArea->setWidgetResizable(true); // add mixer channels for( int i = 1; i < m_fxChannelViews.size(); ++i ) { - m_fxChannelViews[i] = new FxChannelView(channelArea, this, i); + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); m_fxRacksLayout->addWidget( m_fxChannelViews[i]->m_rackView ); } // add the scrolling section to the main layout - ml->addLayout(chLayout); + m_channelAreaWidget->setLayout(chLayout); + channelArea = new QScrollArea(this); + channelArea->setWidget(m_channelAreaWidget); + //channelArea-> get rid of padding + ml->addWidget(channelArea); // show the add new effect channel button QPushButton * newChannelBtn = new QPushButton("new", this ); @@ -123,7 +129,7 @@ FxMixerView::FxMixerView() : Qt::WindowFlags flags = subWin->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); - subWin->layout()->setSizeConstraint(QLayout::SetMinimumSize); + //subWin->layout()->setSizeConstraint(QLayout::SetMinimumSize); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); @@ -144,7 +150,7 @@ void FxMixerView::addNewChannel() FxMixer * mix = engine::fxMixer(); int newChannelIndex = mix->createChannel(); - m_fxChannelViews.push_back(new FxChannelView(channelArea, this, + m_fxChannelViews.push_back(new FxChannelView(m_channelAreaWidget, this, newChannelIndex)); chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); m_fxRacksLayout->addWidget( m_fxChannelViews[newChannelIndex]->m_rackView ); From db6164ca91f671cba81e3c603a99db1176a44db6 Mon Sep 17 00:00:00 2001 From: Paul Giblock Date: Wed, 30 Sep 2009 03:12:25 -0400 Subject: [PATCH 07/34] Requested improvements to new FxMixerView$ * lock fx mixer height Done. channelArea->setFixedHeight and proper sizeConstraints * width: pick a good min. size. keep max. size off. Done. Set to 6 fx-lines. * effects chain should align to the right Done. * get rid of padding in mixer Done. setSpacing and setMargin on chLayout * scroll area so that vert scrollbar is never seen. Done. setVerticalScrollBarPolicy and proper height calculation * Get rid of scroll bar area border Done. FrameStyle. The biggest change, however, was removing the multiple EffectRackViews that were being used. Now just a single EffectRackView exists and it is shared by all models. --- include/FxMixerView.h | 5 +-- src/gui/FxMixerView.cpp | 62 +++++++++++++----------------- src/gui/widgets/EffectRackView.cpp | 2 +- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/include/FxMixerView.h b/include/FxMixerView.h index f752672c9..5c4c2e0c8 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -39,7 +39,6 @@ #include "embed.h" #include "EffectRackView.h" -class QStackedLayout; class QButtonGroup; class FxLine; @@ -53,7 +52,7 @@ public: FxChannelView(QWidget * _parent, FxMixerView * _mv, int _chIndex ); FxLine * m_fxLine; - EffectRackView * m_rackView; + //EffectRackView * m_rackView; pixmapButton * m_muteBtn; fader * m_fader; } ; @@ -92,12 +91,12 @@ private: QVector m_fxChannelViews; - QStackedLayout * m_fxRacksLayout; FxLine * m_currentFxLine; QScrollArea * channelArea; QHBoxLayout * chLayout; QWidget * m_channelAreaWidget; + EffectRackView * m_rackView; } ; #endif diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index f41c9bba4..63a23c891 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "FxMixerView.h" #include "knob.h" @@ -52,55 +53,51 @@ FxMixerView::FxMixerView() : FxMixer * m = engine::fxMixer(); m->setHook( this ); - QPalette pal = palette(); + //QPalette pal = palette(); //pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) ); //setPalette( pal ); setAutoFillBackground( true ); - setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); - setFixedSize(600, 300); - setWindowTitle( tr( "FX-Mixer" ) ); setWindowIcon( embed::getIconPixmap( "fx_mixer" ) ); - m_fxRacksLayout = new QStackedLayout; - m_fxRacksLayout->setSpacing( 0 ); - m_fxRacksLayout->setMargin( 0 ); - //m_fxRacksLayout->setAlignment(Qt::AlignRight); - // main-layout QHBoxLayout * ml = new QHBoxLayout; - ml->setMargin( 0 ); - ml->setSpacing( 0 ); - ml->addSpacing( 6 ); + //ml->setMargin( 0 ); + //ml->setSpacing( 0 ); + //ml->addSpacing( 6 ); + // Channel area m_channelAreaWidget = new QWidget; chLayout = new QHBoxLayout(m_channelAreaWidget); + chLayout->setSizeConstraint(QLayout::SetMinimumSize); + chLayout->setSpacing( 0 ); + chLayout->setMargin( 0 ); + m_channelAreaWidget->setLayout(chLayout); // add master channel m_fxChannelViews.resize(m->numChannels()); m_fxChannelViews[0] = new FxChannelView(this, this, 0); + FxChannelView * masterView = m_fxChannelViews[0]; - m_fxRacksLayout->addWidget( masterView->m_rackView ); + ml->addWidget( masterView->m_fxLine, 0, Qt::AlignTop ); - ml->addWidget(masterView->m_fxLine); - ml->addSpacing(5); QSize fxLineSize = masterView->m_fxLine->size(); - chLayout->setSizeConstraint(QLayout::SetMinimumSize); - // add mixer channels for( int i = 1; i < m_fxChannelViews.size(); ++i ) { m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); - m_fxRacksLayout->addWidget( m_fxChannelViews[i]->m_rackView ); } // add the scrolling section to the main layout - m_channelAreaWidget->setLayout(chLayout); channelArea = new QScrollArea(this); channelArea->setWidget(m_channelAreaWidget); - //channelArea-> get rid of padding + channelArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + channelArea->setFrameStyle( QFrame::NoFrame ); + channelArea->setMinimumWidth( fxLineSize.width() * 6 ); + channelArea->setFixedHeight( fxLineSize.height() + + style()->pixelMetric( QStyle::PM_ScrollBarExtent ) ); ml->addWidget(channelArea); // show the add new effect channel button @@ -108,16 +105,17 @@ FxMixerView::FxMixerView() : newChannelBtn->setFont(QFont("sans-serif", 10, 1, false)); newChannelBtn->setFixedSize(fxLineSize); connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); - ml->addWidget( newChannelBtn ); - - ml->addLayout( m_fxRacksLayout ); + ml->addWidget( newChannelBtn, 0, Qt::AlignTop ); + + // Create EffectRack and set initial index to master channel + m_rackView = new EffectRackView( &m->m_fxChannels[0]->m_fxChain, this ); + ml->addWidget( m_rackView, 0, Qt::AlignTop ); + setCurrentFxLine( m_fxChannelViews[0]->m_fxLine ); setLayout( ml ); updateGeometry(); - setCurrentFxLine( m_fxChannelViews[0]->m_fxLine ); - // timer for updating faders connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( updateFaders() ) ); @@ -129,7 +127,8 @@ FxMixerView::FxMixerView() : Qt::WindowFlags flags = subWin->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); - //subWin->layout()->setSizeConstraint(QLayout::SetMinimumSize); + layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); + subWin->layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); @@ -153,7 +152,6 @@ void FxMixerView::addNewChannel() m_fxChannelViews.push_back(new FxChannelView(m_channelAreaWidget, this, newChannelIndex)); chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); - m_fxRacksLayout->addWidget( m_fxChannelViews[newChannelIndex]->m_rackView ); updateFxLine(newChannelIndex); } @@ -195,9 +193,6 @@ FxMixerView::FxChannelView::FxChannelView(QWidget * _parent, FxMixerView * _mv, m_muteBtn->setCheckable( true ); m_muteBtn->move( 9, m_fader->y()-16); toolTip::add( m_muteBtn, tr( "Mute this FX channel" ) ); - - m_rackView = new EffectRackView( - &m->m_fxChannels[_chIndex]->m_fxChain, _mv ); } @@ -205,7 +200,7 @@ void FxMixerView::setCurrentFxLine( FxLine * _line ) { // select m_currentFxLine = _line; - m_fxRacksLayout->setCurrentIndex( _line->channelIndex() ); + m_rackView->setModel( &engine::fxMixer()->m_fxChannels[_line->channelIndex()]->m_fxChain ); // set up send knob for(int i = 0; i < m_fxChannelViews.size(); ++i) @@ -253,10 +248,7 @@ void FxMixerView::setCurrentFxLine( int _line ) void FxMixerView::clear() { - for( int i = 0; i < m_fxChannelViews.size(); ++i ) - { - m_fxChannelViews[i]->m_rackView->clearViews(); - } + m_rackView->clearViews(); } diff --git a/src/gui/widgets/EffectRackView.cpp b/src/gui/widgets/EffectRackView.cpp index f78c3d7e1..bd59656b6 100644 --- a/src/gui/widgets/EffectRackView.cpp +++ b/src/gui/widgets/EffectRackView.cpp @@ -43,7 +43,7 @@ EffectRackView::EffectRackView( EffectChain * _model, QWidget * _parent ) : m_mainLayout = new QVBoxLayout( this ); m_mainLayout->setSpacing( 0 ); - m_mainLayout->setMargin( 5 ); + m_mainLayout->setMargin( 0 ); m_effectsGroupBox = new groupBox( tr( "EFFECTS CHAIN" ) ); m_mainLayout->addWidget( m_effectsGroupBox ); From e09c12687a87165a8e70364c6c7f6d62e4d211d7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 30 Sep 2009 03:45:08 -0700 Subject: [PATCH 08/34] Ability to delete mixer channels in the FX Mixer Users can now delete mixer channels from the FX mixer, and it doesn't mess up the instrument send channels. --- include/FxLine.h | 2 +- include/FxMixer.h | 7 +++ include/FxMixerView.h | 10 +++-- src/core/FxMixer.cpp | 95 +++++++++++++++++++++++++++++++++++++++++ src/gui/FxMixerView.cpp | 55 ++++++++++++++++++++++-- 5 files changed, 161 insertions(+), 8 deletions(-) diff --git a/include/FxLine.h b/include/FxLine.h index 1700ebba3..70def8637 100644 --- a/include/FxLine.h +++ b/include/FxLine.h @@ -21,11 +21,11 @@ public: virtual void mouseDoubleClickEvent( QMouseEvent * ); inline int channelIndex() { return m_channelIndex; } + inline void setChannelIndex(int index) { m_channelIndex = index; } knob * m_sendKnob; SendButtonIndicator * m_sendBtn; - private: FxMixerView * m_mv; diff --git a/include/FxMixer.h b/include/FxMixer.h index b53045015..19caa1479 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -109,6 +109,13 @@ public: // returns the index of the channel that was just added int createChannel(); + // delete a channel from the FX mixer. + void deleteChannel(int index); + + // re-arrange channels + void moveChannelLeft(int index); + void moveChannelRight(int index); + // reset a channel's name, fx, sends, etc void clearChannel(fx_ch_t channelIndex); diff --git a/include/FxMixerView.h b/include/FxMixerView.h index 5c4c2e0c8..05c5874bf 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -52,15 +52,16 @@ public: FxChannelView(QWidget * _parent, FxMixerView * _mv, int _chIndex ); FxLine * m_fxLine; - //EffectRackView * m_rackView; pixmapButton * m_muteBtn; fader * m_fader; - } ; + }; FxMixerView(); virtual ~FxMixerView(); + virtual void keyPressEvent(QKeyEvent * e); + virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); virtual void loadSettings( const QDomElement & _this ); @@ -81,7 +82,10 @@ public: // display the send button and knob correctly - void updateFxLine(int i); + void updateFxLine(int index); + + // notify the view that an fx channel was deleted + void deleteChannel(int index); private slots: void updateFaders(); diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 8306b28c4..68fba6037 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -30,6 +30,9 @@ #include "Effect.h" #include "song.h" +#include "InstrumentTrack.h" +#include "bb_track_container.h" + FxChannel::FxChannel( Model * _parent ) : m_fxChain( NULL ), @@ -97,6 +100,98 @@ int FxMixer::createChannel() } +void FxMixer::deleteChannel(int index) +{ + // go through every instrument and adjust for the channel index change + QVector songTrackList = engine::getSong()->tracks(); + QVector bbTrackList = engine::getBBTrackContainer()->tracks(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) + { + QVector trackList = trackLists[tl]; + for(int i=0; itype() == track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; + int val = inst->effectChannelModel()->value(0); + if( val == index ) + { + // we are deleting this track's fx send + // send to master + inst->effectChannelModel()->setValue(0); + } + else if( val > index ) + { + // subtract 1 to make up for the missing channel + inst->effectChannelModel()->setValue(val-1); + } + + } + } + } + + // delete all of this channel's sends and receives + for(int i=0; im_sends.size(); ++i) + { + deleteChannelSend(index, m_fxChannels[index]->m_sends[i]); + } + for(int i=0; im_receives.size(); ++i) + { + deleteChannelSend(m_fxChannels[index]->m_receives[i], index); + } + + for(int i=0; im_sends.size(); ++j) + { + if( m_fxChannels[i]->m_sends[j] > index ) + { + // subtract 1 to make up for the missing channel + --m_fxChannels[i]->m_sends[j]; + } + } + for(int j=0; jm_receives.size(); ++j) + { + if( m_fxChannels[i]->m_receives[j] > index ) + { + // subtract 1 to make up for the missing channel + --m_fxChannels[i]->m_receives[j]; + } + } + + } + + // actually delete the channel + m_fxChannels.remove(index); +} + + + +void FxMixer::moveChannelLeft(int index) +{ + // can't move master or first channel + if( index <= 1 ) + { + return; + } + + // channels to swap + int a = index - 1, b = index; + + // go through every instrument and adjust for the channel index change +} + + + +void FxMixer::moveChannelRight(int index) +{ + moveChannelLeft(index+1); +} + + void FxMixer::createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel, float amount) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 63a23c891..e3fa74212 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -63,9 +64,6 @@ FxMixerView::FxMixerView() : // main-layout QHBoxLayout * ml = new QHBoxLayout; - //ml->setMargin( 0 ); - //ml->setSpacing( 0 ); - //ml->addSpacing( 6 ); // Channel area m_channelAreaWidget = new QWidget; @@ -237,6 +235,56 @@ void FxMixerView::updateFxLine(int index) } +void FxMixerView::deleteChannel(int index) +{ + // remember selected line + int selLine = m_currentFxLine->channelIndex(); + + // can't delete master + if( index == 0 ) + return; + + // delete the real channel + engine::fxMixer()->deleteChannel(index); + + // delete the view + chLayout->removeWidget(m_fxChannelViews[index]->m_fxLine); + delete m_fxChannelViews[index]->m_fader; + delete m_fxChannelViews[index]->m_muteBtn; + delete m_fxChannelViews[index]->m_fxLine; + delete m_fxChannelViews[index]; + + // make sure every channel knows what index it is + for(int i=0; i index ) + { + m_fxChannelViews[i]->m_fxLine->setChannelIndex(i-1); + } + } + m_fxChannelViews.remove(index); + + // select the next channel + if( selLine >= m_fxChannelViews.size() ) + { + selLine = m_fxChannelViews.size()-1; + } + setCurrentFxLine(selLine); + +} + + +void FxMixerView::keyPressEvent(QKeyEvent * e) +{ + switch(e->key()) + { + case Qt::Key_Delete: + deleteChannel(m_currentFxLine->channelIndex()); + break; + } +} + + void FxMixerView::setCurrentFxLine( int _line ) { @@ -245,7 +293,6 @@ void FxMixerView::setCurrentFxLine( int _line ) - void FxMixerView::clear() { m_rackView->clearViews(); From 33753495bd064f7453a2eb4cb50af5c5be686ac9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 30 Sep 2009 06:53:23 -0700 Subject: [PATCH 09/34] Ability to re-order mixer channels In the Fx Mixer View, you can select a channel and press Alt+Left and Alt+Right respectively to re-order Fx Channels. This should be made more easily available in the GUI eventually. --- include/FxMixerView.h | 4 ++ src/core/FxMixer.cpp | 59 +++++++++++++++++++++++++++- src/gui/FxMixerView.cpp | 86 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 143 insertions(+), 6 deletions(-) diff --git a/include/FxMixerView.h b/include/FxMixerView.h index 05c5874bf..736c70615 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -87,6 +87,10 @@ public: // notify the view that an fx channel was deleted void deleteChannel(int index); + // move the channel to the left or right + void moveChannelLeft(int index); + void moveChannelRight(int index); + private slots: void updateFaders(); void addNewChannel(); diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 68fba6037..488fb09d9 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -173,7 +173,7 @@ void FxMixer::deleteChannel(int index) void FxMixer::moveChannelLeft(int index) { // can't move master or first channel - if( index <= 1 ) + if( index <= 1 || index >= m_fxChannels.size() ) { return; } @@ -182,6 +182,63 @@ void FxMixer::moveChannelLeft(int index) int a = index - 1, b = index; // go through every instrument and adjust for the channel index change + QVector songTrackList = engine::getSong()->tracks(); + QVector bbTrackList = engine::getBBTrackContainer()->tracks(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) + { + QVector trackList = trackLists[tl]; + for(int i=0; itype() == track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; + int val = inst->effectChannelModel()->value(0); + if( val == a ) + { + inst->effectChannelModel()->setValue(b); + } + else if( val == b ) + { + inst->effectChannelModel()->setValue(a); + } + + } + } + } + + for(int i=0; im_sends.size(); ++j) + { + if( m_fxChannels[i]->m_sends[j] == a ) + { + m_fxChannels[i]->m_sends[j] = b; + } + else if( m_fxChannels[i]->m_sends[j] == b ) + { + m_fxChannels[i]->m_sends[j] = a; + } + } + for(int j=0; jm_receives.size(); ++j) + { + if( m_fxChannels[i]->m_receives[j] == a ) + { + m_fxChannels[i]->m_receives[j] = b; + } + else if( m_fxChannels[i]->m_receives[j] == b ) + { + m_fxChannels[i]->m_receives[j] = a; + } + } + } + + // actually do the swap + FxChannel * tmpChannel = m_fxChannels[a]; + m_fxChannels[a] = m_fxChannels[b]; + m_fxChannels[b] = tmpChannel; } diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index e3fa74212..70745ef8c 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -89,7 +89,22 @@ FxMixerView::FxMixerView() : chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); } // add the scrolling section to the main layout - channelArea = new QScrollArea(this); + + // class for scroll area to pass key presses down + class ChannelArea : public QScrollArea + { + public: + ChannelArea(QWidget * parent, FxMixerView * mv) : + QScrollArea(parent), m_mv(mv) {} + ~ChannelArea() {} + virtual void keyPressEvent(QKeyEvent * e) + { + m_mv->keyPressEvent(e); + } + private: + FxMixerView * m_mv; + }; + channelArea = new ChannelArea(this, this); channelArea->setWidget(m_channelAreaWidget); channelArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); channelArea->setFrameStyle( QFrame::NoFrame ); @@ -237,13 +252,12 @@ void FxMixerView::updateFxLine(int index) void FxMixerView::deleteChannel(int index) { + // can't delete master + if( index == 0 ) return; + // remember selected line int selLine = m_currentFxLine->channelIndex(); - // can't delete master - if( index == 0 ) - return; - // delete the real channel engine::fxMixer()->deleteChannel(index); @@ -253,6 +267,7 @@ void FxMixerView::deleteChannel(int index) delete m_fxChannelViews[index]->m_muteBtn; delete m_fxChannelViews[index]->m_fxLine; delete m_fxChannelViews[index]; + m_channelAreaWidget->adjustSize(); // make sure every channel knows what index it is for(int i=0; i= m_fxChannelViews.size() ) return; + + int selIndex = m_currentFxLine->channelIndex(); + + FxMixer * mix = engine::fxMixer(); + mix->moveChannelLeft(index); + + // refresh the two mixer views + for( int i = index-1; i <= index; ++i ) + { + // delete the mixer view + int replaceIndex = chLayout->indexOf(m_fxChannelViews[i]->m_fxLine); + + chLayout->removeWidget(m_fxChannelViews[i]->m_fxLine); + delete m_fxChannelViews[i]->m_fader; + delete m_fxChannelViews[i]->m_muteBtn; + delete m_fxChannelViews[i]->m_fxLine; + delete m_fxChannelViews[i]; + + // add it again + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); + chLayout->insertWidget(replaceIndex, m_fxChannelViews[i]->m_fxLine); + } + + // keep selected channel + if( selIndex == index ) + { + selIndex = index-1; + } + else if( selIndex == index - 1 ) + { + selIndex = index; + } + setCurrentFxLine(selIndex); +} + + + +void FxMixerView::moveChannelRight(int index) +{ + moveChannelLeft(index+1); +} + + + void FxMixerView::keyPressEvent(QKeyEvent * e) { switch(e->key()) @@ -281,6 +345,18 @@ void FxMixerView::keyPressEvent(QKeyEvent * e) case Qt::Key_Delete: deleteChannel(m_currentFxLine->channelIndex()); break; + case Qt::Key_Left: + if( e->modifiers() & Qt::AltModifier ) + { + moveChannelLeft( m_currentFxLine->channelIndex() ); + } + break; + case Qt::Key_Right: + if( e->modifiers() & Qt::AltModifier ) + { + moveChannelRight( m_currentFxLine->channelIndex() ); + } + break; } } From dd28a654b548a8b27c4875c25e72341a5a64fb50 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 30 Sep 2009 17:04:28 -0700 Subject: [PATCH 10/34] Channel selector has a max range of num channels When you add and remove channels, the range of the L.E.D. channel selector is correct. --- include/AutomatableModel.h | 19 +++++++++++++++---- include/FxMixerView.h | 2 ++ include/SendButtonIndicator.h | 4 ++-- src/core/AutomatableModel.cpp | 4 ++-- src/gui/FxLine.cpp | 6 +++--- src/gui/FxMixerView.cpp | 32 ++++++++++++++++++++++++++++++-- 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index e3c4a9859..6bd68187a 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -225,6 +225,9 @@ protected: float fittedValue( float _value ) const; + float m_minValue; + float m_maxValue; + float m_value; private: void linkModel( AutomatableModel * _model ); @@ -232,10 +235,7 @@ private: DataType m_dataType; - float m_value; float m_initValue; - float m_minValue; - float m_maxValue; float m_step; float m_range; @@ -281,7 +281,18 @@ signals: { \ return AutomatableModel::maxValue(); \ } \ - + \ + inline void setMinValue(type val) \ + { \ + m_minValue = val; \ + if( m_value < m_minValue ) m_value = m_minValue; \ + } \ + \ + inline void setMaxValue(type val) \ + { \ + m_maxValue = val; \ + if( m_value > m_maxValue ) m_value = m_maxValue; \ + } // some typed AutomatableModel-definitions diff --git a/include/FxMixerView.h b/include/FxMixerView.h index 736c70615..cff856659 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -105,6 +105,8 @@ private: QHBoxLayout * chLayout; QWidget * m_channelAreaWidget; EffectRackView * m_rackView; + + void updateMaxChannelSelector(); } ; #endif diff --git a/include/SendButtonIndicator.h b/include/SendButtonIndicator.h index f2c7ef44d..2b0819791 100644 --- a/include/SendButtonIndicator.h +++ b/include/SendButtonIndicator.h @@ -1,9 +1,9 @@ #ifndef SENDBUTTONINDICATOR_H #define SENDBUTTONINDICATOR_H -#include #include -#include +#include +#include #include "FxLine.h" #include "FxMixerView.h" diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 2864f266c..7762edcb8 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -44,11 +44,11 @@ AutomatableModel::AutomatableModel( DataType _type, const QString & _display_name, bool _default_constructed ) : Model( _parent, _display_name, _default_constructed ), + m_minValue( _min ), + m_maxValue( _max ), m_dataType( _type ), m_value( _val ), m_initValue( _val ), - m_minValue( _min ), - m_maxValue( _max ), m_step( _step ), m_range( _max - _min ), m_journalEntryReady( false ), diff --git a/src/gui/FxLine.cpp b/src/gui/FxLine.cpp index fe92ba66b..5d57a6b0f 100644 --- a/src/gui/FxLine.cpp +++ b/src/gui/FxLine.cpp @@ -1,9 +1,9 @@ #include "FxLine.h" #include -#include -#include -#include +#include +#include +#include #include "FxMixer.h" #include "FxMixerView.h" diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 70745ef8c..70832c71b 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -37,6 +36,7 @@ #include #include #include +#include #include "FxMixerView.h" #include "knob.h" @@ -45,6 +45,9 @@ #include "MainWindow.h" #include "lcd_spinbox.h" #include "gui_templates.h" +#include "InstrumentTrack.h" +#include "song.h" +#include "bb_track_container.h" FxMixerView::FxMixerView() : QWidget(), @@ -90,7 +93,7 @@ FxMixerView::FxMixerView() : } // add the scrolling section to the main layout - // class for scroll area to pass key presses down + // class solely for scroll area to pass key presses down class ChannelArea : public QScrollArea { public: @@ -167,9 +170,33 @@ void FxMixerView::addNewChannel() chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); updateFxLine(newChannelIndex); + + updateMaxChannelSelector(); } +void FxMixerView::updateMaxChannelSelector() +{ + // update the and max. channel number for every instrument + QVector songTrackList = engine::getSong()->tracks(); + QVector bbTrackList = engine::getBBTrackContainer()->tracks(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) + { + QVector trackList = trackLists[tl]; + for(int i=0; itype() == track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; + inst->effectChannelModel()->setMaxValue( + m_fxChannelViews.size()-1); + } + } + } +} + void FxMixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { @@ -286,6 +313,7 @@ void FxMixerView::deleteChannel(int index) } setCurrentFxLine(selLine); + updateMaxChannelSelector(); } From 333df687e6e21a838cd1145b269cf522fe5109dd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Oct 2009 02:45:32 -0700 Subject: [PATCH 11/34] Ability to save mixer sends to disk with the mmp LMMS can load projects with the old mixer and new projects with the new mixer. By "new mixer", I simply mean not hardcoded to 64 channels. --- include/AutomatableModel.h | 12 ------ include/FxLine.h | 5 ++- include/FxMixer.h | 9 +++-- include/FxMixerView.h | 4 ++ src/core/FxMixer.cpp | 83 ++++++++++++++++++++++++++++++++------ src/core/song.cpp | 22 +++++++--- src/gui/FxLine.cpp | 27 ++++++++++--- src/gui/FxMixerView.cpp | 32 +++++++++++++-- 8 files changed, 152 insertions(+), 42 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 6bd68187a..0f9308975 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -280,18 +280,6 @@ signals: inline type maxValue() const \ { \ return AutomatableModel::maxValue(); \ - } \ - \ - inline void setMinValue(type val) \ - { \ - m_minValue = val; \ - if( m_value < m_minValue ) m_value = m_minValue; \ - } \ - \ - inline void setMaxValue(type val) \ - { \ - m_maxValue = val; \ - if( m_value > m_maxValue ) m_value = m_maxValue; \ } // some typed AutomatableModel-definitions diff --git a/include/FxLine.h b/include/FxLine.h index 70def8637..89e0321f3 100644 --- a/include/FxLine.h +++ b/include/FxLine.h @@ -5,6 +5,7 @@ #include #include "knob.h" +#include "lcd_spinbox.h" #include "SendButtonIndicator.h" class FxMixerView; @@ -15,19 +16,21 @@ class FxLine : public QWidget Q_OBJECT public: FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex); + ~FxLine(); virtual void paintEvent( QPaintEvent * ); virtual void mousePressEvent( QMouseEvent * ); virtual void mouseDoubleClickEvent( QMouseEvent * ); inline int channelIndex() { return m_channelIndex; } - inline void setChannelIndex(int index) { m_channelIndex = index; } + void setChannelIndex(int index); knob * m_sendKnob; SendButtonIndicator * m_sendBtn; private: FxMixerView * m_mv; + lcdSpinBox * m_lcd; int m_channelIndex; diff --git a/include/FxMixer.h b/include/FxMixer.h index 19caa1479..b9f11ca3e 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -73,9 +73,6 @@ public: void prepareMasterMix(); void masterMix( sampleFrame * _buf ); - - void clear(); - virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent ); virtual void loadSettings( const QDomElement & _this ); @@ -112,6 +109,9 @@ public: // delete a channel from the FX mixer. void deleteChannel(int index); + // delete all the mixer channels except master and remove all effects + void clear(); + // re-arrange channels void moveChannelLeft(int index); void moveChannelRight(int index); @@ -128,6 +128,9 @@ private: // the fx channels in the mixer. index 0 is always master. QVector m_fxChannels; + + void allocateChannelsTo(int num); + friend class mixerWorkerThread; friend class FxMixerView; diff --git a/include/FxMixerView.h b/include/FxMixerView.h index cff856659..1dc08a6e5 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -91,6 +91,10 @@ public: void moveChannelLeft(int index); void moveChannelRight(int index); + // make sure the display syncs up with the fx mixer. + // useful for loading projects + void refreshDisplay(); + private slots: void updateFaders(); void addNewChannel(); diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 488fb09d9..e28deda3b 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -442,13 +442,16 @@ void FxMixer::masterMix( sampleFrame * _buf ) void FxMixer::clear() { - for( int i = 0; i < m_fxChannels.size(); ++i ) + while( m_fxChannels.size() > 1 ) { - clearChannel(i); + deleteChannel(1); } + + clearChannel(0); } + void FxMixer::clearChannel(fx_ch_t index) { FxChannel * ch = m_fxChannels[index]; @@ -483,38 +486,94 @@ void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) { for( int i = 0; i < m_fxChannels.size(); ++i ) { + FxChannel * ch = m_fxChannels[i]; + QDomElement fxch = _doc.createElement( QString( "fxchannel" ) ); _this.appendChild( fxch ); - m_fxChannels[i]->m_fxChain.saveState( _doc, fxch ); - m_fxChannels[i]->m_volumeModel.saveSettings( _doc, fxch, - "volume" ); - m_fxChannels[i]->m_muteModel.saveSettings( _doc, fxch, - "muted" ); + + ch->m_fxChain.saveState( _doc, fxch ); + ch->m_volumeModel.saveSettings( _doc, fxch, "volume" ); + ch->m_muteModel.saveSettings( _doc, fxch, "muted" ); fxch.setAttribute( "num", i ); - fxch.setAttribute( "name", m_fxChannels[i]->m_name ); + fxch.setAttribute( "name", ch->m_name ); + + // add the channel sends + for( int si = 0; si < ch->m_sends.size(); ++si ) + { + QDomElement sendsDom = _doc.createElement( QString( "send" ) ); + fxch.appendChild( sendsDom ); + + sendsDom.setAttribute( "channel", ch->m_sends[si] ); + ch->m_sendAmount[si]->saveSettings( _doc, sendsDom, "amount"); + } } } +void FxMixer::allocateChannelsTo(int num) +{ + while( num > m_fxChannels.size() - 1 ) + { + createChannel(); + + // delete the default send to master + deleteChannelSend(m_fxChannels.size()-1, 0); + } +} void FxMixer::loadSettings( const QDomElement & _this ) { clear(); QDomNode node = _this.firstChild(); - for( int i = 0; i <= 64; ++i ) // TODO make this work + bool thereIsASend = false; + + while( ! node.isNull() ) { QDomElement fxch = node.toElement(); + + // index of the channel we are about to load int num = fxch.attribute( "num" ).toInt(); - m_fxChannels[num]->m_fxChain.restoreState( - fxch.firstChildElement( - m_fxChannels[num]->m_fxChain.nodeName() ) ); + + // allocate enough channels + allocateChannelsTo( num ); + m_fxChannels[num]->m_volumeModel.loadSettings( fxch, "volume" ); m_fxChannels[num]->m_muteModel.loadSettings( fxch, "muted" ); m_fxChannels[num]->m_name = fxch.attribute( "name" ); + + m_fxChannels[num]->m_fxChain.restoreState( fxch.firstChildElement( + m_fxChannels[num]->m_fxChain.nodeName() ) ); + + // mixer sends + QDomNodeList chData = fxch.childNodes(); + for( unsigned int i=0; inodeName() ) + { + engine::fxMixer()->restoreState( node.toElement() ); + + // refresh FxMixerView + engine::fxMixerView()->refreshDisplay(); + } + + node = node.nextSibling(); + } + + node = mmp.content().firstChild(); + while( !node.isNull() ) { if( node.isElement() ) @@ -917,10 +934,6 @@ void song::loadProject( const QString & _file_name ) { restoreControllerStates( node.toElement() ); } - else if( node.nodeName() == engine::fxMixer()->nodeName() ) - { - engine::fxMixer()->restoreState( node.toElement() ); - } else if( engine::hasGUI() ) { if( node.nodeName() == @@ -973,7 +986,6 @@ void song::loadProject( const QString & _file_name ) // resolve all IDs so that autoModels are automated automationPattern::resolveAllIDs(); - engine::getMixer()->unlock(); configManager::inst()->addRecentlyOpenedProject( _file_name ); diff --git a/src/gui/FxLine.cpp b/src/gui/FxLine.cpp index 5d57a6b0f..172d30b17 100644 --- a/src/gui/FxLine.cpp +++ b/src/gui/FxLine.cpp @@ -9,7 +9,6 @@ #include "FxMixerView.h" #include "embed.h" #include "engine.h" -#include "lcd_spinbox.h" #include "SendButtonIndicator.h" FxLine::FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex) : @@ -32,11 +31,27 @@ FxLine::FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex) : m_sendBtn->move(4,4); // channel number - lcdSpinBox * l = new lcdSpinBox( 2, this ); - l->model()->setRange( m_channelIndex, m_channelIndex ); - l->model()->setValue( m_channelIndex ); - l->move( 2, 58 ); - l->setMarginWidth( 1 ); + m_lcd = new lcdSpinBox( 2, this ); + m_lcd->model()->setRange( m_channelIndex, m_channelIndex ); + m_lcd->model()->setValue( m_channelIndex ); + m_lcd->move( 2, 58 ); + m_lcd->setMarginWidth( 1 ); +} + +FxLine::~FxLine() +{ + delete m_sendKnob; + delete m_sendBtn; + delete m_lcd; +} + + +void FxLine::setChannelIndex(int index) { + m_channelIndex = index; + + m_lcd->model()->setRange( m_channelIndex, m_channelIndex ); + m_lcd->model()->setValue( m_channelIndex ); + m_lcd->update(); } diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 70832c71b..ad844ec4f 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -91,8 +91,8 @@ FxMixerView::FxMixerView() : m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); } - // add the scrolling section to the main layout + // add the scrolling section to the main layout // class solely for scroll area to pass key presses down class ChannelArea : public QScrollArea { @@ -175,6 +175,32 @@ void FxMixerView::addNewChannel() } +void FxMixerView::refreshDisplay() +{ + // delete all views and re-add them + for( int i = 1; iremoveWidget(m_fxChannelViews[i]->m_fxLine); + delete m_fxChannelViews[i]->m_fader; + delete m_fxChannelViews[i]->m_muteBtn; + delete m_fxChannelViews[i]->m_fxLine; + delete m_fxChannelViews[i]; + } + m_channelAreaWidget->adjustSize(); + + // re-add the views + m_fxChannelViews.resize(engine::fxMixer()->numChannels()); + for( int i = 1; i < m_fxChannelViews.size(); ++i ) + { + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); + chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); + } + + // fix master +//TODO +} + + void FxMixerView::updateMaxChannelSelector() { // update the and max. channel number for every instrument @@ -190,8 +216,8 @@ void FxMixerView::updateMaxChannelSelector() if( trackList[i]->type() == track::InstrumentTrack ) { InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; - inst->effectChannelModel()->setMaxValue( - m_fxChannelViews.size()-1); + inst->effectChannelModel()->setRange(0, + m_fxChannelViews.size()-1,1); } } } From 275bf5bb0e5ebe124c813195859c177fbe1fddbf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Oct 2009 03:40:20 -0700 Subject: [PATCH 12/34] Fix FL Import with new mixer --- plugins/flp_import/FlpImport.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/flp_import/FlpImport.cpp b/plugins/flp_import/FlpImport.cpp index b243c9427..5cebea7b3 100644 --- a/plugins/flp_import/FlpImport.cpp +++ b/plugins/flp_import/FlpImport.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "FlpImport.h" #include "note_play_handle.h" @@ -40,6 +41,7 @@ #include "Effect.h" #include "engine.h" #include "FxMixer.h" +#include "FxMixerView.h" #include "group_box.h" #include "Instrument.h" #include "InstrumentTrack.h" @@ -903,7 +905,6 @@ bool FlpImport::tryFLPImport( trackContainer * _tc ) const bool is_journ = engine::projectJournal()->isJournalling(); engine::projectJournal()->setJournalling( false ); - while( file().atEnd() == false ) { FLP_Events ev = static_cast( readByte() ); @@ -1558,9 +1559,15 @@ else // now create a project from FL_Project data structure - engine::getSong()->clearProject(); + // configure the mixer + for( int i=0; icreateChannel(); + } + engine::fxMixerView()->refreshDisplay(); + // set global parameters engine::getSong()->setMasterVolume( p.mainVolume ); engine::getSong()->setMasterPitch( p.mainPitch ); From 23e33010376852f8af8f67cc1d1eea0a01b29009 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Oct 2009 03:45:04 -0700 Subject: [PATCH 13/34] FxMixerView - Left and right to select channels --- src/gui/FxMixerView.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index ad844ec4f..0cb3f1938 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -404,12 +404,22 @@ void FxMixerView::keyPressEvent(QKeyEvent * e) { moveChannelLeft( m_currentFxLine->channelIndex() ); } + else + { + // select channel to the left + setCurrentFxLine( m_currentFxLine->channelIndex()-1 ); + } break; case Qt::Key_Right: if( e->modifiers() & Qt::AltModifier ) { moveChannelRight( m_currentFxLine->channelIndex() ); } + else + { + // select channel to the right + setCurrentFxLine( m_currentFxLine->channelIndex()+1 ); + } break; } } @@ -418,7 +428,10 @@ void FxMixerView::keyPressEvent(QKeyEvent * e) void FxMixerView::setCurrentFxLine( int _line ) { - setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); + if( _line >= 0 && _line < m_fxChannelViews.size() ) + { + setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); + } } From 1c9b24afb783a1efd6da3171f332918583b4e438 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Oct 2009 11:37:46 -0700 Subject: [PATCH 14/34] Fixed a mixer bug regarding deleting channels --- src/core/FxMixer.cpp | 3 +++ src/gui/widgets/knob.cpp | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index e28deda3b..43a254660 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -102,6 +102,8 @@ int FxMixer::createChannel() void FxMixer::deleteChannel(int index) { + m_fxChannels[index]->m_lock.lock(); + // go through every instrument and adjust for the channel index change QVector songTrackList = engine::getSong()->tracks(); QVector bbTrackList = engine::getBBTrackContainer()->tracks(); @@ -165,6 +167,7 @@ void FxMixer::deleteChannel(int index) } // actually delete the channel + delete m_fxChannels[index]; m_fxChannels.remove(index); } diff --git a/src/gui/widgets/knob.cpp b/src/gui/widgets/knob.cpp index 31a772c1b..72a57c735 100644 --- a/src/gui/widgets/knob.cpp +++ b/src/gui/widgets/knob.cpp @@ -492,7 +492,11 @@ void knob::mouseMoveEvent( QMouseEvent * _me ) void knob::mouseReleaseEvent( QMouseEvent * /* _me*/ ) { - model()->addJournalEntryFromOldToCurVal(); + AutomatableModel * thisModel = model(); + if( thisModel ) + { + thisModel->addJournalEntryFromOldToCurVal(); + } m_buttonPressed = false; From abfdb6a74d68272558e360d98789e1c11bbd9eb4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Oct 2009 17:42:14 -0700 Subject: [PATCH 15/34] Fixed bug - Instruments had wrong channel models Instruments were initialized with hardcoded 0-10 for min/max channel selector range. Fixed. --- src/core/FxMixer.cpp | 2 +- src/gui/FxMixerView.cpp | 6 +++--- src/tracks/InstrumentTrack.cpp | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 43a254660..7cf27b8c3 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -512,7 +512,7 @@ void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) } } - +// make sure we have at least num channels void FxMixer::allocateChannelsTo(int num) { while( num > m_fxChannels.size() - 1 ) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 0cb3f1938..b61bcbf7b 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -196,14 +196,13 @@ void FxMixerView::refreshDisplay() chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); } - // fix master -//TODO + updateMaxChannelSelector(); } +// update the and max. channel number for every instrument void FxMixerView::updateMaxChannelSelector() { - // update the and max. channel number for every instrument QVector songTrackList = engine::getSong()->tracks(); QVector bbTrackList = engine::getBBTrackContainer()->tracks(); @@ -439,6 +438,7 @@ void FxMixerView::setCurrentFxLine( int _line ) void FxMixerView::clear() { m_rackView->clearViews(); + refreshDisplay(); } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index f56d6487b..252980448 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -103,13 +103,14 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) : this, tr( "Panning" ) ), m_pitchModel( 0, -100, 100, 1, this, tr( "Pitch" ) ), m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ), - m_effectChannelModel( 0, 0, 10, this, tr( "FX channel" ) ), // change this so it's a combo box, all the channels and then new. + m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), // change this so it's a combo box, all the channels and then new. m_instrument( NULL ), m_soundShaping( this ), m_arpeggiator( this ), m_chordCreator( this ), m_piano( this ) { + m_effectChannelModel.setRange( 0, engine::fxMixer()->numChannels()-1, 1); connect( baseNoteModel(), SIGNAL( dataChanged() ), this, SLOT( updateBaseNote() ) ); connect( &m_pitchModel, SIGNAL( dataChanged() ), From 3fa96a576c621a2ac9f470c557ec3f5e2f80df16 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Oct 2009 01:36:55 +0200 Subject: [PATCH 16/34] Mixer: rewrote/reorganized job queueing for worker threads In Mixer, the old C-macro based code has been replaced by an OOP-like design. Management of job queue now happens via some static member methods of MixerWorkerThread. All the moved code still needs to be splitted into some new files but here's a first dirty version. All objects that are intended to be processed by MixerWorkerThreads have to inherit ThreadableJob (name of class is subject of change). One can add jobs to the job queue even if the queue is already being processed. This is merely important for multithreading with upcoming FX sends support. --- include/AudioPort.h | 9 +- include/mixer.h | 177 +++++++++++++++++++++++++++- include/play_handle.h | 18 ++- src/core/FxMixer.cpp | 7 ++ src/core/audio/AudioPort.cpp | 17 ++- src/core/mixer.cpp | 220 +---------------------------------- 6 files changed, 224 insertions(+), 224 deletions(-) diff --git a/include/AudioPort.h b/include/AudioPort.h index 7157f729b..d84dd179f 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -33,7 +33,7 @@ class EffectChain; -class AudioPort +class AudioPort : public ThreadableJob { public: AudioPort( const QString & _name, bool _has_effect_chain = true ); @@ -109,6 +109,13 @@ public: bool processEffects(); + // ThreadableJob stuff + virtual void doProcessing( sampleFrame * ); + virtual bool requiresProcessing() const + { + return true; + } + enum bufferUsages { diff --git a/include/mixer.h b/include/mixer.h index 3562db3f1..81be6af7f 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -62,11 +62,46 @@ const Keys BaseKey = Key_A; const Octaves BaseOctave = DefaultOctave; -#include "play_handle.h" - class MixerWorkerThread; +// TODO: move to ThreadableJob.h +class ThreadableJob +{ +public: + ThreadableJob() : + m_done( false ) + { + } + + void reset() + { + m_done = false; + } + + bool process( sampleFrame * _working_buffer ) + { + if( m_done.fetchAndStoreOrdered( true ) == false ) + { + doProcessing( _working_buffer ); + return true; + } + return false; + } + + virtual bool requiresProcessing() const = 0; + + +private: + virtual void doProcessing( sampleFrame * _working_buffer ) = 0; + + QAtomicInt m_done; + +} ; + + +#include "play_handle.h" + class EXPORT mixer : public QObject { @@ -466,4 +501,142 @@ private: } ; +// TODO: move to MixerWorkerThread.h / MixerWorkerThread.cpp +#include "Cpu.h" +#include "engine.h" + +class MixerWorkerThread : public QThread +{ +public: + struct JobQueue + { +#define JOB_QUEUE_SIZE 1024 + JobQueue() : + queueSize( 0 ), + itemsDone( 0 ) + { + for( int i = 0; i < JOB_QUEUE_SIZE; ++i ) + { + items[i] = NULL; + } + } + + ThreadableJob * items[JOB_QUEUE_SIZE]; + QAtomicInt queueSize; + QAtomicInt itemsDone; + } ; + + static JobQueue s_jobQueue; + + MixerWorkerThread( int _worker_num, mixer * _mixer ) : + QThread( _mixer ), + m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), + m_workerNum( _worker_num ), + m_quit( false ), + m_mixer( _mixer ), + m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) + { + } + + virtual ~MixerWorkerThread() + { + CPU::freeFrames( m_workingBuf ); + } + + virtual void quit() + { + m_quit = true; + } + + void processJobQueue() + { + for( int i = 0; i < s_jobQueue.queueSize; ++i ) + { + // returns true if ThreadableJob was not processed before + if( s_jobQueue.items[i]->process( m_workingBuf ) ) + { + s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); + } + } + } + + template + static void fillJobQueue( const T & _vec ) + { + s_jobQueue.queueSize = 0; + s_jobQueue.itemsDone = 0; + for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) + { + addJob( *it ); + } + } + + static void addJob( ThreadableJob * _job ) + { + if( _job->requiresProcessing() ) + { + _job->reset(); + s_jobQueue.items[s_jobQueue.queueSize.fetchAndAddOrdered(1)] = _job; + } + } + + +// define a pause instruction for spinlock-loop - merely useful on +// HyperThreading systems with just one physical core (e.g. Intel Atom) +#ifdef LMMS_HOST_X86 +#define SPINLOCK_PAUSE() asm( "pause" ) +#else +#ifdef LMMS_HOST_X86_64 +#define SPINLOCK_PAUSE() asm( "pause" ) +#else +#define SPINLOCK_PAUSE() +#endif +#endif + + static void startJobs() + { + // TODO: this is dirty! + engine::getMixer()->m_queueReadyWaitCond.wakeAll(); + } + + static void waitForJobs() + { + // TODO: this is dirty! + mixer * m = engine::getMixer(); + m->m_workers[m->m_numWorkers]->processJobQueue(); + while( s_jobQueue.itemsDone < s_jobQueue.queueSize ) + { + SPINLOCK_PAUSE(); + } + } + + static void startAndWaitForJobs() + { + startJobs(); + waitForJobs(); + } + + +private: + virtual void run() + { + QMutex m; + while( m_quit == false ) + { + m.lock(); + m_queueReadyWaitCond->wait( &m ); + processJobQueue(); + m.unlock(); + } + } + + sampleFrame * m_workingBuf; + int m_workerNum; + volatile bool m_quit; + mixer * m_mixer; + QWaitCondition * m_queueReadyWaitCond; + +} ; + + #endif diff --git a/include/play_handle.h b/include/play_handle.h index 365da3f19..1a49fb540 100644 --- a/include/play_handle.h +++ b/include/play_handle.h @@ -25,15 +25,12 @@ #ifndef _PLAY_HANDLE_H #define _PLAY_HANDLE_H -#include -#include - -#include "lmms_basics.h" +#include "mixer.h" class track; -class playHandle +class playHandle : public ThreadableJob { public: enum Types @@ -71,6 +68,17 @@ public: return m_type; } + // required for ThreadableJob + virtual void doProcessing( sampleFrame * _working_buffer ) + { + play( _working_buffer ); + } + + virtual bool requiresProcessing() const + { + return !done(); + } + virtual void play( sampleFrame * _working_buffer ) = 0; virtual bool done() const = 0; diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 7cf27b8c3..844f8fd7d 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -438,6 +438,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 + for( int i = 0; i < numChannels(); ++i) + { + engine::getMixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer, + engine::getMixer()->framesPerPeriod() ); + } } diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 2b4301178..d9dc86b06 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -24,9 +24,10 @@ #include "AudioPort.h" #include "AudioDevice.h" -#include "EffectChain.h" -#include "engine.h" #include "Cpu.h" +#include "EffectChain.h" +#include "FxMixer.h" +#include "engine.h" AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) : @@ -123,3 +124,15 @@ bool AudioPort::processEffects() } + + +void AudioPort::doProcessing( sampleFrame * ) +{ + const bool me = processEffects(); + if( me || m_bufferUsage != NoUsage ) + { + engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() ); + nextPeriod(); + } +} + diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index b33e4c49f..f8e0c38b0 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -61,197 +61,11 @@ #endif -class MixerWorkerThread : public QThread -{ -public: - enum JobTypes - { - InvalidJob, - PlayHandle, - AudioPortEffects, - EffectChannel, - NumJobTypes - } ; - - struct JobQueueItem - { - JobQueueItem() : - type( InvalidJob ), - job( NULL ), - param( 0 ), - done( false ) - { - } - JobQueueItem( JobTypes _type, void * _job, int _param = 0 ) : - type( _type ), - job( _job ), - param( _param ), - done( false ) - { - } - - JobTypes type; - void * job; - int param; - - QAtomicInt done; - - } ; - - struct JobQueue - { -#define JOB_QUEUE_SIZE 1024 - JobQueue() : - queueSize( 0 ) - { - } - - JobQueueItem items[JOB_QUEUE_SIZE]; - int queueSize; - QAtomicInt itemsDone; - } ; - - static JobQueue s_jobQueue; - - MixerWorkerThread( int _worker_num, mixer * _mixer ) : - QThread( _mixer ), - m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), - m_workerNum( _worker_num ), - m_quit( false ), - m_mixer( _mixer ), - m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) - { - } - - virtual ~MixerWorkerThread() - { - CPU::freeFrames( m_workingBuf ); - } - - virtual void quit() - { - m_quit = true; - } - - void processJobQueue(); - - -private: - virtual void run() - { - QMutex m; - while( m_quit == false ) - { - m.lock(); - m_queueReadyWaitCond->wait( &m ); - processJobQueue(); - m.unlock(); - } - } - - sampleFrame * m_workingBuf; - int m_workerNum; - volatile bool m_quit; - mixer * m_mixer; - QWaitCondition * m_queueReadyWaitCond; - -} ; - MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; -void MixerWorkerThread::processJobQueue() -{ - for( int i = 0; i < s_jobQueue.queueSize; ++i ) - { - JobQueueItem * it = &s_jobQueue.items[i]; - if( it->done.fetchAndStoreOrdered( 1 ) == 0 ) - { - switch( it->type ) - { - case PlayHandle: - ( (playHandle *) it->job )-> - play( m_workingBuf ); - break; - case AudioPortEffects: - { - AudioPort * a = (AudioPort *) it->job; - const bool me = a->processEffects(); - if( me || a->m_bufferUsage != AudioPort::NoUsage ) - { - engine::fxMixer()->mixToChannel( a->firstBuffer(), - a->nextFxChannel() ); - a->nextPeriod(); - } - } - break; - case EffectChannel: - engine::fxMixer()->processChannel( (fx_ch_t) it->param ); - break; - default: - break; - } - s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); - } - } -} - -#define FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ - MixerWorkerThread::s_jobQueue.queueSize = 0; \ - MixerWorkerThread::s_jobQueue.itemsDone = 0; \ - for( _vec_type::Iterator it = _vec.begin(); \ - it != _vec.end(); ++it ) \ - { \ - if( _condition ) \ - { - -#define FILL_JOB_QUEUE_END() \ - ++MixerWorkerThread::s_jobQueue.queueSize; \ - } \ - } - -#define FILL_JOB_QUEUE(_vec_type,_vec,_job_type,_condition) \ - FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ - MixerWorkerThread::s_jobQueue.items \ - [MixerWorkerThread::s_jobQueue.queueSize] = \ - MixerWorkerThread::JobQueueItem( _job_type, \ - (void *) *it ); \ - FILL_JOB_QUEUE_END() - -#define FILL_JOB_QUEUE_PARAM(_vec_type,_vec,_job_type,_condition) \ - FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ - MixerWorkerThread::s_jobQueue.items \ - [MixerWorkerThread::s_jobQueue.queueSize] = \ - MixerWorkerThread::JobQueueItem( _job_type, \ - NULL, *it ); \ - FILL_JOB_QUEUE_END() - -#define START_JOBS() \ - m_queueReadyWaitCond.wakeAll(); - -// define a pause instruction for spinlock-loop - merely useful on -// HyperThreading systems with just one physical core (e.g. Intel Atom) -#ifdef LMMS_HOST_X86 -#define SPINLOCK_PAUSE() asm( "pause" ) -#else -#ifdef LMMS_HOST_X86_64 -#define SPINLOCK_PAUSE() asm( "pause" ) -#else -#define SPINLOCK_PAUSE() -#endif -#endif - -#define WAIT_FOR_JOBS() \ - m_workers[m_numWorkers]->processJobQueue(); \ - while( MixerWorkerThread::s_jobQueue.itemsDone < \ - MixerWorkerThread::s_jobQueue.queueSize ) \ - { \ - SPINLOCK_PAUSE(); \ - } \ - - mixer::mixer() : @@ -347,7 +161,7 @@ mixer::~mixer() { m_workers[w]->quit(); } - START_JOBS(); + MixerWorkerThread::startJobs(); for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->wait( 500 ); @@ -565,11 +379,8 @@ sampleFrameA * mixer::renderNextBuffer() // STAGE 1: run and render all play handles - FILL_JOB_QUEUE(PlayHandleList,m_playHandles, - MixerWorkerThread::PlayHandle, - !( *it )->done()); - START_JOBS(); - WAIT_FOR_JOBS(); + MixerWorkerThread::fillJobQueue( m_playHandles ); + MixerWorkerThread::startAndWaitForJobs(); // removed all play handles which are done for( PlayHandleList::Iterator it = m_playHandles.begin(); @@ -594,31 +405,12 @@ sampleFrameA * mixer::renderNextBuffer() // STAGE 2: process effects of all instrument- and sampletracks - FILL_JOB_QUEUE(QVector,m_audioPorts, - MixerWorkerThread::AudioPortEffects,1); - START_JOBS(); - WAIT_FOR_JOBS(); + MixerWorkerThread::fillJobQueue >( m_audioPorts ); + MixerWorkerThread::startAndWaitForJobs(); - - // STAGE 3: process effects in FX mixer - /*FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, - MixerWorkerThread::EffectChannel,1); - START_JOBS(); - WAIT_FOR_JOBS();*/ - - - // STAGE 4: do master mix in FX mixer + // STAGE 3: do master mix in FX mixer fxm->masterMix( m_writeBuf ); - WAIT_FOR_JOBS(); - - // clear all channel buffers - for( int i = 0; i < fxm->numChannels(); ++i) - { - engine::getMixer()->clearAudioBuffer( fxm->effectChannel(i)->m_buffer, - engine::getMixer()->framesPerPeriod() ); - } - unlock(); From f6f4414c989e55a867045a2575252b8b78289942 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 12 Oct 2009 01:09:35 -0700 Subject: [PATCH 17/34] 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(); } } From 95eb60f05fc2dc190e8c8e84ffcbcbba7fad8e7c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 13 Oct 2009 02:21:13 -0700 Subject: [PATCH 18/34] Fixed the audio/visual screwups With the help of Toby, effects in the channels work and the peak displays correctly. However the freeze-up bug is still wreaking havoc. --- include/FxMixer.h | 1 + include/mixer.h | 9 +++++++-- src/core/FxMixer.cpp | 27 ++++++++++++++++++++------- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/include/FxMixer.h b/include/FxMixer.h index d4e4df6a3..3d76903d5 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -52,6 +52,7 @@ class FxChannel : public ThreadableJob QString m_name; QMutex m_lock; int m_channelIndex; // what channel index are we + bool m_queued; // are we queued up for rendering yet? // pointers to other channels that this one sends to QVector m_sends; diff --git a/include/mixer.h b/include/mixer.h index cb6b65aaa..d992920c6 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -569,11 +569,16 @@ public: } } - template - static void fillJobQueue( const T & _vec ) + static void resetJobQueue() { s_jobQueue.queueSize = 0; s_jobQueue.itemsDone = 0; + } + + template + static void fillJobQueue( const T & _vec ) + { + resetJobQueue(); for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) { addJob( *it ); diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 66c65537a..4c3e17007 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -43,7 +43,8 @@ FxChannel::FxChannel( Model * _parent ) : m_muteModel( false, _parent ), m_volumeModel( 1.0, 0.0, 2.0, 0.01, _parent ), m_name(), - m_lock() + m_lock(), + m_queued( false ) { engine::getMixer()->clearAudioBuffer( m_buffer, engine::getMixer()->framesPerPeriod() ); @@ -62,12 +63,16 @@ FxChannel::~FxChannel() void FxChannel::doProcessing(sampleFrame * _buf) { FxMixer * fxm = engine::fxMixer(); - const fpp_t fpp = engine::getMixer()->framesPerPeriod(); - if( _buf == NULL ) - { - _buf = m_buffer; - } + + // ignore the passed _buf + // always use m_buffer + // this is just an auxilliary buffer if doProcessing() + // needs one for processing while running + // particularly important for playHandles, so Instruments + // can operate on this buffer the whole time + // this improves cache hit rate + _buf = m_buffer; if( ! m_muteModel.value() ) { @@ -454,6 +459,10 @@ void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) // remember what channel number we are, 'cause we need it later thisCh->m_channelIndex = _ch; + // if we're muted or this channel is seen already, discount it + if( thisCh->m_muteModel.value() || thisCh->m_queued ) + return; + int numDeps = thisCh->m_receives.size(); if( numDeps > 0 ) { @@ -465,6 +474,7 @@ void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) else { // add this channel to job list + thisCh->m_queued = true; MixerWorkerThread::addJob( thisCh ); } @@ -475,15 +485,17 @@ void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) void FxMixer::masterMix( sampleFrame * _buf ) { const int fpp = engine::getMixer()->framesPerPeriod(); - memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); // 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. + MixerWorkerThread::resetJobQueue(); addChannelLeaf( 0, _buf ); MixerWorkerThread::startAndWaitForJobs(); + memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); + const float v = m_fxChannels[0]->m_volumeModel.value(); for( f_cnt_t f = 0; f < engine::getMixer()->framesPerPeriod(); ++f ) { @@ -501,6 +513,7 @@ void FxMixer::masterMix( sampleFrame * _buf ) engine::getMixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer, engine::getMixer()->framesPerPeriod() ); m_fxChannels[i]->reset(); + m_fxChannels[i]->m_queued = false; } } From 45a2f81eaa94a39ef619dfdb2aaaab46dba98d5c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 13 Oct 2009 03:14:23 -0700 Subject: [PATCH 19/34] Rough fix for freeze-up bug Added startJobs() to the waitForJobs() loop, and don't give up in masterMix() until status of master channel is Done. --- include/mixer.h | 2 +- src/core/FxMixer.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/mixer.h b/include/mixer.h index d992920c6..d6db2cb12 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -620,7 +620,7 @@ public: m->m_workers[m->m_numWorkers]->processJobQueue(); while( s_jobQueue.itemsDone < s_jobQueue.queueSize ) { - SPINLOCK_PAUSE(); + startJobs(); } } diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 4c3e17007..1e8a255e6 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -492,7 +492,10 @@ void FxMixer::masterMix( sampleFrame * _buf ) // to be processed. MixerWorkerThread::resetJobQueue(); addChannelLeaf( 0, _buf ); - MixerWorkerThread::startAndWaitForJobs(); + while( m_fxChannels[0]->m_state != ThreadableJob::Done ) + { + MixerWorkerThread::startAndWaitForJobs(); + } memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); From a9d24d34f26ffe63306650979bbd571d1052c7a0 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 14 Oct 2009 01:22:31 +0200 Subject: [PATCH 20/34] Mixer/FxMixer: separated MixerWorkerThread and ThreadableJob into files Declarations and implementations of MixerWorkerThread and ThreadableJob have been moved into separate source files. Furthermore there were some improvements to MixerWorkerThreads. MixerWorkerThread::processJobQueue() does not return until the job queue completely has been processed. This way each thread can "help" to finish processing the queue and does not get back to sleep until all of the work is done. Management of the queue is now done via an array of QAtomicPointers. Items that are non-NULL still need to be processed while NULL-items were taken from the queue (i.e. in progress or done). Thus we do not need to deal with ThreadableJob-states within MixerWorkerThread anymore. --- include/AudioPort.h | 1 - include/FxMixer.h | 1 - include/MixerWorkerThread.h | 97 +++++++++++++++++ include/ThreadableJob.h | 84 +++++++++++++++ include/mixer.h | 190 +-------------------------------- include/play_handle.h | 1 + src/core/FxMixer.cpp | 23 ++-- src/core/MixerWorkerThread.cpp | 138 ++++++++++++++++++++++++ src/core/mixer.cpp | 8 +- 9 files changed, 329 insertions(+), 214 deletions(-) create mode 100644 include/MixerWorkerThread.h create mode 100644 include/ThreadableJob.h create mode 100644 src/core/MixerWorkerThread.cpp diff --git a/include/AudioPort.h b/include/AudioPort.h index d84dd179f..6ac7e8294 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -27,7 +27,6 @@ #include #include -#include #include "mixer.h" diff --git a/include/FxMixer.h b/include/FxMixer.h index 3d76903d5..21fe100f3 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -76,7 +76,6 @@ public: virtual ~FxMixer(); void mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ); - void processChannel( fx_ch_t _ch, sampleFrame * _buf = NULL ); void prepareMasterMix(); void masterMix( sampleFrame * _buf ); diff --git a/include/MixerWorkerThread.h b/include/MixerWorkerThread.h new file mode 100644 index 000000000..03e195e60 --- /dev/null +++ b/include/MixerWorkerThread.h @@ -0,0 +1,97 @@ +/* + * MixerWorkerThread.h - declaration of class MixerWorkerThread + * + * Copyright (c) 2009 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef _MIXER_WORKER_THREAD_H +#define _MIXER_WORKER_THREAD_H + +#include +#include +#include + +#include "mixer.h" + + +class MixerWorkerThread : public QThread +{ +public: + struct JobQueue + { +#define JOB_QUEUE_SIZE 1024 + JobQueue() : + items(), + queueSize( 0 ), + itemsDone( 0 ) + { + } + + QAtomicPointer items[JOB_QUEUE_SIZE]; + QAtomicInt queueSize; + QAtomicInt itemsDone; + } ; + + static JobQueue s_jobQueue; + + MixerWorkerThread( int _worker_num, mixer * _mixer ); + virtual ~MixerWorkerThread(); + + virtual void quit(); + + void processJobQueue(); + static void resetJobQueue(); + + template + static void fillJobQueue( const T & _vec ) + { + resetJobQueue(); + for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) + { + addJob( *it ); + } + } + + static void addJob( ThreadableJob * _job ); + + static void startJobs(); + static void waitForJobs(); + + static void startAndWaitForJobs() + { + startJobs(); + waitForJobs(); + } + + +private: + virtual void run(); + + sampleFrame * m_workingBuf; + int m_workerNum; + volatile bool m_quit; + mixer * m_mixer; + QWaitCondition * m_queueReadyWaitCond; + +} ; + + +#endif diff --git a/include/ThreadableJob.h b/include/ThreadableJob.h new file mode 100644 index 000000000..bade2baa5 --- /dev/null +++ b/include/ThreadableJob.h @@ -0,0 +1,84 @@ +/* + * ThreadableJob.h - declaration of class ThreadableJob + * + * Copyright (c) 2009 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef _THREADABLE_JOB_H +#define _THREADABLE_JOB_H + +#include + +#include "mixer.h" + + +class ThreadableJob +{ +public: + + enum ProcessingState + { + Unstarted, + Queued, + InProgress, + Done + }; + + ThreadableJob() : + m_state( ThreadableJob::Unstarted ) + { + } + + inline ProcessingState state() const + { + return static_cast( (int) m_state ); + } + + inline void reset() + { + m_state = Unstarted; + } + + inline void queue() + { + m_state = Queued; + } + + void process( sampleFrame * _working_buffer ) + { + if( m_state.testAndSetOrdered( Queued, InProgress ) ) + { + doProcessing( _working_buffer ); + m_state = Done; + } + } + + virtual bool requiresProcessing() const = 0; + + +protected: + virtual void doProcessing( sampleFrame * _working_buffer ) = 0; + + QAtomicInt m_state; + +} ; + +#endif diff --git a/include/mixer.h b/include/mixer.h index d6db2cb12..8c539a0c8 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -62,53 +62,9 @@ const Keys BaseKey = Key_A; const Octaves BaseOctave = DefaultOctave; - class MixerWorkerThread; -// TODO: move to ThreadableJob.h -class ThreadableJob -{ -public: - - enum ProcessingState - { - Unstarted, - Queued, - InProgress, - Done - }; - - ThreadableJob() : - m_state( ThreadableJob::Unstarted ) - { - } - - void reset() - { - m_state = ThreadableJob::Unstarted; - } - - bool process( sampleFrame * _working_buffer ) - { - if( m_state.testAndSetOrdered( Queued, InProgress ) ) - { - doProcessing( _working_buffer ); - m_state = Done; - return true; - } - return false; - } - - virtual bool requiresProcessing() const = 0; - - QAtomicInt m_state; - -private: - virtual void doProcessing( sampleFrame * _working_buffer ) = 0; - -} ; - - +#include "ThreadableJob.h" #include "play_handle.h" @@ -509,148 +465,4 @@ private: } ; - -// TODO: move to MixerWorkerThread.h / MixerWorkerThread.cpp -#include "Cpu.h" -#include "engine.h" - -class MixerWorkerThread : public QThread -{ -public: - struct JobQueue - { -#define JOB_QUEUE_SIZE 1024 - JobQueue() : - queueSize( 0 ), - itemsDone( 0 ) - { - for( int i = 0; i < JOB_QUEUE_SIZE; ++i ) - { - items[i] = NULL; - } - } - - ThreadableJob * items[JOB_QUEUE_SIZE]; - QAtomicInt queueSize; - QAtomicInt itemsDone; - } ; - - static JobQueue s_jobQueue; - - MixerWorkerThread( int _worker_num, mixer * _mixer ) : - QThread( _mixer ), - m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), - m_workerNum( _worker_num ), - m_quit( false ), - m_mixer( _mixer ), - m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) - { - } - - virtual ~MixerWorkerThread() - { - CPU::freeFrames( m_workingBuf ); - } - - virtual void quit() - { - m_quit = true; - } - - void processJobQueue() - { - for( int i = 0; i < s_jobQueue.queueSize; ++i ) - { - // returns true if ThreadableJob was not processed before - if( s_jobQueue.items[i]->process( m_workingBuf ) ) - { - s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); - } - } - } - - static void resetJobQueue() - { - s_jobQueue.queueSize = 0; - s_jobQueue.itemsDone = 0; - } - - template - static void fillJobQueue( const T & _vec ) - { - resetJobQueue(); - for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) - { - addJob( *it ); - } - } - - static void addJob( ThreadableJob * _job ) - { - if( _job->requiresProcessing() ) - { - _job->m_state = ThreadableJob::Queued; - s_jobQueue.items[s_jobQueue.queueSize.fetchAndAddOrdered(1)] = _job; - } - } - - -// define a pause instruction for spinlock-loop - merely useful on -// HyperThreading systems with just one physical core (e.g. Intel Atom) -#ifdef LMMS_HOST_X86 -#define SPINLOCK_PAUSE() asm( "pause" ) -#else -#ifdef LMMS_HOST_X86_64 -#define SPINLOCK_PAUSE() asm( "pause" ) -#else -#define SPINLOCK_PAUSE() -#endif -#endif - - static void startJobs() - { - // TODO: this is dirty! - engine::getMixer()->m_queueReadyWaitCond.wakeAll(); - } - - static void waitForJobs() - { - // TODO: this is dirty! - mixer * m = engine::getMixer(); - m->m_workers[m->m_numWorkers]->processJobQueue(); - while( s_jobQueue.itemsDone < s_jobQueue.queueSize ) - { - startJobs(); - } - } - - static void startAndWaitForJobs() - { - startJobs(); - waitForJobs(); - } - - -private: - virtual void run() - { - QMutex m; - while( m_quit == false ) - { - m.lock(); - m_queueReadyWaitCond->wait( &m ); - processJobQueue(); - m.unlock(); - } - } - - sampleFrame * m_workingBuf; - int m_workerNum; - volatile bool m_quit; - mixer * m_mixer; - QWaitCondition * m_queueReadyWaitCond; - -} ; - - #endif diff --git a/include/play_handle.h b/include/play_handle.h index 1a49fb540..26a8f1058 100644 --- a/include/play_handle.h +++ b/include/play_handle.h @@ -25,6 +25,7 @@ #ifndef _PLAY_HANDLE_H #define _PLAY_HANDLE_H +#include "ThreadableJob.h" #include "mixer.h" class track; diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 1e8a255e6..e936d33ba 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -22,10 +22,10 @@ * */ - #include #include "FxMixer.h" +#include "MixerWorkerThread.h" #include "Cpu.h" #include "Effect.h" #include "song.h" @@ -60,7 +60,7 @@ FxChannel::~FxChannel() -void FxChannel::doProcessing(sampleFrame * _buf) +void FxChannel::doProcessing( sampleFrame * _buf ) { FxMixer * fxm = engine::fxMixer(); const fpp_t fpp = engine::getMixer()->framesPerPeriod(); @@ -97,7 +97,7 @@ void FxChannel::doProcessing(sampleFrame * _buf) const float v = m_volumeModel.value(); m_fxChain.startRunning(); - m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp); + m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp ); m_peakLeft = engine::getMixer()->peakValueLeft( _buf, fpp ) * v; m_peakRight = engine::getMixer()->peakValueRight( _buf, fpp ) * v; } @@ -113,13 +113,13 @@ void FxChannel::doProcessing(sampleFrame * _buf) { // if parent.unstarted and every parent.leaf.done: FxChannel * parent = fxm->effectChannel(m_sends[i]); - if( parent->m_state == ThreadableJob::Unstarted ) + if( parent->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 ) + if( fxm->effectChannel( parent->m_receives[j] )->state() != + ThreadableJob::Done ) { everyLeafDone = false; break; @@ -435,15 +435,6 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) -void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) -{ - m_fxChannels[_ch]->process(_buf); - -} - - - - void FxMixer::prepareMasterMix() { engine::getMixer()->clearAudioBuffer( m_fxChannels[0]->m_buffer, @@ -492,7 +483,7 @@ void FxMixer::masterMix( sampleFrame * _buf ) // to be processed. MixerWorkerThread::resetJobQueue(); addChannelLeaf( 0, _buf ); - while( m_fxChannels[0]->m_state != ThreadableJob::Done ) + while( m_fxChannels[0]->state() != ThreadableJob::Done ) { MixerWorkerThread::startAndWaitForJobs(); } diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp new file mode 100644 index 000000000..539b32245 --- /dev/null +++ b/src/core/MixerWorkerThread.cpp @@ -0,0 +1,138 @@ +/* + * MixerWorkerThread.cpp - implementation of MixerWorkerThread + * + * Copyright (c) 2009 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "MixerWorkerThread.h" +#include "Cpu.h" +#include "engine.h" +#include "mixer.h" + + +MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; + + +MixerWorkerThread::MixerWorkerThread( int _worker_num, mixer * _mixer ) : + QThread( _mixer ), + m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), + m_workerNum( _worker_num ), + m_quit( false ), + m_mixer( _mixer ), + m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) +{ + resetJobQueue(); +} + + + + +MixerWorkerThread::~MixerWorkerThread() +{ + CPU::freeFrames( m_workingBuf ); +} + + + + +void MixerWorkerThread::quit() +{ + m_quit = true; +} + + + + +void MixerWorkerThread::processJobQueue() +{ + while( s_jobQueue.itemsDone != s_jobQueue.queueSize ) + { + for( int i = 0; i < s_jobQueue.queueSize; ++i ) + { + ThreadableJob * job = + s_jobQueue.items[i].fetchAndStoreOrdered( NULL ); + if( job ) + { + job->process( m_workingBuf ); + s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); + } + } + } +} + + + + +void MixerWorkerThread::resetJobQueue() +{ + s_jobQueue.queueSize = 0; + s_jobQueue.itemsDone = 0; +} + + + + +void MixerWorkerThread::addJob( ThreadableJob * _job ) +{ + if( _job->requiresProcessing() ) + { + // update job state + _job->queue(); + // actually queue the job via atomic operations + s_jobQueue.items[s_jobQueue.queueSize.fetchAndAddOrdered(1)] = _job; + } +} + + + + +void MixerWorkerThread::startJobs() +{ + // TODO: this is dirty! + engine::getMixer()->m_queueReadyWaitCond.wakeAll(); +} + + + + +void MixerWorkerThread::waitForJobs() +{ + // TODO: this is dirty! + mixer * m = engine::getMixer(); + m->m_workers[m->m_numWorkers]->processJobQueue(); +} + + + + +void MixerWorkerThread::run() +{ + QMutex m; + while( m_quit == false ) + { + m.lock(); + m_queueReadyWaitCond->wait( &m ); + processJobQueue(); + m.unlock(); + } +} + + diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index f8e0c38b0..dab8ee496 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -26,6 +26,7 @@ #include "mixer.h" #include "FxMixer.h" +#include "MixerWorkerThread.h" #include "play_handle.h" #include "song.h" #include "templates.h" @@ -61,13 +62,6 @@ #endif - -MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; - - - - - mixer::mixer() : m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), m_workingBuf( NULL ), From 05b1325c09d344834d8c243b198871a7e2e95b72 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 19 Oct 2009 01:03:22 +0200 Subject: [PATCH 21/34] Song: do not refresh FxMixerView when loading song in console mode Calling FxMixerView::refreshDisplay causes LMMS to crash when running in console mode. Therefore explicitely check GUI mode before calling this function. --- src/core/song.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/song.cpp b/src/core/song.cpp index 0d5e37c08..6b3ff3cc5 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -912,8 +912,11 @@ void song::loadProject( const QString & _file_name ) { engine::fxMixer()->restoreState( node.toElement() ); - // refresh FxMixerView - engine::fxMixerView()->refreshDisplay(); + if( engine::hasGUI() ) + { + // refresh FxMixerView + engine::fxMixerView()->refreshDisplay(); + } } node = node.nextSibling(); From 4f5d31f862e72103b701515f2b7e40c72b8cf1ac Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 19 Oct 2009 01:04:18 +0200 Subject: [PATCH 22/34] MixerWorkerThread: exit outer loop in processJobQueue() if finished Exit the outer loop of processJobQueue() Only as soon as we went through the job queue without having processed at least one job. In MixerWorkerThread::waitForJobs() re-introduced "pause" instruction on x86 and x86_64 giving better performance on HyperThreading systems. These improvements however still do not solve all performance and multithreading issues. --- src/core/MixerWorkerThread.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp index 539b32245..8941b1a56 100644 --- a/src/core/MixerWorkerThread.cpp +++ b/src/core/MixerWorkerThread.cpp @@ -63,8 +63,10 @@ void MixerWorkerThread::quit() void MixerWorkerThread::processJobQueue() { - while( s_jobQueue.itemsDone != s_jobQueue.queueSize ) + bool processedJob = true; + while( processedJob && (int) s_jobQueue.itemsDone < (int) s_jobQueue.queueSize ) { + processedJob = false; for( int i = 0; i < s_jobQueue.queueSize; ++i ) { ThreadableJob * job = @@ -72,6 +74,7 @@ void MixerWorkerThread::processJobQueue() if( job ) { job->process( m_workingBuf ); + processedJob = true; s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); } } @@ -118,6 +121,13 @@ void MixerWorkerThread::waitForJobs() // TODO: this is dirty! mixer * m = engine::getMixer(); m->m_workers[m->m_numWorkers]->processJobQueue(); + + while( (int) s_jobQueue.itemsDone < (int) s_jobQueue.queueSize ) + { +#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64) + asm( "pause" ); +#endif + } } From ff03ddb8e4902e6bf5fd5c6d2a970dad851dedf1 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 24 Oct 2009 18:59:00 +0200 Subject: [PATCH 23/34] MixerWorkerThread: added job queue modes and improved class structure JobQueues can now operate in JobQueue::Static and JobQueue::Dynamic mode. In static mode it operates the way it always used to while in dynamic mode a changing job queue is supported. This is particularly important for FX mixer sends. There were also heavy improvements regarding the overall structure and functionality of MixerWorkerThread and MixerWorkerThread::JobQueue. There's now a clean distinction between multi-threaded processing and actual (thread-safe) job queue processing. MixerWorkerThread does not need to be a friend class of Mixer anymore. --- include/MixerWorkerThread.h | 80 ++++++++++------ include/mixer.h | 2 - src/core/FxMixer.cpp | 2 +- src/core/MixerWorkerThread.cpp | 167 ++++++++++++++++++--------------- src/core/mixer.cpp | 8 +- 5 files changed, 146 insertions(+), 113 deletions(-) diff --git a/include/MixerWorkerThread.h b/include/MixerWorkerThread.h index 03e195e60..09a54ba92 100644 --- a/include/MixerWorkerThread.h +++ b/include/MixerWorkerThread.h @@ -27,7 +27,6 @@ #include #include -#include #include "mixer.h" @@ -35,61 +34,82 @@ class MixerWorkerThread : public QThread { public: - struct JobQueue + // internal representation of the job queue - all functions are thread-safe + class JobQueue { -#define JOB_QUEUE_SIZE 1024 + public: + enum OperationMode + { + Static, // no jobs added while processing queue + Dynamic // jobs can be added while processing queue + } ; + JobQueue() : - items(), - queueSize( 0 ), - itemsDone( 0 ) + m_items(), + m_queueSize( 0 ), + m_itemsDone( 0 ), + m_opMode( Static ) { } - QAtomicPointer items[JOB_QUEUE_SIZE]; - QAtomicInt queueSize; - QAtomicInt itemsDone; + void reset( OperationMode _opMode ); + + void addJob( ThreadableJob * _job ); + + void run( sampleFrame * _buffer ); + void wait(); + + private: +#define JOB_QUEUE_SIZE 1024 + QAtomicPointer m_items[JOB_QUEUE_SIZE]; + QAtomicInt m_queueSize; + QAtomicInt m_itemsDone; + OperationMode m_opMode; + } ; - static JobQueue s_jobQueue; - MixerWorkerThread( int _worker_num, mixer * _mixer ); + MixerWorkerThread( mixer * _mixer ); virtual ~MixerWorkerThread(); virtual void quit(); - void processJobQueue(); - static void resetJobQueue(); - - template - static void fillJobQueue( const T & _vec ) + static void resetJobQueue( JobQueue::OperationMode _opMode = + JobQueue::Static ) { - resetJobQueue(); + globalJobQueue.reset( _opMode ); + } + + static void addJob( ThreadableJob * _job ) + { + globalJobQueue.addJob( _job ); + } + + // a convenient helper function allowing to pass a container with pointers + // to ThreadableJob objects + template + static void fillJobQueue( const T & _vec, + JobQueue::OperationMode _opMode = JobQueue::Static ) + { + resetJobQueue( _opMode ); for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) { addJob( *it ); } } - static void addJob( ThreadableJob * _job ); - - static void startJobs(); - static void waitForJobs(); - - static void startAndWaitForJobs() - { - startJobs(); - waitForJobs(); - } + static void startAndWaitForJobs(); private: virtual void run(); + static JobQueue globalJobQueue; + static QWaitCondition * queueReadyWaitCond; + static QList workerThreads; + sampleFrame * m_workingBuf; - int m_workerNum; volatile bool m_quit; - mixer * m_mixer; - QWaitCondition * m_queueReadyWaitCond; } ; diff --git a/include/mixer.h b/include/mixer.h index 8c539a0c8..a92b16924 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -433,7 +433,6 @@ private: int m_cpuLoad; QVector m_workers; int m_numWorkers; - QWaitCondition m_queueReadyWaitCond; PlayHandleList m_playHandles; @@ -461,7 +460,6 @@ private: friend class engine; - friend class MixerWorkerThread; } ; diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index e936d33ba..13c7d626a 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -481,7 +481,7 @@ void FxMixer::masterMix( sampleFrame * _buf ) // 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. - MixerWorkerThread::resetJobQueue(); + MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic ); addChannelLeaf( 0, _buf ); while( m_fxChannels[0]->state() != ThreadableJob::Done ) { diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp index 8941b1a56..f9d2e3e95 100644 --- a/src/core/MixerWorkerThread.cpp +++ b/src/core/MixerWorkerThread.cpp @@ -28,17 +28,92 @@ #include "mixer.h" -MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; +MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue; +QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL; +QList MixerWorkerThread::workerThreads; -MixerWorkerThread::MixerWorkerThread( int _worker_num, mixer * _mixer ) : + +// implementation of internal JobQueue +void MixerWorkerThread::JobQueue::reset( OperationMode _opMode ) +{ + m_queueSize = 0; + m_itemsDone = 0; + m_opMode = _opMode; +} + + + + +void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job ) +{ + if( _job->requiresProcessing() ) + { + // update job state + _job->queue(); + // actually queue the job via atomic operations + m_items[m_queueSize.fetchAndAddOrdered(1)] = _job; + } +} + + + +void MixerWorkerThread::JobQueue::run( sampleFrame * _buffer ) +{ + bool processedJob = true; + while( processedJob && (int) m_itemsDone < (int) m_queueSize ) + { + processedJob = false; + for( int i = 0; i < m_queueSize; ++i ) + { + ThreadableJob * job = m_items[i].fetchAndStoreOrdered( NULL ); + if( job ) + { + job->process( _buffer ); + processedJob = true; + m_itemsDone.fetchAndAddOrdered( 1 ); + } + } + // always exit loop if we're not in dynamic mode + processedJob = processedJob && ( m_opMode == Dynamic ); + } +} + + + + +void MixerWorkerThread::JobQueue::wait() +{ + while( (int) m_itemsDone < (int) m_queueSize ) + { +#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64) + asm( "pause" ); +#endif + } +} + + + + + +// implementation of worker threads + +MixerWorkerThread::MixerWorkerThread( mixer * _mixer ) : QThread( _mixer ), m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), - m_workerNum( _worker_num ), - m_quit( false ), - m_mixer( _mixer ), - m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) + m_quit( false ) { + // initialize global static data + if( queueReadyWaitCond == NULL ) + { + queueReadyWaitCond = new QWaitCondition; + } + + // keep track of all instantiated worker threads - this is used for + // processing the last worker thread "inline", see comments in + // MixerWorkerThread::startAndWaitForJobs() for details + workerThreads << this; + resetJobQueue(); } @@ -48,6 +123,8 @@ MixerWorkerThread::MixerWorkerThread( int _worker_num, mixer * _mixer ) : MixerWorkerThread::~MixerWorkerThread() { CPU::freeFrames( m_workingBuf ); + + workerThreads.removeAll( this ); } @@ -56,78 +133,20 @@ MixerWorkerThread::~MixerWorkerThread() void MixerWorkerThread::quit() { m_quit = true; + resetJobQueue(); } -void MixerWorkerThread::processJobQueue() +void MixerWorkerThread::startAndWaitForJobs() { - bool processedJob = true; - while( processedJob && (int) s_jobQueue.itemsDone < (int) s_jobQueue.queueSize ) - { - processedJob = false; - for( int i = 0; i < s_jobQueue.queueSize; ++i ) - { - ThreadableJob * job = - s_jobQueue.items[i].fetchAndStoreOrdered( NULL ); - if( job ) - { - job->process( m_workingBuf ); - processedJob = true; - s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); - } - } - } -} - - - - -void MixerWorkerThread::resetJobQueue() -{ - s_jobQueue.queueSize = 0; - s_jobQueue.itemsDone = 0; -} - - - - -void MixerWorkerThread::addJob( ThreadableJob * _job ) -{ - if( _job->requiresProcessing() ) - { - // update job state - _job->queue(); - // actually queue the job via atomic operations - s_jobQueue.items[s_jobQueue.queueSize.fetchAndAddOrdered(1)] = _job; - } -} - - - - -void MixerWorkerThread::startJobs() -{ - // TODO: this is dirty! - engine::getMixer()->m_queueReadyWaitCond.wakeAll(); -} - - - - -void MixerWorkerThread::waitForJobs() -{ - // TODO: this is dirty! - mixer * m = engine::getMixer(); - m->m_workers[m->m_numWorkers]->processJobQueue(); - - while( (int) s_jobQueue.itemsDone < (int) s_jobQueue.queueSize ) - { -#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64) - asm( "pause" ); -#endif - } + queueReadyWaitCond->wakeAll(); + // The last worker-thread is never started. Instead it's processed "inline" + // i.e. within the global Mixer thread. This way we can reduce latencies + // that otherwise would be caused by synchronizing with another thread. + globalJobQueue.run( workerThreads.last()->m_workingBuf ); + globalJobQueue.wait(); } @@ -139,8 +158,8 @@ void MixerWorkerThread::run() while( m_quit == false ) { m.lock(); - m_queueReadyWaitCond->wait( &m ); - processJobQueue(); + queueReadyWaitCond->wait( &m ); + globalJobQueue.run( m_workingBuf ); m.unlock(); } } diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index dab8ee496..ac6663930 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -72,7 +72,6 @@ mixer::mixer() : m_cpuLoad( 0 ), m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), - m_queueReadyWaitCond(), m_qualitySettings( qualitySettings::Mode_Draft ), m_masterGain( 1.0f ), m_audioDev( NULL ), @@ -130,7 +129,7 @@ mixer::mixer() : for( int i = 0; i < m_numWorkers+1; ++i ) { - MixerWorkerThread * wt = new MixerWorkerThread( i, this ); + MixerWorkerThread * wt = new MixerWorkerThread( this ); if( i < m_numWorkers ) { wt->start( QThread::TimeCriticalPriority ); @@ -148,14 +147,11 @@ mixer::mixer() : mixer::~mixer() { - // distribute an empty job-queue so that worker-threads - // get out of their processing-loop - MixerWorkerThread::s_jobQueue.queueSize = 0; for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->quit(); } - MixerWorkerThread::startJobs(); + MixerWorkerThread::startAndWaitForJobs(); for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->wait( 500 ); From aff378983408481cdbf2f6234a8027a24102b625 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Oct 2009 00:00:37 +0100 Subject: [PATCH 24/34] Cpu: added BufMixCoeff + SSE implementation Added another buffer operation BufMixCoeff allowing to mix a certain buffer to another at a given amount (coeff). CpuX86 has been extended by an according SSE implementation. --- include/Cpu.h | 4 ++++ src/core/Cpu.cpp | 22 ++++++++++++++++++++++ src/core/CpuX86.c | 23 +++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/include/Cpu.h b/include/Cpu.h index f4e956690..b4ad59ba0 100644 --- a/include/Cpu.h +++ b/include/Cpu.h @@ -56,6 +56,9 @@ typedef void (*BufApplyGainFunc)( sampleFrameA * RP _dst, typedef void (*BufMixFunc)( sampleFrameA * RP _dst, const sampleFrameA * RP _src, int _frames ); +typedef void (*BufMixCoeffFunc)( sampleFrameA * RP _dst, + const sampleFrameA * RP _src, + float _coeff, int _frames ); typedef void (*BufMixLRCoeffFunc)( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, @@ -81,6 +84,7 @@ extern MemCpyFunc memCpy; extern MemClearFunc memClear; extern BufApplyGainFunc bufApplyGain; extern BufMixFunc bufMix; +extern BufMixCoeffFunc bufMixCoeff; extern BufMixLRCoeffFunc bufMixLRCoeff; extern UnalignedBufMixLRCoeffFunc unalignedBufMixLRCoeff; extern BufWetDryMixFunc bufWetDryMix; diff --git a/src/core/Cpu.cpp b/src/core/Cpu.cpp index 7e72e5a8f..79f6ab539 100644 --- a/src/core/Cpu.cpp +++ b/src/core/Cpu.cpp @@ -179,6 +179,25 @@ void bufMixNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src, +void bufMixCoeffNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src, + float _coeff, int _frames ) +{ + for( int i = 0; i < _frames; ) + { + _dst[i+0][0] += _src[i+0][0]*_coeff; + _dst[i+0][1] += _src[i+0][1]*_coeff; + _dst[i+1][0] += _src[i+1][0]*_coeff; + _dst[i+1][1] += _src[i+1][1]*_coeff; + _dst[i+2][0] += _src[i+2][0]*_coeff; + _dst[i+2][1] += _src[i+2][1]*_coeff; + _dst[i+3][0] += _src[i+3][0]*_coeff; + _dst[i+3][1] += _src[i+3][1]*_coeff; + i += 4; + } +} + + + void bufMixLRCoeffNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames ) @@ -306,6 +325,7 @@ MemCpyFunc memCpy = memCpyNoOpt; MemClearFunc memClear = memClearNoOpt; BufApplyGainFunc bufApplyGain = bufApplyGainNoOpt; BufMixFunc bufMix = bufMixNoOpt; +BufMixCoeffFunc bufMixCoeff = bufMixCoeffNoOpt; BufMixLRCoeffFunc bufMixLRCoeff = bufMixLRCoeffNoOpt; UnalignedBufMixLRCoeffFunc unalignedBufMixLRCoeff = unalignedBufMixLRCoeffNoOpt; BufWetDryMixFunc bufWetDryMix = bufWetDryMixNoOpt; @@ -337,6 +357,7 @@ void memCpySSE( void * RP _dst, const void * RP _src, int _size ); void memClearSSE( void * RP _dst, int _size ); void bufApplyGainSSE( sampleFrameA * RP _dst, float _gain, int _frames ); void bufMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, int _frames ); +void bufMixCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _coeff, int _frames ); void bufMixLRCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames ); void unalignedBufMixLRCoeffSSE( sampleFrame * RP _dst, const sampleFrame * RP _src, const float _left, const float _right, int _frames ); void bufWetDryMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _wet, float _dry, int _frames ); @@ -447,6 +468,7 @@ void init() memClear = memClearSSE; bufApplyGain = bufApplyGainSSE; bufMix = bufMixSSE; + bufMixCoeff = bufMixCoeffSSE; bufMixLRCoeff = bufMixLRCoeffSSE; unalignedBufMixLRCoeff = unalignedBufMixLRCoeffSSE; bufWetDryMix = bufWetDryMixSSE; diff --git a/src/core/CpuX86.c b/src/core/CpuX86.c index 4c5e7217c..6c9c5a1c0 100644 --- a/src/core/CpuX86.c +++ b/src/core/CpuX86.c @@ -229,6 +229,29 @@ void bufMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, } +void bufMixCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, + float _coeff, int _frames ) +{ + int i; + + PREFETCH_READ(_src); + PREFETCH_WRITE(_dst); + + for( i = 0; i < _frames; ) + { + _dst[i+0][0] += _src[i+0][0]*_coeff; + _dst[i+0][1] += _src[i+0][1]*_coeff; + _dst[i+1][0] += _src[i+1][0]*_coeff; + _dst[i+1][1] += _src[i+1][1]*_coeff; + _dst[i+2][0] += _src[i+2][0]*_coeff; + _dst[i+2][1] += _src[i+2][1]*_coeff; + _dst[i+3][0] += _src[i+3][0]*_coeff; + _dst[i+3][1] += _src[i+3][1]*_coeff; + i += 4; + } +} + + void bufMixLRCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames ) From 2262c0097322fc1eb5e2e6f9d7201f21a7a7005f Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Oct 2009 00:12:06 +0100 Subject: [PATCH 25/34] FxMixer: use new CPU::bufMixCoeff() When mixing FX channel to another use new CPU::bufMixCoeff() function instead of implementing an own (slower) mixing loop. --- src/core/FxMixer.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 13c7d626a..ce43d7196 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -85,13 +85,8 @@ void FxChannel::doProcessing( sampleFrame * _buf ) // 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; - } + CPU::bufMixCoeff( _buf, sender->m_buffer, + sender->m_volumeModel.value() * amt, fpp ); } const float v = m_volumeModel.value(); From 96c8dcbeb41aac58440f46e6f0ad0226aef172cb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 10 Nov 2009 20:22:38 -0700 Subject: [PATCH 26/34] don't show WelcomeScreen when importing/loading WelcomeScreen was incorrectly shown when lmms loaded or imported a project (for example via command line). Fixed. --- include/MainWindow.h | 4 ++-- include/WelcomeScreen.h | 2 +- src/core/main.cpp | 13 ++++++++++--- src/gui/MainWindow.cpp | 24 ++++++++++++------------ src/gui/WelcomeScreen.cpp | 10 +++++----- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 69090d855..5916893fc 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -69,8 +69,6 @@ public: return m_toolBar; } - // show MainWidget or WelcomeScreen - void setMainWidgetVisible( bool _visible ); //int addWidgetToToolBar( QWidget * _w, int _row = -1, int _col = -1 ); //void addSpacingToToolBar( int _size ); @@ -109,6 +107,7 @@ public: void setPlaybackMode( ProjectPlaybackMode _playbackMode ); + void showWelcomeScreen(bool _visible = true); public slots: void resetWindowTitle(); @@ -145,6 +144,7 @@ public slots: protected: virtual void closeEvent( QCloseEvent * _ce ); + virtual void showEvent( QShowEvent * _se ); virtual void focusOutEvent( QFocusEvent * _fe ); virtual void keyPressEvent( QKeyEvent * _ke ); virtual void keyReleaseEvent( QKeyEvent * _ke ); diff --git a/include/WelcomeScreen.h b/include/WelcomeScreen.h index 9f73476b7..d1e38b48a 100644 --- a/include/WelcomeScreen.h +++ b/include/WelcomeScreen.h @@ -53,7 +53,7 @@ private slots: private: - void switchView(); + void hideWelcomeScreen(); Ui::WelcomeScreen * ui; RecentResourceListModel * m_recentProjectsModel; diff --git a/src/core/main.cpp b/src/core/main.cpp index b91c15653..41227025f 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -458,6 +458,9 @@ int main( int argc, char * * argv ) engine::mainWindow()->showMaximized(); } engine::getSong()->loadProject( file_to_load ); + + // don't show welcome screen + engine::mainWindow()->showWelcomeScreen( false ); } else if( !file_to_import.isEmpty() ) { @@ -472,6 +475,9 @@ int main( int argc, char * * argv ) { engine::mainWindow()->showMaximized(); } + + // don't show welcome screen + engine::mainWindow()->showWelcomeScreen( false ); } else { @@ -484,6 +490,9 @@ int main( int argc, char * * argv ) { engine::mainWindow()->showMaximized(); } + + // show welcome screen + engine::mainWindow()->showWelcomeScreen(); } } else @@ -519,9 +528,7 @@ int main( int argc, char * * argv ) } } - const int ret = app->exec(); - delete app; - return( ret ); + return app->exec(); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 8dc2874f5..dfb101be6 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -92,7 +92,6 @@ MainWindow::MainWindow() : vbox->setSpacing( 0 ); vbox->setMargin( 0 ); - QWidget * w = new QWidget( m_mainWidget ); QHBoxLayout * hbox = new QHBoxLayout( w ); hbox->setSpacing( 0 ); @@ -152,17 +151,14 @@ MainWindow::MainWindow() : vbox->addWidget( m_toolBar ); vbox->addWidget( w ); + m_updateTimer.start( 1000 / 20, this ); // 20 fps m_welcomeScreen = new WelcomeScreen( this ); - - setCentralWidget( m_welcomeScreen ); - - m_updateTimer.start( 1000 / 20, this ); // 20 fps + m_welcomeScreen->setVisible( false ); } - MainWindow::~MainWindow() { for( QList::Iterator it = m_tools.begin(); @@ -182,15 +178,14 @@ MainWindow::~MainWindow() - -void MainWindow::setMainWidgetVisible( bool _visible ) +void MainWindow::showWelcomeScreen(bool _visible) { - setCentralWidget( _visible ? m_mainWidget : m_welcomeScreen ); + m_welcomeScreen->setVisible( _visible ); + setCentralWidget( _visible ? m_welcomeScreen : m_mainWidget ); } - void MainWindow::finalize() { resetWindowTitle(); @@ -312,8 +307,7 @@ void MainWindow::finalize() // create the grid layout for the first buttons area QWidget * gridButtons_w = new QWidget( m_toolBar ); - QGridLayout * gridButtons_layout = new QGridLayout( gridButtons_w/*, 2, 1*/ ); - + QGridLayout * gridButtons_layout = new QGridLayout( gridButtons_w ); // create tool-buttons toolButton * project_new = new toolButton( @@ -1197,6 +1191,12 @@ void MainWindow::closeEvent( QCloseEvent * _ce ) } +void MainWindow::showEvent( QShowEvent * _se ) +{ + //showWelcomeScreen( false ); // must explicitly ask for welcome screen + _se->accept(); +} + void MainWindow::focusOutEvent( QFocusEvent * _fe ) diff --git a/src/gui/WelcomeScreen.cpp b/src/gui/WelcomeScreen.cpp index 964988aeb..3b33ed976 100644 --- a/src/gui/WelcomeScreen.cpp +++ b/src/gui/WelcomeScreen.cpp @@ -112,7 +112,7 @@ WelcomeScreen::~WelcomeScreen() void WelcomeScreen::createNewProject() { - switchView(); + hideWelcomeScreen(); } @@ -141,7 +141,7 @@ void WelcomeScreen::instantMidiAction() void WelcomeScreen::openRecentProject( const QModelIndex & _idx ) { - switchView(); + hideWelcomeScreen(); ResourceAction( m_recentProjectsModel->item( _idx ) ).loadProject(); } @@ -156,7 +156,7 @@ void WelcomeScreen::openCommunityResource( const QModelIndex & _idx ) switch( item->type() ) { case ResourceItem::TypeProject: - switchView(); + hideWelcomeScreen(); action.loadProject(); break; default: @@ -177,9 +177,9 @@ void WelcomeScreen::openOnlineResource( QListWidgetItem * _item ) -void WelcomeScreen::switchView() +void WelcomeScreen::hideWelcomeScreen() { - engine::mainWindow()->setMainWidgetVisible( true ); + engine::mainWindow()->showWelcomeScreen( false ); } From f73ccadc17b03de3125854301207ffd2002d0b9d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 10 Nov 2009 21:41:45 -0700 Subject: [PATCH 27/34] auto-saves every minute and recovers upon crash auto-save time is not configurable yet. saves "recover.mmp" to WORKING_DIR every 60 seconds. Deletes recover.mmp on successful close of LMMS. If recover.mmp is found upon start, it loads that project. --- include/MainWindow.h | 4 ++++ src/core/main.cpp | 8 ++++++++ src/gui/MainWindow.cpp | 19 +++++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 5916893fc..3a7a770ed 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -26,6 +26,7 @@ #define _MAIN_WINDOW_H #include +#include #include #include #include @@ -207,6 +208,7 @@ private: QList m_tools; QBasicTimer m_updateTimer; + QTimer m_autoSaveTimer; ResourceBrowser * m_resourceBrowser; @@ -243,6 +245,8 @@ private slots: void playAndRecord(); void stop(); + void autoSave(); + signals: void periodicUpdate(); diff --git a/src/core/main.cpp b/src/core/main.cpp index 41227025f..cb05bbecb 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -23,6 +23,7 @@ */ +#include #include #include #include @@ -449,6 +450,13 @@ int main( int argc, char * * argv ) // srandom() calls in their init procedure srand( getpid() + time( 0 ) ); + // recover a file? + QString recoveryFile = QDir(configManager::inst()->workingDir()).absoluteFilePath("recover.mmp"); + if( QFileInfo(recoveryFile).exists() ) + { + file_to_load = recoveryFile; + } + // we try to load given file if( !file_to_load.isEmpty() ) { diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index dfb101be6..6d92b5ae7 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -83,7 +83,8 @@ MainWindow::MainWindow() : m_workspace( NULL ), m_templatesMenu( NULL ), m_recentlyOpenedProjectsMenu( NULL ), - m_toolsMenu( NULL ) + m_toolsMenu( NULL ), + m_autoSaveTimer( this ) { setAttribute( Qt::WA_DeleteOnClose ); @@ -153,6 +154,10 @@ MainWindow::MainWindow() : m_updateTimer.start( 1000 / 20, this ); // 20 fps + // connect auto save + connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave())); + m_autoSaveTimer.start(1000 * 60); // 1 minute + m_welcomeScreen = new WelcomeScreen( this ); m_welcomeScreen->setVisible( false ); } @@ -1182,6 +1187,9 @@ void MainWindow::closeEvent( QCloseEvent * _ce ) { if( mayChangeProject() ) { + // delete recovery file + QDir working(configManager::inst()->workingDir()); + working.remove("recover.mmp"); _ce->accept(); } else @@ -1387,7 +1395,7 @@ void MainWindow::keyReleaseEvent( QKeyEvent * _ke ) -void MainWindow::timerEvent( QTimerEvent * ) +void MainWindow::timerEvent( QTimerEvent * _te) { emit periodicUpdate(); } @@ -1557,6 +1565,13 @@ void MainWindow::toggleRecordAutomation( bool _recording ) +void MainWindow::autoSave() +{ + QDir work(configManager::inst()->workingDir()); + engine::getSong()->saveProjectAs(work.absoluteFilePath("recover.mmp")); +} + + #include "moc_MainWindow.cxx" /* vim: set tw=0 noexpandtab: */ From 953522f34a7fb751d487abe0b3c05afe8603c36d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 13 Nov 2009 18:43:08 -0700 Subject: [PATCH 28/34] don't change the current project when auto-saving every time auto-save ran, it would change the current project to "recover.mmp". Now it doesn't do this because Song has guiSaveProject(), guiSaveProjectAs(), and saveProjectFile(). (the latter is used for auto-save) --- include/song.h | 7 ++++--- src/core/main.cpp | 2 +- src/core/song.cpp | 22 ++++++++++++++-------- src/gui/MainWindow.cpp | 6 +++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/include/song.h b/include/song.h index 858f577f5..7406bdc22 100644 --- a/include/song.h +++ b/include/song.h @@ -151,9 +151,10 @@ public: // file management void createNewProject(); void createNewProjectFromTemplate( const QString & _template ); - void loadProject( const QString & _file_name ); - bool saveProject(); - bool saveProjectAs( const QString & _file_name ); + void loadProject( const QString & _filename ); + bool guiSaveProject(); + bool guiSaveProjectAs( const QString & _filename ); + bool saveProjectFile( const QString & _filename ); inline const QString & projectFileName() const { return m_fileName; diff --git a/src/core/main.cpp b/src/core/main.cpp index cb05bbecb..96019ce37 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -531,7 +531,7 @@ int main( int argc, char * * argv ) } else { - engine::getSong()->saveProjectAs( file_to_save ); + engine::getSong()->saveProjectFile( file_to_save ); return( 0 ); } } diff --git a/src/core/song.cpp b/src/core/song.cpp index 11392cda8..97c388c41 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -994,10 +994,8 @@ void song::loadProject( const QString & _file_name ) } - - -// save current song -bool song::saveProject() +// only save current song as _filename and do nothing else +bool song::saveProjectFile( const QString & _filename ) { multimediaProject mmp( multimediaProject::SongProject ); @@ -1006,7 +1004,6 @@ bool song::saveProject() m_masterVolumeModel.saveSettings( mmp, mmp.head(), "mastervol" ); m_masterPitchModel.saveSettings( mmp, mmp.head(), "masterpitch" ); - saveState( mmp, mmp.content() ); m_globalAutomationTrack->saveState( mmp, mmp.content() ); @@ -1024,8 +1021,17 @@ bool song::saveProject() saveControllerStates( mmp, mmp.content() ); + return mmp.writeFile( _filename ); +} + + + +// save current song and update the gui +bool song::guiSaveProject() +{ + multimediaProject mmp( multimediaProject::SongProject ); m_fileName = mmp.nameWithExtension( m_fileName ); - if( mmp.writeFile( m_fileName ) == true && engine::hasGUI() ) + if( saveProjectFile( m_fileName ) && engine::hasGUI() ) { textFloat::displayMessage( tr( "Project saved" ), tr( "The project %1 is now saved." @@ -1052,12 +1058,12 @@ bool song::saveProject() // save current song in given filename -bool song::saveProjectAs( const QString & _file_name ) +bool song::guiSaveProjectAs( const QString & _file_name ) { QString o = m_oldFileName; m_oldFileName = m_fileName; m_fileName = _file_name; - if( saveProject() == false ) + if( guiSaveProject() == false ) { m_fileName = m_oldFileName; m_oldFileName = o; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 6d92b5ae7..afbb6eb2d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1007,7 +1007,7 @@ bool MainWindow::saveProject() } else { - engine::getSong()->saveProject(); + engine::getSong()->guiSaveProject(); } return true; } @@ -1036,7 +1036,7 @@ bool MainWindow::saveProjectAs() if( sfd.exec () == QFileDialog::Accepted && !sfd.selectedFiles().isEmpty() && sfd.selectedFiles()[0] != "" ) { - engine::getSong()->saveProjectAs( + engine::getSong()->guiSaveProjectAs( sfd.selectedFiles()[0] ); return true; } @@ -1568,7 +1568,7 @@ void MainWindow::toggleRecordAutomation( bool _recording ) void MainWindow::autoSave() { QDir work(configManager::inst()->workingDir()); - engine::getSong()->saveProjectAs(work.absoluteFilePath("recover.mmp")); + engine::getSong()->saveProjectFile(work.absoluteFilePath("recover.mmp")); } From 20589f19e4e90513befe1c7e102013bcd542deea Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 29 Nov 2009 01:33:25 +0100 Subject: [PATCH 29/34] Mixer: rewrote processing chain of rendered audio buffers Until now, Mixer not only was responsible for rendering audio buffers but also managed writing them to audio backend (through a FIFO) and handled various quality related parameters. All this functionality has been moved into the new AudioOutputContext class. It glues together AudioBackend (formerly called AudioDevice), global quality settings and the Mixer. The AudioOutputContext class creates a FifoWriter which calls Mixer::renderNextBuffer() and writes the output into the BufferFifo of the AudioOutputContext it belongs to. The BufferFifo is read by the according AudioBackend which belongs to the AudioOutputContext as well. The AudioOutputContext also handles resampling in case the AudioBackend wants the buffers in a different samplerate. During this rewrite the Mixer class and the according source files have been renamed from "mixer" to "Mixer". This results in small changes all over LMMS' code base. --- include/AudioAlsa.h | 8 +- include/{AudioDevice.h => AudioBackend.h} | 93 +++-- include/AudioDummy.h | 26 +- include/AudioFileDevice.h | 8 +- include/AudioFileFlac.h | 7 +- include/AudioFileMp3.h | 7 +- include/AudioFileOgg.h | 7 +- include/AudioFileWave.h | 9 +- include/AudioJack.h | 12 +- include/AudioOss.h | 8 +- include/AudioOutputContext.h | 377 ++++++++++++++++++ include/AudioPort.h | 4 +- include/AudioPortAudio.h | 6 +- include/AudioPulseAudio.h | 8 +- include/AudioSampleRecorder.h | 10 +- include/AudioSdl.h | 10 +- include/Controller.h | 20 +- include/Effect.h | 2 +- include/EffectChain.h | 2 +- include/FxMixer.h | 2 +- include/Instrument.h | 2 +- include/InstrumentSoundShaping.h | 2 +- include/{mixer.h => Mixer.h} | 212 ++-------- include/ProjectRenderer.h | 10 +- include/basic_filters.h | 2 +- include/cpuload_widget.h | 2 +- include/engine.h | 11 +- include/fifo_buffer.h | 89 ----- include/note_play_handle.h | 2 +- include/pch.h | 2 +- include/sample_play_handle.h | 2 +- include/sample_record_handle.h | 2 +- include/setup_dialog.h | 4 +- include/surround_area.h | 2 +- include/visualization_widget.h | 2 +- plugins/ladspa_browser/ladspa_description.cpp | 10 +- plugins/ladspa_browser/ladspa_port_dialog.cpp | 10 +- plugins/ladspa_effect/LadspaEffect.cpp | 18 +- .../ladspa_effect/LadspaSubPluginFeatures.cpp | 7 +- plugins/lb302/lb302.h | 1 - plugins/sf2_player/sf2_player.cpp | 25 +- plugins/vibed/vibrating_string.cpp | 4 +- plugins/vst_base/VstPlugin.h | 1 - src/core/Controller.cpp | 2 +- src/core/ControllerConnection.cpp | 1 - src/core/Effect.cpp | 6 +- src/core/EnvelopeAndLfoParameters.cpp | 2 +- src/core/LfoController.cpp | 1 - src/core/{mixer.cpp => Mixer.cpp} | 345 ++++------------ src/core/Oscillator.cpp | 8 +- src/core/PeakController.cpp | 1 - src/core/Plugin.cpp | 1 - src/core/ProjectRenderer.cpp | 116 +++--- src/core/RemotePlugin.cpp | 2 +- src/core/audio/AudioAlsa.cpp | 25 +- src/core/audio/AudioBackend.cpp | 144 +++++++ src/core/audio/AudioDevice.cpp | 208 ---------- src/core/audio/AudioFileDevice.cpp | 4 +- src/core/audio/AudioFileFlac.cpp | 4 +- src/core/audio/AudioFileMp3.cpp | 4 +- src/core/audio/AudioFileOgg.cpp | 4 +- src/core/audio/AudioFileWave.cpp | 6 +- src/core/audio/AudioJack.cpp | 20 +- src/core/audio/AudioOss.cpp | 24 +- src/core/audio/AudioOutputContext.cpp | 311 +++++++++++++++ src/core/audio/AudioPort.cpp | 11 +- src/core/audio/AudioPortAudio.cpp | 4 +- src/core/audio/AudioPulseAudio.cpp | 29 +- src/core/audio/AudioSampleRecorder.cpp | 30 +- src/core/audio/AudioSdl.cpp | 18 +- src/core/engine.cpp | 10 +- src/core/main.cpp | 39 +- src/core/midi/MidiControlListener.cpp | 1 - src/core/midi/MidiController.cpp | 1 - src/core/sample_buffer.cpp | 2 +- src/gui/ExportProjectDialog.cpp | 11 +- src/gui/MainWindow.cpp | 8 +- src/gui/setup_dialog.cpp | 1 - src/gui/song_editor.cpp | 5 +- src/gui/widgets/EnvelopeAndLfoView.cpp | 4 +- src/gui/widgets/InstrumentMidiIOView.cpp | 4 +- src/gui/widgets/cpuload_widget.cpp | 8 +- src/gui/widgets/visualization_widget.cpp | 30 +- src/tracks/bb_track.cpp | 2 +- src/tracks/pattern.cpp | 25 +- 85 files changed, 1379 insertions(+), 1151 deletions(-) rename include/{AudioDevice.h => AudioBackend.h} (60%) create mode 100644 include/AudioOutputContext.h rename include/{mixer.h => Mixer.h} (58%) rename src/core/{mixer.cpp => Mixer.cpp} (76%) create mode 100644 src/core/audio/AudioBackend.cpp delete mode 100644 src/core/audio/AudioDevice.cpp create mode 100644 src/core/audio/AudioOutputContext.cpp diff --git a/include/AudioAlsa.h b/include/AudioAlsa.h index 6e194cf77..a2a645c9f 100644 --- a/include/AudioAlsa.h +++ b/include/AudioAlsa.h @@ -34,17 +34,17 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" class lcdSpinBox; class QComboBox; -class AudioAlsa : public AudioDevice, public QThread +class AudioAlsa : public AudioBackend, public QThread { public: - AudioAlsa( bool & _success_ful, mixer * _mixer ); + AudioAlsa( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioAlsa(); inline static QString name() @@ -56,7 +56,7 @@ public: static QString probeDevice(); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioDevice.h b/include/AudioBackend.h similarity index 60% rename from include/AudioDevice.h rename to include/AudioBackend.h index 660e2ca01..10d17c2da 100644 --- a/include/AudioDevice.h +++ b/include/AudioBackend.h @@ -1,5 +1,5 @@ /* - * AudioDevice.h - base-class for audio-devices, used by LMMS-mixer + * AudioBackend.h - base-class for audio-devices, used by LMMS-mixer * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -26,37 +26,31 @@ #define _AUDIO_DEVICE_H #include -#include #include -#include "mixer.h" +#include "Mixer.h" #include "tab_widget.h" class AudioPort; - -class AudioDevice +/*! \brief The AudioBackend class is the base class for all kinds of AudioBackends. + * + * All classes derived from AudioBackend receive audio data so they can output + * it. + */ +class AudioBackend { public: - AudioDevice( const ch_cnt_t _channels, mixer * _mixer ); - virtual ~AudioDevice(); + /*! \brief Constructs an AudioBackend object for the given AudioOutputContext. */ + AudioBackend( const ch_cnt_t _channels, AudioOutputContext * context ); + virtual ~AudioBackend(); - inline void lock() - { - m_devMutex.lock(); - } - - inline void unlock() - { - m_devMutex.unlock(); - } - - - // if audio-driver supports ports, classes inherting AudioPort - // (e.g. channel-tracks) can register themselves for making - // audio-driver able to collect their individual output and provide - // them at a specific port - currently only supported by JACK + /*! If the audio backend supports ports, classes creating an AudioPort + * (e.g. InstrumentTrack) can register themselves for making + * audio backend able to collect their individual output and provide + * them at a specific port - currently only supported by JACK + */ virtual void registerPort( AudioPort * _port ); virtual void unregisterPort( AudioPort * _port ); virtual void renamePort( AudioPort * _port ); @@ -77,11 +71,14 @@ public: return m_channels; } - void processNextBuffer(); + /*! \brief Fetches one buffer and writes it to output device. + * + * \return Number of frames processed + */ + int processNextBuffer(); virtual void startProcessing() { - m_inProcess = true; } virtual void stopProcessing(); @@ -115,41 +112,47 @@ public: } ; + /*! \brief Returns const pointer to AudioOutputContext this AudioBackend acts for. */ + const AudioOutputContext * outputContext() const + { + return m_context; + } + + /*! \brief Returns const pointer to Mixer this AudioBackend acts for. */ + const Mixer * mixer() const; + protected: - // subclasses can re-implement this for being used in conjunction with - // processNextBuffer() + /*! \brief Writes given buffer to actual device. + * + * Subclasses can reimplement this for being used in conjunction with + * processNextBuffer() + */ virtual void writeBuffer( const sampleFrameA * /* _buf*/, const fpp_t /*_frames*/, const float /*_master_gain*/ ) { } - // called by according driver for fetching new sound-data - fpp_t getNextBuffer( sampleFrameA * _ab ); + /*! \brief Called by according backend for fetching new audio data. */ + int getNextBuffer( sampleFrameA * _ab ); - // clear given signed-int-16-buffer + /*! \brief Clears given signed-int-16-buffer. */ void clearS16Buffer( intSampleFrameA * _outbuf, const fpp_t _frames ); - // resample given buffer from samplerate _src_sr to samplerate _dst_sr - void resample( const sampleFrameA * _src, - const fpp_t _frames, - sampleFrameA * _dst, - const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ); - inline void setSampleRate( const sample_rate_t _new_sr ) { m_sampleRate = _new_sr; } - mixer * getMixer() - { - return m_mixer; - } - bool hqAudio() const; + AudioOutputContext * outputContext() + { + return m_context; + } + + Mixer * mixer(); protected: @@ -157,15 +160,9 @@ protected: private: + AudioOutputContext * m_context; sample_rate_t m_sampleRate; ch_cnt_t m_channels; - mixer * m_mixer; - bool m_inProcess; - - QMutex m_devMutex; - - SRC_DATA m_srcData; - SRC_STATE * m_srcState; sampleFrameA * m_buffer; diff --git a/include/AudioDummy.h b/include/AudioDummy.h index 2a38bd622..324b08c85 100644 --- a/include/AudioDummy.h +++ b/include/AudioDummy.h @@ -25,16 +25,16 @@ #ifndef _AUDIO_DUMMY_H #define _AUDIO_DUMMY_H -#include "AudioDevice.h" +#include "AudioBackend.h" #include "Cpu.h" #include "MicroTimer.h" -class AudioDummy : public AudioDevice, public QThread +class AudioDummy : public AudioBackend, public QThread { public: - AudioDummy( bool & _success_ful, mixer * _mixer ) : - AudioDevice( DEFAULT_CHANNELS, _mixer ) + AudioDummy( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( DEFAULT_CHANNELS, context ) { _success_ful = true; } @@ -50,11 +50,11 @@ public: } - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioDummy::name(), _parent ) + AudioBackend::setupWidget( AudioDummy::name(), _parent ) { } @@ -93,27 +93,25 @@ private: virtual void run() { MicroTimer timer; + sampleFrameA * buf = CPU::allocFrames( mixer()->framesPerPeriod() ); while( true ) { timer.reset(); - surroundSampleFrame * b = - getMixer()->nextBuffer(); - if( !b ) + int frames = getNextBuffer( buf ); + if( frames == 0 ) { break; } - CPU::freeFrames( b ); const Sint32 microseconds = static_cast( - getMixer()->framesPerPeriod() * - 1000000.0f / - getMixer()->processingSampleRate() - - timer.elapsed() ); + mixer()->framesPerPeriod() * 1000000.0f / + mixer()->processingSampleRate() - timer.elapsed() ); if( microseconds > 0 ) { usleep( microseconds ); } } + CPU::freeFrames( buf ); } } ; diff --git a/include/AudioFileDevice.h b/include/AudioFileDevice.h index cbc30b28f..b3ea33968 100644 --- a/include/AudioFileDevice.h +++ b/include/AudioFileDevice.h @@ -28,10 +28,10 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" -class AudioFileDevice : public AudioDevice +class AudioFileDevice : public AudioBackend { public: AudioFileDevice( const sample_rate_t _sample_rate, @@ -41,7 +41,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileDevice(); QString outputFile() const @@ -108,7 +108,7 @@ typedef AudioFileDevice * ( * AudioFileDeviceInstantiaton ) const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * mixer ); #endif diff --git a/include/AudioFileFlac.h b/include/AudioFileFlac.h index dd8ec6643..aaa3b2eeb 100644 --- a/include/AudioFileFlac.h +++ b/include/AudioFileFlac.h @@ -48,7 +48,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileFlac(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -60,13 +60,12 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileFlac( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - _mixer ); + _max_bitrate, _depth, context ); } diff --git a/include/AudioFileMp3.h b/include/AudioFileMp3.h index 7379fa4fa..6b5b6d8ea 100644 --- a/include/AudioFileMp3.h +++ b/include/AudioFileMp3.h @@ -44,7 +44,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileMp3(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -56,13 +56,12 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileMp3( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - _mixer ); + _max_bitrate, _depth, context ); } diff --git a/include/AudioFileOgg.h b/include/AudioFileOgg.h index 8ea9b9d51..363fbf8ab 100644 --- a/include/AudioFileOgg.h +++ b/include/AudioFileOgg.h @@ -47,7 +47,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileOgg(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -59,12 +59,11 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileOgg( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, - _depth, _mixer ); + _min_bitrate, _max_bitrate, _depth, context ); } diff --git a/include/AudioFileWave.h b/include/AudioFileWave.h index 6446e35f6..eabe56d4a 100644 --- a/include/AudioFileWave.h +++ b/include/AudioFileWave.h @@ -1,5 +1,5 @@ /* - * AudioFileWave.h - AudioDevice which encodes wave-stream and writes it + * AudioFileWave.h - AudioBackend which encodes wave-stream and writes it * into a WAVE-file. This is used for song-export. * * Copyright (c) 2004-2009 Tobias Doerffel @@ -44,7 +44,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileWave(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -56,13 +56,12 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileWave( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - _mixer ); + _max_bitrate, _depth, context ); } diff --git a/include/AudioJack.h b/include/AudioJack.h index e9c018a95..86217d1ae 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -31,22 +31,22 @@ #include #endif -#include -#include #include +#include +#include -#include "AudioDevice.h" +#include "AudioBackend.h" class QLineEdit; class lcdSpinBox; -class AudioJack : public QObject, public AudioDevice +class AudioJack : public QObject, public AudioBackend { Q_OBJECT public: - AudioJack( bool & _success_ful, mixer * _mixer ); + AudioJack( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioJack(); inline static QString name() @@ -56,7 +56,7 @@ public: } - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioOss.h b/include/AudioOss.h index 293694fe4..997b60657 100644 --- a/include/AudioOss.h +++ b/include/AudioOss.h @@ -29,17 +29,17 @@ #ifdef LMMS_HAVE_OSS -#include "AudioDevice.h" +#include "AudioBackend.h" class lcdSpinBox; class QLineEdit; -class AudioOss : public AudioDevice, public QThread +class AudioOss : public AudioBackend, public QThread { public: - AudioOss( bool & _success_ful, mixer * _mixer ); + AudioOss( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioOss(); inline static QString name() @@ -50,7 +50,7 @@ public: static QString probeDevice(); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioOutputContext.h b/include/AudioOutputContext.h new file mode 100644 index 000000000..8bd57ac38 --- /dev/null +++ b/include/AudioOutputContext.h @@ -0,0 +1,377 @@ +/* + * AudioOutputContext.h - centralize all audio output related functionality + * + * Copyright (c) 2009 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef _AUDIO_OUTPUT_CONTEXT_H +#define _AUDIO_OUTPUT_CONTEXT_H + +#include +#include + +#include "Mixer.h" + +class AudioBackend; + +/*! \brief The AudioOutputContext class centralizes all functionality + * and data related to output of audio data. + * + * The process of audio output is rather complicated due to different kinds of + * AudioBackend implementations, FIFO buffering and dedicated quality settings. + * The AudioOutputContext class handles all this so the Mixer class can just + * deal with actual audio rendering and processing. + */ +class AudioOutputContext +{ +public: + /*! \brief The QualitySettings class holds quality related settings. + * + * There's nothing special about it. It's just a data aggregration class. + */ + class QualitySettings + { + public: + /*! Lists all quality presets. */ + enum Preset + { + Preset_Draft, /*!< Draft quality - used for editing project */ + Preset_HighQuality, /*!< High quality - standard setting for project export */ + Preset_FinalMix, /*!< Final mix quality - very slow, best quality */ + NumPresets + } ; + + /*! Lists all supported interpolation types. */ + enum Interpolation + { + Interpolation_Linear, /*!< Linear interpolation - fast */ + Interpolation_SincFastest, /*!< Fastest Sinc interpolation - good quality */ + Interpolation_SincMedium, /*!< Medium Sinc interpolation - better quality */ + Interpolation_SincBest /*!< High quality interpolation */ + } ; + + /*! Lists all supported oversampling ratios. */ + enum Oversampling + { + Oversampling_None, /*!< No oversampling - fast */ + Oversampling_2x, /*!< 2x oversampling - good quality */ + Oversampling_4x, /*!< 4x oversampling - better quality */ + Oversampling_8x /*!< 8x oversampling - best quality but might break some filters */ + } ; + + /*! \brief Constructs a QualitySettings object based on a given preset. */ + QualitySettings( Preset m ) + { + switch( m ) + { + case Preset_Draft: + m_interpolation = Interpolation_Linear; + m_oversampling = Oversampling_None; + m_sampleExactControllers = false; + m_aliasFreeOscillators = false; + break; + case Preset_HighQuality: + m_interpolation = Interpolation_SincFastest; + m_oversampling = Oversampling_2x; + m_sampleExactControllers = true; + m_aliasFreeOscillators = false; + break; + case Preset_FinalMix: + m_interpolation = Interpolation_SincBest; + m_oversampling = Oversampling_8x; + m_sampleExactControllers = true; + m_aliasFreeOscillators = true; + break; + default: + break; + } + } + + /*! \brief Constructs a QualitySettings object based on specific quality settings. */ + QualitySettings( Interpolation _i, Oversampling _o, bool _sec, + bool _afo ) : + m_interpolation( _i ), + m_oversampling( _o ), + m_sampleExactControllers( _sec ), + m_aliasFreeOscillators( _afo ) + { + } + + /*! \brief Returns multiplier for sample rate based on oversampling settings. */ + int sampleRateMultiplier() const + { + switch( oversampling() ) + { + case Oversampling_None: return 1; + case Oversampling_2x: return 2; + case Oversampling_4x: return 4; + case Oversampling_8x: return 8; + } + return 1; + } + + /*! \brief Maps interpolation setting to libsamplerate constants. */ + int libsrcInterpolation() const + { + switch( interpolation() ) + { + case Interpolation_Linear: + return SRC_ZERO_ORDER_HOLD; + case Interpolation_SincFastest: + return SRC_SINC_FASTEST; + case Interpolation_SincMedium: + return SRC_SINC_MEDIUM_QUALITY; + case Interpolation_SincBest: + return SRC_SINC_BEST_QUALITY; + } + return SRC_LINEAR; + } + + /*! \brief Returns current interpolation setting. */ + Interpolation interpolation() const + { + return m_interpolation; + } + + /*! \brief Sets a new interpolation method. */ + void setInterpolation( Interpolation interpolation ) + { + m_interpolation = interpolation; + } + + /*! \brief Returns current oversampling setting. */ + Oversampling oversampling() const + { + return m_oversampling; + } + + /*! \brief Sets a new oversampling factor. */ + void setOversampling( Oversampling oversampling ) + { + m_oversampling = oversampling; + } + + /*! \brief Returns whether to use sample exact controllers. */ + bool sampleExactControllers() const + { + return m_sampleExactControllers; + } + + /*! \brief Returns whether to use alias free oscillators. */ + bool aliasFreeOscillators() const + { + return m_aliasFreeOscillators; + } + + private: + Interpolation m_interpolation; + Oversampling m_oversampling; + bool m_sampleExactControllers; + bool m_aliasFreeOscillators; + + } ; + + /*! \brief The BufferFifo class provides an internal FIFO for rendered buffers. + * + * When working with buffer sizes greater than the default buffer size, one + * big output buffer is still splitted into smaller chunks. This is + * especially neccessary for automation which takes place once a buffer + * period. Transitions would be anything else but smooth when adjusting a + * control just 20 times per second. BufferFifo handles the queueing of + * rendered buffers. */ + class BufferFifo + { + public: + /*! Each buffer in the FIFO can have a special state. This is used + * by FifoWriter to inject NULL buffers to indicate, the FIFO has + * been emptied after FifoWriter was told to finish. */ + enum BufferStates + { + Running, /*!< Regular buffer */ + NullBuffer /*!< Even if the buffer returned by currentReadBuffer() + * is not NULL, the FIFO input was NULL. FIFO reader can + * use this information for own purposes. */ + } ; + typedef BufferStates BufferState; + + /*! \brief Constructs a new BufferFifo object. + * + * \param size The number of buffers in the FIFO + * \param bufferSize The size of each buffer in the FIFO + */ + BufferFifo( int size, int bufferSize ); + ~BufferFifo(); + + /*! \brief Pushes a new buffer into the FIFO. + * + * You can also push NULL which will set the according buffer state + * to HasNullBuffer. */ + void write( sampleFrameA * buffer ); + + /*! \brief Prepares for reading next buffer (might block until one is available). */ + void startRead(); + + /*! \brief Returns current front buffer for reading. */ + sampleFrameA * currentReadBuffer() const + { + return m_buffers[m_readerIndex]; + } + + /*! \brief Returns state of current front buffer. */ + BufferState currentReadBufferState() const + { + return m_bufferStates[m_readerIndex]; + } + + /*! \brief Finish the current buffer read operation. + * + * The buffer returned by currentReadBuffer() is not guaranteed to + * be valid anymore after calling this function. */ + void finishRead(); + + /*! \brief Returns whether FIFO is empty. */ + bool isEmpty() const + { + return m_readerSem.available() == false; + } + + + private: + QSemaphore m_readerSem; + QSemaphore m_writerSem; + int m_readerIndex; + int m_writerIndex; + int m_size; + int m_bufferSize; + sampleFrameA * * m_buffers; + BufferState * m_bufferStates; + + } ; + + /*! \brief The FifoWriter class provides an internal thread for feeding + * the FIFO read by the active AudioBackend */ + class FifoWriter : public QThread + { + public: + FifoWriter( AudioOutputContext * context ); + + void finish(); + + + private: + AudioOutputContext * m_context; + volatile bool m_writing; + + virtual void run(); + + } ; + + /*! \brief Constructs an AudioOutputContext object for given AudioBackend. + * + * \param mixer The Mixer instance to fetch audio data from + * \param audioBackend The AudioBackend to write audio data to + * \param qualitySettings A QualitySettings object describing desired quality + */ + AudioOutputContext( Mixer * mixer, + AudioBackend * audioBackend, + const QualitySettings & qualitySettings ); + ~AudioOutputContext(); + + /*! \brief Sets an AudioBackend for this context. */ + void setAudioBackend( AudioBackend * backend ) + { + m_audioBackend = backend; + } + + /*! \brief Returns AudioBackend used by this context. */ + AudioBackend * audioBackend() + { + return m_audioBackend; + } + + /*! \brief Returns const AudioBackend used by this context. */ + const AudioBackend * audioBackend() const + { + return m_audioBackend; + } + + /*! \brief Returns Mixer used by this context. */ + Mixer * mixer() + { + return m_mixer; + } + + /*! \brief Returns const Mixer used by this context. */ + const Mixer * mixer() const + { + return m_mixer; + } + + /*! \brief Returns BufferFifo object used by this context. */ + BufferFifo * fifo() + { + return m_fifo; + } + + /*! \brief Returns current quality settings. */ + const QualitySettings & qualitySettings() const + { + return m_qualitySettings; + } + + /*! \brief Starts audio processing in this context. */ + void startProcessing(); + + /*! \brief Stops audio processing in this context. */ + void stopProcessing(); + + /*! \brief Returns whether audio processing in this context is running. */ + bool isProcessing() const; + + /*! \brief Copies current output buffer to destination buffer and optionally + * does resampling. + * + * If the Mixer has a running FifoWriter, it will make the FifoWriter start + * rendering the next buffer so it can be read from the Fifo next period + * without any delay. If the desired sample rate does not match current + * processing sample rate, resampling will be done. + * \param destBuffer The (aligned) destination buffer + * \param destSampleRate The desired output sample rate */ + int getCurrentOutputBuffer( sampleFrameA * destBuffer, + sample_rate_t destSampleRate ); + + +private: + Mixer * m_mixer; + QualitySettings m_qualitySettings; + AudioBackend * m_audioBackend; + BufferFifo * m_fifo; + FifoWriter * m_fifoWriter; + + // resample data + SRC_DATA m_srcData; + SRC_STATE * m_srcState; + + +} ; + + +#endif diff --git a/include/AudioPort.h b/include/AudioPort.h index 7157f729b..c005a7e6c 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -29,7 +29,7 @@ #include #include -#include "mixer.h" +#include "Mixer.h" class EffectChain; @@ -134,7 +134,7 @@ private: EffectChain * m_effects; - friend class mixer; + friend class Mixer; friend class MixerWorkerThread; } ; diff --git a/include/AudioPortAudio.h b/include/AudioPortAudio.h index a3f968729..3b2083168 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -47,7 +47,7 @@ public: #include -#include "AudioDevice.h" +#include "AudioBackend.h" #if defined paNeverDropInput || defined paNonInterleaved # define PORTAUDIO_V19 @@ -60,7 +60,7 @@ class comboBox; class lcdSpinBox; -class AudioPortAudio : public AudioDevice +class AudioPortAudio : public AudioBackend { public: AudioPortAudio( bool & _success_ful, mixer * _mixer ); @@ -77,7 +77,7 @@ public: unsigned long _framesPerBuffer ); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index 33a11da1f..0a3a9513f 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -31,17 +31,17 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" class lcdSpinBox; class QLineEdit; -class AudioPulseAudio : public AudioDevice, public QThread +class AudioPulseAudio : public AudioBackend, public QThread { public: - AudioPulseAudio( bool & _success_ful, mixer * _mixer ); + AudioPulseAudio( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioPulseAudio(); inline static QString name() @@ -52,7 +52,7 @@ public: static QString probeDevice(); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h index e1e732f80..94a30e180 100644 --- a/include/AudioSampleRecorder.h +++ b/include/AudioSampleRecorder.h @@ -29,16 +29,16 @@ #include #include -#include "AudioDevice.h" +#include "AudioBackend.h" class sampleBuffer; -class AudioSampleRecorder : public AudioDevice +class AudioSampleRecorder : public AudioBackend { public: AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioSampleRecorder(); f_cnt_t framesRecorded() const; @@ -46,11 +46,11 @@ public: private: - virtual void writeBuffer( const surroundSampleFrame * _ab, + virtual void writeBuffer( const sampleFrameA * _ab, const fpp_t _frames, const float _master_gain ); - typedef QList > BufferList; + typedef QList > BufferList; BufferList m_buffers; } ; diff --git a/include/AudioSdl.h b/include/AudioSdl.h index 19260829d..7826fcc69 100644 --- a/include/AudioSdl.h +++ b/include/AudioSdl.h @@ -29,18 +29,20 @@ #ifdef LMMS_HAVE_SDL +#include + #include #include -#include "AudioDevice.h" +#include "AudioBackend.h" class QLineEdit; -class AudioSdl : public AudioDevice +class AudioSdl : public AudioBackend { public: - AudioSdl( bool & _success_ful, mixer * _mixer ); + AudioSdl( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioSdl(); inline static QString name() @@ -50,7 +52,7 @@ public: } - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/Controller.h b/include/Controller.h index 5ca110a53..129c44c18 100644 --- a/include/Controller.h +++ b/include/Controller.h @@ -23,13 +23,13 @@ * */ - #ifndef _CONTROLLER_H #define _CONTROLLER_H #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "Model.h" +//#include "AudioOutputContext.h" #include "JournallingObject.h" class ControllerDialog; @@ -64,9 +64,9 @@ public: inline bool isSampleExact() const { - return m_sampleExact || - engine::getMixer()->currentQualitySettings(). - sampleExactControllers; + return m_sampleExact /*|| + engine::mixer()->audioOutputContext()-> + qualitySettings().sampleExactControllers()*/; } void setSampleExact( bool _exact ) @@ -76,7 +76,7 @@ public: inline ControllerTypes type() const { - return( m_type ); + return m_type; } // return whether this controller updates models frequently - used for @@ -85,17 +85,17 @@ public: { switch( m_type ) { - case LfoController: return( true ); - case PeakController: return( true ); + case LfoController: return true; + case PeakController: return true; default: break; } - return( false ); + return false; } virtual const QString & name() const { - return( m_name ); + return m_name; } diff --git a/include/Effect.h b/include/Effect.h index 4fff6b178..610d4f998 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -28,7 +28,7 @@ #include "Plugin.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "AutomatableModel.h" #include "TempoSyncKnobModel.h" diff --git a/include/EffectChain.h b/include/EffectChain.h index bf949f2bd..b09863896 100644 --- a/include/EffectChain.h +++ b/include/EffectChain.h @@ -28,7 +28,7 @@ #include "Model.h" #include "SerializingObject.h" -#include "mixer.h" +#include "Mixer.h" #include "AutomatableModel.h" class Effect; diff --git a/include/FxMixer.h b/include/FxMixer.h index 2a6bd84e0..9f7654bf5 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -26,7 +26,7 @@ #define _FX_MIXER_H #include "Model.h" -#include "mixer.h" +#include "Mixer.h" #include "EffectChain.h" #include "JournallingObject.h" diff --git a/include/Instrument.h b/include/Instrument.h index 09a60df38..8621dffa5 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -29,7 +29,7 @@ #include #include "Plugin.h" -#include "mixer.h" +#include "Mixer.h" // forward-declarations diff --git a/include/InstrumentSoundShaping.h b/include/InstrumentSoundShaping.h index 398df0688..eae4b9b69 100644 --- a/include/InstrumentSoundShaping.h +++ b/include/InstrumentSoundShaping.h @@ -25,7 +25,7 @@ #ifndef _INSTRUMENT_SOUND_SHAPING_H #define _INSTRUMENT_SOUND_SHAPING_H -#include "mixer.h" +#include "Mixer.h" #include "ComboBoxModel.h" diff --git a/include/mixer.h b/include/Mixer.h similarity index 58% rename from include/mixer.h rename to include/Mixer.h index 3562db3f1..16f5f4d3d 100644 --- a/include/mixer.h +++ b/include/Mixer.h @@ -1,5 +1,5 @@ /* - * mixer.h - audio-device-independent mixer for LMMS + * Mixer.h - Mixer for audio processing and rendering * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -39,19 +39,18 @@ #include -#include #include #include #include "lmms_basics.h" #include "note.h" -#include "fifo_buffer.h" -class AudioDevice; -class MidiClient; +class AudioBackend; +class AudioOutputContext; class AudioPort; +class MidiClient; const fpp_t DEFAULT_BUFFER_SIZE = 256; @@ -67,105 +66,11 @@ const Octaves BaseOctave = DefaultOctave; class MixerWorkerThread; - -class EXPORT mixer : public QObject +/*! \brief The Mixer class is responsible for processing and rendering audio chunks. */ +class EXPORT Mixer : public QObject { Q_OBJECT public: - struct qualitySettings - { - enum Mode - { - Mode_Draft, - Mode_HighQuality, - Mode_FinalMix - } ; - - enum Interpolation - { - Interpolation_Linear, - Interpolation_SincFastest, - Interpolation_SincMedium, - Interpolation_SincBest - } ; - - enum Oversampling - { - Oversampling_None, - Oversampling_2x, - Oversampling_4x, - Oversampling_8x - } ; - - Interpolation interpolation; - Oversampling oversampling; - bool sampleExactControllers; - bool aliasFreeOscillators; - - qualitySettings( Mode _m ) - { - switch( _m ) - { - case Mode_Draft: - interpolation = Interpolation_Linear; - oversampling = Oversampling_None; - sampleExactControllers = false; - aliasFreeOscillators = false; - break; - case Mode_HighQuality: - interpolation = - Interpolation_SincFastest; - oversampling = Oversampling_2x; - sampleExactControllers = true; - aliasFreeOscillators = false; - break; - case Mode_FinalMix: - interpolation = Interpolation_SincBest; - oversampling = Oversampling_8x; - sampleExactControllers = true; - aliasFreeOscillators = true; - break; - } - } - - qualitySettings( Interpolation _i, Oversampling _o, bool _sec, - bool _afo ) : - interpolation( _i ), - oversampling( _o ), - sampleExactControllers( _sec ), - aliasFreeOscillators( _afo ) - { - } - - int sampleRateMultiplier() const - { - switch( oversampling ) - { - case Oversampling_None: return 1; - case Oversampling_2x: return 2; - case Oversampling_4x: return 4; - case Oversampling_8x: return 8; - } - return 1; - } - - int libsrcInterpolation() const - { - switch( interpolation ) - { - case Interpolation_Linear: - return SRC_ZERO_ORDER_HOLD; - case Interpolation_SincFastest: - return SRC_SINC_FASTEST; - case Interpolation_SincMedium: - return SRC_SINC_MEDIUM_QUALITY; - case Interpolation_SincBest: - return SRC_SINC_BEST_QUALITY; - } - return SRC_LINEAR; - } - } ; - void initDevices(); void clear(); @@ -176,16 +81,20 @@ public: return m_audioDevName; } - void setAudioDevice( AudioDevice * _dev ); - void setAudioDevice( AudioDevice * _dev, - const struct qualitySettings & _qs, - bool _needs_fifo ); - void restoreAudioDevice(); - inline AudioDevice * audioDev() + /*! \brief Sets a specific AudioOutputContext to be the active context. */ + void setAudioOutputContext( AudioOutputContext * context ); + const AudioOutputContext * audioOutputContext() const { - return m_audioDev; + return m_audioOutputContext; + } + AudioOutputContext * audioOutputContext() + { + return m_audioOutputContext; + } + AudioOutputContext * defaultAudioOutputContext() + { + return m_defaultAudioOutputContext; } - // audio-port-stuff inline void addAudioPort( AudioPort * _port ) @@ -246,7 +155,7 @@ public: return m_framesPerPeriod; } - inline const surroundSampleFrame * currentReadBuffer() const + inline const sampleFrameA * currentReadBuffer() const { return m_readBuf; } @@ -257,11 +166,6 @@ public: return m_cpuLoad; } - const qualitySettings & currentQualitySettings() const - { - return m_qualitySettings; - } - sample_rate_t baseSampleRate() const; sample_rate_t outputSampleRate() const; @@ -325,11 +229,6 @@ public: static void clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset = 0 ); -#ifndef LMMS_DISABLE_SURROUND - static void clearAudioBuffer( surroundSampleFrame * _ab, - const f_cnt_t _frames, - const f_cnt_t _offset = 0 ); -#endif static float peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ); static float peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ); @@ -337,13 +236,8 @@ public: bool criticalXRuns() const; - inline bool hasFifoWriter() const - { - return m_fifoWriter != NULL; - } - void pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ); - + inline const sampleFrame * inputBuffer() { return m_inputBuffer[ m_inputBufferRead ]; @@ -354,56 +248,27 @@ public: return m_inputBufferFrames[ m_inputBufferRead ]; } - inline surroundSampleFrame * nextBuffer() - { - return hasFifoWriter() ? m_fifo->read() : renderNextBuffer(); - } - - void changeQuality( const struct qualitySettings & _qs ); + /*! \brief Processes and renders next chunk of audio. */ + sampleFrameA * renderNextBuffer(); signals: - void qualitySettingsChanged(); void sampleRateChanged(); void nextAudioBuffer(); private: - typedef fifoBuffer fifo; + Mixer(); + virtual ~Mixer(); - class fifoWriter : public QThread - { - public: - fifoWriter( mixer * _mixer, fifo * _fifo ); - - void finish(); - - - private: - mixer * m_mixer; - fifo * m_fifo; - volatile bool m_writing; - - virtual void run(); - - } ; - - - mixer(); - virtual ~mixer(); - - void startProcessing( bool _needs_fifo = true ); + void startProcessing(); void stopProcessing(); - AudioDevice * tryAudioDevices(); + AudioBackend * tryAudioBackends(); MidiClient * tryMidiClients(); - surroundSampleFrame * renderNextBuffer(); - - - QVector m_audioPorts; fpp_t m_framesPerPeriod; @@ -415,21 +280,21 @@ private: f_cnt_t m_inputBufferSize[2]; int m_inputBufferRead; int m_inputBufferWrite; - - surroundSampleFrame * m_readBuf; - surroundSampleFrame * m_writeBuf; - - QVector m_bufferPool; + + sampleFrameA * m_readBuf; + sampleFrameA * m_writeBuf; + + QVector m_bufferPool; int m_readBuffer; int m_writeBuffer; int m_poolDepth; - surroundSampleFrame m_maxClip; - surroundSampleFrame m_previousSample; + sampleFrame m_maxClip; + sampleFrame m_previousSample; fpp_t m_halfStart[SURROUND_CHANNELS]; bool m_oldBuffer[SURROUND_CHANNELS]; bool m_newBuffer[SURROUND_CHANNELS]; - + int m_cpuLoad; QVector m_workers; int m_numWorkers; @@ -439,12 +304,11 @@ private: PlayHandleList m_playHandles; ConstPlayHandleList m_playHandlesToRemove; - struct qualitySettings m_qualitySettings; float m_masterGain; - AudioDevice * m_audioDev; - AudioDevice * m_oldAudioDev; + AudioOutputContext * m_audioOutputContext; + AudioOutputContext * m_defaultAudioOutputContext; QString m_audioDevName; @@ -456,10 +320,6 @@ private: QMutex m_inputFramesMutex; - fifo * m_fifo; - fifoWriter * m_fifoWriter; - - friend class engine; friend class MixerWorkerThread; diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index f72b55c9c..68bcf1b7c 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -26,6 +26,7 @@ #define _PROJECT_RENDERER_H #include "AudioFileDevice.h" +#include "AudioOutputContext.h" #include "lmmsconfig.h" class QTimer; @@ -71,7 +72,7 @@ public: } ; - ProjectRenderer( const mixer::qualitySettings & _qs, + ProjectRenderer( const AudioOutputContext::QualitySettings & _qs, const OutputSettings & _os, ExportFileFormats _file_format, const QString & _out_file ); @@ -101,12 +102,15 @@ signals: void progressChanged( int ); +private slots: + void finishProcessing(); + + private: virtual void run(); AudioFileDevice * m_fileDev; - mixer::qualitySettings m_qualitySettings; - mixer::qualitySettings m_oldQualitySettings; + AudioOutputContext * m_context; volatile int m_progress; volatile bool m_abort; diff --git a/include/basic_filters.h b/include/basic_filters.h index ee986b0e2..2462978ee 100644 --- a/include/basic_filters.h +++ b/include/basic_filters.h @@ -36,7 +36,7 @@ #include #include "lmms_basics.h" -#include "mixer.h" +#include "Mixer.h" #include "templates.h" #include "lmms_constants.h" diff --git a/include/cpuload_widget.h b/include/cpuload_widget.h index 7a2d27696..4653f300d 100644 --- a/include/cpuload_widget.h +++ b/include/cpuload_widget.h @@ -51,7 +51,7 @@ protected slots: private: - Uint8 m_currentLoad; + int m_currentLoad; QPixmap m_temp; QPixmap m_background; diff --git a/include/engine.h b/include/engine.h index 0f9d471c3..4a17cee6b 100644 --- a/include/engine.h +++ b/include/engine.h @@ -41,7 +41,7 @@ class FxMixer; class FxMixerView; class ProjectJournal; class MainWindow; -class mixer; +class Mixer; class pianoRoll; class projectNotes; class ResourceDB; @@ -75,7 +75,12 @@ public: } // core - static mixer * getMixer() + static Mixer * getMixer() + { + return s_mixer; + } + + static Mixer * mixer() { return s_mixer; } @@ -207,7 +212,7 @@ private: static float s_framesPerTick; // core - static mixer * s_mixer; + static Mixer * s_mixer; static FxMixer * s_fxMixer; static song * s_song; static ResourceDB * s_workingDirResourceDB; diff --git a/include/fifo_buffer.h b/include/fifo_buffer.h index 4bf908611..e69de29bb 100644 --- a/include/fifo_buffer.h +++ b/include/fifo_buffer.h @@ -1,89 +0,0 @@ -/* - * fifo_buffer.h - FIFO fixed-size buffer - * - * Copyright (c) 2007 Javier Serrano Polo - * Copyright (c) 2008 Tobias Doerffel - * - * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef _FIFO_BUFFER_H -#define _FIFO_BUFFER_H - -#include - - -template -class fifoBuffer -{ -public: - fifoBuffer( int _size ) : - m_readerSem( _size ), - m_writerSem( _size ), - m_readerIndex( 0 ), - m_writerIndex( 0 ), - m_size( _size ) - { - m_buffer = new T[_size]; - m_readerSem.acquire( _size ); - } - - ~fifoBuffer() - { - delete[] m_buffer; - m_readerSem.release( m_size ); - } - - void write( T _element ) - { - m_writerSem.acquire(); - m_buffer[m_writerIndex++] = _element; - m_writerIndex %= m_size; - m_readerSem.release(); - } - - T read() - { - m_readerSem.acquire(); - T element = m_buffer[m_readerIndex++]; - m_readerIndex %= m_size; - m_writerSem.release(); - return element; - } - - bool available() - { - return m_readerSem.available(); - } - - -private: - QSemaphore m_readerSem; - QSemaphore m_writerSem; - int m_readerIndex; - int m_writerIndex; - int m_size; - T * m_buffer; - -} ; - - - - -#endif diff --git a/include/note_play_handle.h b/include/note_play_handle.h index 3341058dc..40b0c1d84 100644 --- a/include/note_play_handle.h +++ b/include/note_play_handle.h @@ -28,7 +28,7 @@ #define _NOTE_PLAY_HANDLE_H #include "lmmsconfig.h" -#include "mixer.h" +#include "Mixer.h" #include "note.h" #include "engine.h" #include "track.h" diff --git a/include/pch.h b/include/pch.h index 67aa73033..aa334c07d 100644 --- a/include/pch.h +++ b/include/pch.h @@ -31,6 +31,6 @@ #include -#include "mixer.h" +#include "Mixer.h" #endif diff --git a/include/sample_play_handle.h b/include/sample_play_handle.h index 2c1161737..2b590e892 100644 --- a/include/sample_play_handle.h +++ b/include/sample_play_handle.h @@ -26,7 +26,7 @@ #ifndef _SAMPLE_PLAY_HANDLE_H #define _SAMPLE_PLAY_HANDLE_H -#include "mixer.h" +#include "Mixer.h" #include "sample_buffer.h" #include "AutomatableModel.h" diff --git a/include/sample_record_handle.h b/include/sample_record_handle.h index f07cfd350..5afeb08f8 100644 --- a/include/sample_record_handle.h +++ b/include/sample_record_handle.h @@ -30,7 +30,7 @@ #include #include -#include "mixer.h" +#include "Mixer.h" #include "sample_buffer.h" class bbTrack; diff --git a/include/setup_dialog.h b/include/setup_dialog.h index 6f0dcb0ab..d0cd46b6b 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -29,7 +29,7 @@ #include #include "lmmsconfig.h" -#include "AudioDevice.h" +#include "AudioBackend.h" #include "MidiClient.h" #include "MidiPort.h" #include "MidiPortMenu.h" @@ -171,7 +171,7 @@ private: bool m_disableChActInd; bool m_manualChPiano; - typedef QMap AswMap; + typedef QMap AswMap; typedef QMap MswMap; typedef QMap trMap; diff --git a/include/surround_area.h b/include/surround_area.h index 6e1774728..986a1de49 100644 --- a/include/surround_area.h +++ b/include/surround_area.h @@ -31,7 +31,7 @@ #include #include "AutomatableModel.h" -#include "mixer.h" +#include "Mixer.h" class QPixmap; diff --git a/include/visualization_widget.h b/include/visualization_widget.h index 863a0ecca..8b8c3fd16 100644 --- a/include/visualization_widget.h +++ b/include/visualization_widget.h @@ -29,7 +29,7 @@ #include #include -#include "mixer.h" +#include "Mixer.h" class visualizationWidget : public QWidget diff --git a/plugins/ladspa_browser/ladspa_description.cpp b/plugins/ladspa_browser/ladspa_description.cpp index 209d0b89a..004a9996d 100644 --- a/plugins/ladspa_browser/ladspa_description.cpp +++ b/plugins/ladspa_browser/ladspa_description.cpp @@ -2,6 +2,7 @@ * ladspa_description.cpp - LADSPA plugin description * * Copyright (c) 2007 Javier Serrano Polo + * Copyright (c) 2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -30,10 +31,11 @@ #include #include -#include "AudioDevice.h" +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "engine.h" #include "ladspa_2_lmms.h" -#include "mixer.h" +#include "Mixer.h" @@ -73,8 +75,8 @@ ladspaDescription::ladspaDescription( QWidget * _parent, it != plugins.end(); it++ ) { if( _type != VALID || - manager->getDescription( ( *it ).second )->inputChannels - <= engine::getMixer()->audioDev()->channels() ) + manager->getDescription( ( *it ).second )->inputChannels <= + engine::mixer()->audioOutputContext()->audioBackend()->channels() ) { pluginNames.push_back( ( *it ).first ); m_pluginKeys.push_back( ( *it ).second ); diff --git a/plugins/ladspa_browser/ladspa_port_dialog.cpp b/plugins/ladspa_browser/ladspa_port_dialog.cpp index 3f324dbf4..0f2db8c28 100644 --- a/plugins/ladspa_browser/ladspa_port_dialog.cpp +++ b/plugins/ladspa_browser/ladspa_port_dialog.cpp @@ -2,7 +2,8 @@ * ladspa_port_dialog.cpp - dialog to test a LADSPA plugin * * Copyright (c) 2006-2008 Danny McRae - * + * Copyright (c) 2009 Tobias Doerffel + * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or @@ -22,7 +23,6 @@ * */ - #include "ladspa_port_dialog.h" #include @@ -31,7 +31,7 @@ #include "embed.h" #include "engine.h" #include "ladspa_2_lmms.h" -#include "mixer.h" +#include "Mixer.h" ladspaPortDialog::ladspaPortDialog( const ladspa_key_t & _key ) @@ -95,11 +95,11 @@ ladspaPortDialog::ladspaPortDialog( const ladspa_key_t & _key ) { if( min != NOHINT ) { - min *= engine::getMixer()->processingSampleRate(); + min *= engine::mixer()->processingSampleRate(); } if( max != NOHINT ) { - max *= engine::getMixer()->processingSampleRate(); + max *= engine::mixer()->processingSampleRate(); } } diff --git a/plugins/ladspa_effect/LadspaEffect.cpp b/plugins/ladspa_effect/LadspaEffect.cpp index ea5d53e99..5dbff0ff6 100644 --- a/plugins/ladspa_effect/LadspaEffect.cpp +++ b/plugins/ladspa_effect/LadspaEffect.cpp @@ -25,14 +25,15 @@ #include +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "LadspaEffect.h" #include "mmp.h" -#include "AudioDevice.h" #include "config_mgr.h" #include "ladspa_2_lmms.h" #include "LadspaControl.h" #include "LadspaSubPluginFeatures.h" -#include "mixer.h" +#include "Mixer.h" #include "EffectChain.h" #include "Cpu.h" #include "automation_pattern.h" @@ -87,7 +88,7 @@ LadspaEffect::LadspaEffect( Model * _parent, pluginInstantiation(); - connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( changeSampleRate() ) ); } @@ -144,13 +145,13 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, int frames = _frames; sampleFrame * o_buf = NULL; - if( m_maxSampleRate < engine::getMixer()->processingSampleRate() ) + if( m_maxSampleRate < engine::mixer()->processingSampleRate() ) { o_buf = _buf; _buf = CPU::allocFrames( _frames ); sampleDown( o_buf, _buf, m_maxSampleRate ); frames = _frames * m_maxSampleRate / - engine::getMixer()->processingSampleRate(); + engine::mixer()->processingSampleRate(); } // Copy the LMMS audio buffer to the LADSPA input buffer and initialize @@ -298,7 +299,8 @@ void LadspaEffect::pluginInstantiation() ladspa2LMMS * manager = engine::getLADSPAManager(); // Calculate how many processing units are needed. - const ch_cnt_t lmms_chnls = engine::getMixer()->audioDev()->channels(); + const ch_cnt_t lmms_chnls = engine::mixer()->audioOutputContext()-> + audioBackend()->channels(); int effect_channels = manager->getDescription( m_key )->inputChannels; setProcessorCount( lmms_chnls / effect_channels ); @@ -325,7 +327,7 @@ void LadspaEffect::pluginInstantiation() // during cleanup. It was easier to troubleshoot with the // memory management all taking place in one file. p->buffer = - new LADSPA_Data[engine::getMixer()->framesPerPeriod()]; + new LADSPA_Data[engine::mixer()->framesPerPeriod()]; if( p->name.toUpper().contains( "IN" ) && manager->isPortInput( m_key, port ) ) @@ -566,7 +568,7 @@ sample_rate_t LadspaEffect::maxSamplerate( const QString & _name ) { return __buggy_plugins[_name]; } - return engine::getMixer()->processingSampleRate(); + return engine::mixer()->processingSampleRate(); } diff --git a/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp b/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp index bb6cba447..35d49749e 100644 --- a/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp +++ b/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp @@ -28,12 +28,13 @@ #include #include +#include "AudioOutputContext.h" +#include "AudioBackend.h" #include "LadspaSubPluginFeatures.h" -#include "AudioDevice.h" #include "engine.h" #include "ladspa_2_lmms.h" #include "LadspaBase.h" -#include "mixer.h" +#include "Mixer.h" LadspaSubPluginFeatures::LadspaSubPluginFeatures( Plugin::PluginTypes _type ) : @@ -142,7 +143,7 @@ void LadspaSubPluginFeatures::listSubPluginKeys( it != plugins.end(); ++it ) { if( lm->getDescription( ( *it ).second )->inputChannels <= - engine::getMixer()->audioDev()->channels() ) + engine::mixer()->audioOutputContext()->audioBackend()->channels() ) { _kl.push_back( ladspaKeyToSubPluginKey( _desc, ( *it ).first, ( *it ).second ) ); } diff --git a/plugins/lb302/lb302.h b/plugins/lb302/lb302.h index 6094c4c53..7d4422769 100644 --- a/plugins/lb302/lb302.h +++ b/plugins/lb302/lb302.h @@ -37,7 +37,6 @@ #include "InstrumentView.h" #include "led_checkbox.h" #include "knob.h" -#include "mixer.h" class lb302SynthView; class notePlayHandle; diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index a93a125fd..56479236d 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -30,6 +30,8 @@ #include #include +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "ResourceFileMapper.h" #include "sf2_player.h" #include "engine.h" @@ -122,14 +124,14 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : m_settings = new_fluid_settings(); fluid_settings_setint( m_settings, (char *) "audio.period-size", - engine::getMixer()->framesPerPeriod() ); + engine::mixer()->framesPerPeriod() ); // This is just our starting instance of synth. It is recreated // everytime we load a new soundfont. m_synth = new_fluid_synth( m_settings ); InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); - engine::getMixer()->addPlayHandle( iph ); + engine::mixer()->addPlayHandle( iph ); //loadFile( configManager::inst()->defaultSoundfont() ); @@ -147,7 +149,7 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : connect( &m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) ); - connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); // Gain @@ -192,7 +194,7 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : sf2Instrument::~sf2Instrument() { - engine::getMixer()->removePlayHandles( instrumentTrack() ); + engine::mixer()->removePlayHandles( instrumentTrack() ); freeFont(); delete_fluid_synth( m_synth ); delete_fluid_settings( m_settings ); @@ -498,7 +500,7 @@ void sf2Instrument::updateSampleRate() // Set & get, returns the true sample rate fluid_settings_setnum( m_settings, (char *) "synth.sample-rate", - engine::getMixer()->processingSampleRate() ); + engine::mixer()->processingSampleRate() ); fluid_settings_getnum( m_settings, (char *) "synth.sample-rate", &tempRate ); m_internalSampleRate = static_cast( tempRate ); @@ -529,8 +531,9 @@ void sf2Instrument::updateSampleRate() } m_synthMutex.lock(); - if( engine::getMixer()->currentQualitySettings().interpolation >= - mixer::qualitySettings::Interpolation_SincFastest ) + if( engine::mixer()->audioOutputContext()->qualitySettings(). + interpolation() >= + AudioOutputContext::QualitySettings::Interpolation_SincFastest ) { fluid_synth_set_interp_method( m_synth, -1, FLUID_INTERP_7THORDER ); @@ -541,7 +544,7 @@ void sf2Instrument::updateSampleRate() FLUID_INTERP_DEFAULT ); } m_synthMutex.unlock(); - if( m_internalSampleRate < engine::getMixer()->processingSampleRate() ) + if( m_internalSampleRate < engine::mixer()->processingSampleRate() ) { m_synthMutex.lock(); if( m_srcState != NULL ) @@ -549,9 +552,9 @@ void sf2Instrument::updateSampleRate() src_delete( m_srcState ); } int error; - m_srcState = src_new( engine::getMixer()-> - currentQualitySettings().libsrcInterpolation(), - DEFAULT_CHANNELS, &error ); + m_srcState = src_new( engine::mixer()->audioOutputContext()-> + qualitySettings().libsrcInterpolation(), + DEFAULT_CHANNELS, &error ); if( m_srcState == NULL || error ) { printf( "error while creating SRC-data-" diff --git a/plugins/vibed/vibrating_string.cpp b/plugins/vibed/vibrating_string.cpp index cdffd2caf..fc91a9036 100644 --- a/plugins/vibed/vibrating_string.cpp +++ b/plugins/vibed/vibrating_string.cpp @@ -26,7 +26,7 @@ #include "vibrating_string.h" #include "templates.h" #include "interpolation.h" -#include "mixer.h" +#include "Mixer.h" #include "engine.h" @@ -42,7 +42,7 @@ vibratingString::vibratingString( float _pitch, float _detune, bool _state ) : m_oversample( 2 * _oversample / (int)( _sample_rate / - engine::getMixer()->baseSampleRate() ) ), + engine::mixer()->baseSampleRate() ) ), m_randomize( _randomize ), m_stringLoss( 1.0f - _string_loss ), m_state( 0.1f ) diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index c365b9253..6d0fb7b10 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -29,7 +29,6 @@ #include #include -#include "mixer.h" #include "JournallingObject.h" #include "communication.h" diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index f65d9bee5..6bcde8f35 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -30,7 +30,7 @@ #include "song.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "Controller.h" #include "ControllerConnection.h" #include "ControllerDialog.h" diff --git a/src/core/ControllerConnection.cpp b/src/core/ControllerConnection.cpp index 0b346fd83..31c4ee4c5 100644 --- a/src/core/ControllerConnection.cpp +++ b/src/core/ControllerConnection.cpp @@ -30,7 +30,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "ControllerConnection.h" diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index f848aacea..ebd595bbf 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -23,11 +23,11 @@ * */ - #include #include +#include "AudioOutputContext.h" #include "Effect.h" #include "engine.h" #include "DummyEffect.h" @@ -168,8 +168,8 @@ void Effect::reinitSRC() } int error; if( ( m_srcState[i] = src_new( - engine::getMixer()->currentQualitySettings(). - libsrcInterpolation(), + engine::mixer()->audioOutputContext()-> + qualitySettings().libsrcInterpolation(), DEFAULT_CHANNELS, &error ) ) == NULL ) { fprintf( stderr, "Error: src_new() failed in effect.cpp!\n" ); diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index dbf3c37d4..28209cb06 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -27,7 +27,7 @@ #include "EnvelopeAndLfoParameters.h" #include "debug.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "mmp.h" #include "Oscillator.h" diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 94d6cc687..696a11b73 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -31,7 +31,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "LfoController.h" #include "ControllerDialog.h" diff --git a/src/core/mixer.cpp b/src/core/Mixer.cpp similarity index 76% rename from src/core/mixer.cpp rename to src/core/Mixer.cpp index b82d6886f..14313505b 100644 --- a/src/core/mixer.cpp +++ b/src/core/Mixer.cpp @@ -1,5 +1,5 @@ /* - * mixer.cpp - audio-device-independent mixer for LMMS + * Mixer.cpp - Mixer for audio processing and rendering * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -24,7 +24,8 @@ #include -#include "mixer.h" +#include "AudioOutputContext.h" +#include "Mixer.h" #include "FxMixer.h" #include "play_handle.h" #include "song.h" @@ -117,7 +118,7 @@ public: static JobQueue s_jobQueue; - MixerWorkerThread( int _worker_num, mixer * _mixer ) : + MixerWorkerThread( int _worker_num, Mixer * _mixer ) : QThread( _mixer ), m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), m_workerNum( _worker_num ), @@ -166,7 +167,7 @@ private: sampleFrame * m_workingBuf; int m_workerNum; volatile bool m_quit; - mixer * m_mixer; + Mixer * m_mixer; QWaitCondition * m_queueReadyWaitCond; } ; @@ -268,8 +269,11 @@ void MixerWorkerThread::processJobQueue() -mixer::mixer() : - m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), + +Mixer::Mixer() : + m_framesPerPeriod( qBound( 32, + configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt(), + DEFAULT_BUFFER_SIZE ) ), m_workingBuf( NULL ), m_inputBufferRead( 0 ), m_inputBufferWrite( 1 ), @@ -279,17 +283,16 @@ mixer::mixer() : m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), m_queueReadyWaitCond(), - m_qualitySettings( qualitySettings::Mode_Draft ), m_masterGain( 1.0f ), - m_audioDev( NULL ), - m_oldAudioDev( NULL ), + m_audioOutputContext( NULL ), + m_defaultAudioOutputContext( NULL ), m_globalMutex( QMutex::Recursive ) { for( int i = 0; i < 2; ++i ) { m_inputBufferFrames[i] = 0; m_inputBufferSize[i] = DEFAULT_BUFFER_SIZE * 100; - m_inputBuffer[i] = CPU::allocFrames( + m_inputBuffer[i] = CPU::allocFrames( DEFAULT_BUFFER_SIZE * 100 ); clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] ); } @@ -299,38 +302,6 @@ mixer::mixer() : __fx_channel_jobs[i-1] = (fx_ch_t) i; } - // just rendering? - if( !engine::hasGUI() ) - { - m_framesPerPeriod = DEFAULT_BUFFER_SIZE; - m_fifo = new fifo( 1 ); - } - else if( configManager::inst()->value( "mixer", "framesperaudiobuffer" - ).toInt() >= 32 ) - { - m_framesPerPeriod = - (fpp_t) configManager::inst()->value( "mixer", - "framesperaudiobuffer" ).toInt(); - - if( m_framesPerPeriod > DEFAULT_BUFFER_SIZE ) - { - m_fifo = new fifo( m_framesPerPeriod - / DEFAULT_BUFFER_SIZE ); - m_framesPerPeriod = DEFAULT_BUFFER_SIZE; - } - else - { - m_fifo = new fifo( 1 ); - } - } - else - { - configManager::inst()->setValue( "mixer", - "framesperaudiobuffer", - QString::number( m_framesPerPeriod ) ); - m_fifo = new fifo( 1 ); - } - m_workingBuf = CPU::allocFrames( m_framesPerPeriod ); for( Uint8 i = 0; i < 3; i++ ) { @@ -352,12 +323,17 @@ mixer::mixer() : m_poolDepth = 2; m_readBuffer = 0; m_writeBuffer = 1; + + // initialize default AudioOutputContext + m_defaultAudioOutputContext = new AudioOutputContext( this, NULL, + AudioOutputContext::QualitySettings::Preset_Draft ); + m_audioOutputContext = m_defaultAudioOutputContext; } -mixer::~mixer() +Mixer::~Mixer() { // distribute an empty job-queue so that worker-threads // get out of their processing-loop @@ -372,13 +348,7 @@ mixer::~mixer() m_workers[w]->wait( 500 ); } - while( m_fifo->available() ) - { - delete[] m_fifo->read(); - } - delete m_fifo; - - delete m_audioDev; + delete m_audioOutputContext; delete m_midiClient; for( Uint8 i = 0; i < 3; i++ ) @@ -392,54 +362,54 @@ mixer::~mixer() -void mixer::initDevices() +void Mixer::initDevices() { - m_audioDev = tryAudioDevices(); + audioOutputContext()->setAudioBackend( tryAudioBackends() ); m_midiClient = tryMidiClients(); } -void mixer::startProcessing( bool _needs_fifo ) +void Mixer::setAudioOutputContext( AudioOutputContext * context ) { - if( _needs_fifo ) - { - m_fifoWriter = new fifoWriter( this, m_fifo ); - m_fifoWriter->start( QThread::HighPriority ); - } - else - { - m_fifoWriter = NULL; - } + stopProcessing(); - m_audioDev->startProcessing(); + m_audioOutputContext = context; + + //m_audioDev->applyQualitySettings(); + + emit sampleRateChanged(); + + startProcessing(); } -void mixer::stopProcessing() +void Mixer::startProcessing() { - if( m_fifoWriter != NULL ) + if( m_audioOutputContext ) { - m_fifoWriter->finish(); - m_audioDev->stopProcessing(); - m_fifoWriter->wait( 1000 ); - m_fifoWriter->terminate(); - delete m_fifoWriter; - m_fifoWriter = NULL; - } - else - { - m_audioDev->stopProcessing(); + m_audioOutputContext->startProcessing(); } } -sample_rate_t mixer::baseSampleRate() const +void Mixer::stopProcessing() +{ + if( m_audioOutputContext ) + { + m_audioOutputContext->stopProcessing(); + } +} + + + + +sample_rate_t Mixer::baseSampleRate() const { sample_rate_t sr = configManager::inst()->value( "mixer", "samplerate" ).toInt(); @@ -453,33 +423,36 @@ sample_rate_t mixer::baseSampleRate() const -sample_rate_t mixer::outputSampleRate() const +sample_rate_t Mixer::outputSampleRate() const { - return m_audioDev != NULL ? m_audioDev->sampleRate() : - baseSampleRate(); + if( audioOutputContext()->audioBackend() ) + { + return audioOutputContext()->audioBackend()->sampleRate(); + } + return baseSampleRate(); } -sample_rate_t mixer::inputSampleRate() const +sample_rate_t Mixer::inputSampleRate() const { - return m_audioDev != NULL ? m_audioDev->sampleRate() : - baseSampleRate(); + return outputSampleRate(); } -sample_rate_t mixer::processingSampleRate() const +sample_rate_t Mixer::processingSampleRate() const { - return outputSampleRate() * m_qualitySettings.sampleRateMultiplier(); + return outputSampleRate() * + audioOutputContext()->qualitySettings().sampleRateMultiplier(); } -bool mixer::criticalXRuns() const +bool Mixer::criticalXRuns() const { return m_cpuLoad >= 99 && engine::getSong()->realTimeTask() == true; } @@ -487,14 +460,14 @@ bool mixer::criticalXRuns() const -void mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) +void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) { lockInputFrames(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; int size = m_inputBufferSize[ m_inputBufferWrite ]; sampleFrame * buf = m_inputBuffer[ m_inputBufferWrite ]; - + if( frames + _frames > size ) { size = qMax( size * 2, frames + _frames ); @@ -507,17 +480,17 @@ void mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) buf = ab; } - + CPU::memCpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); m_inputBufferFrames[ m_inputBufferWrite ] += _frames; - + unlockInputFrames(); } -sampleFrameA * mixer::renderNextBuffer() +sampleFrameA * Mixer::renderNextBuffer() { MicroTimer timer; static song::playPos last_metro_pos = -1; @@ -649,7 +622,7 @@ sampleFrameA * mixer::renderNextBuffer() // removes all play-handles. this is neccessary, when the song is stopped -> // all remaining notes etc. would be played until their end -void mixer::clear() +void Mixer::clear() { // TODO: m_midiClient->noteOffAll(); lock(); @@ -669,7 +642,7 @@ void mixer::clear() -void mixer::bufferToPort( const sampleFrame * _buf, +void Mixer::bufferToPort( const sampleFrame * _buf, const fpp_t _frames, const f_cnt_t _offset, stereoVolumeVector _vv, @@ -709,7 +682,7 @@ void mixer::bufferToPort( const sampleFrame * _buf, -void mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, +void Mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { if( likely( (size_t)( _ab+_offset ) % 16 == 0 && _frames % 8 == 0 ) ) @@ -724,18 +697,8 @@ void mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, -#ifndef LMMS_DISABLE_SURROUND -void mixer::clearAudioBuffer( surroundSampleFrame * _ab, const f_cnt_t _frames, - const f_cnt_t _offset ) -{ - memset( _ab+_offset, 0, sizeof( *_ab ) * _frames ); -} -#endif - - - -float mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) +float Mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) @@ -755,7 +718,7 @@ float mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) -float mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) +float Mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) @@ -775,97 +738,7 @@ float mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) -void mixer::changeQuality( const struct qualitySettings & _qs ) -{ - // don't delete the audio-device - stopProcessing(); - - m_qualitySettings = _qs; - m_audioDev->applyQualitySettings(); - - emit sampleRateChanged(); - emit qualitySettingsChanged(); - - startProcessing(); -} - - - - -void mixer::setAudioDevice( AudioDevice * _dev ) -{ - stopProcessing(); - - m_oldAudioDev = m_audioDev; - - if( _dev == NULL ) - { - printf( "param _dev == NULL in mixer::setAudioDevice(...). " - "Trying any working audio-device\n" ); - m_audioDev = tryAudioDevices(); - } - else - { - m_audioDev = _dev; - } - - emit sampleRateChanged(); - - startProcessing(); -} - - - - -void mixer::setAudioDevice( AudioDevice * _dev, - const struct qualitySettings & _qs, - bool _needs_fifo ) -{ - // don't delete the audio-device - stopProcessing(); - - m_qualitySettings = _qs; - m_oldAudioDev = m_audioDev; - - if( _dev == NULL ) - { - printf( "param _dev == NULL in mixer::setAudioDevice(...). " - "Trying any working audio-device\n" ); - m_audioDev = tryAudioDevices(); - } - else - { - m_audioDev = _dev; - } - - emit qualitySettingsChanged(); - emit sampleRateChanged(); - - startProcessing( _needs_fifo ); -} - - - - -void mixer::restoreAudioDevice() -{ - if( m_oldAudioDev != NULL ) - { - stopProcessing(); - delete m_audioDev; - - m_audioDev = m_oldAudioDev; - emit sampleRateChanged(); - - m_oldAudioDev = NULL; - startProcessing(); - } -} - - - - -void mixer::removeAudioPort( AudioPort * _port ) +void Mixer::removeAudioPort( AudioPort * _port ) { QVector::Iterator it = qFind( m_audioPorts.begin(), m_audioPorts.end(), @@ -881,7 +754,7 @@ void mixer::removeAudioPort( AudioPort * _port ) -void mixer::removePlayHandle( playHandle * _ph ) +void Mixer::removePlayHandle( playHandle * _ph ) { lock(); // check thread affinity as we must not delete play-handles @@ -908,7 +781,7 @@ void mixer::removePlayHandle( playHandle * _ph ) -void mixer::removePlayHandles( track * _track, playHandle::Type _type ) +void Mixer::removePlayHandles( track * _track, playHandle::Type _type ) { lock(); PlayHandleList::Iterator it = m_playHandles.begin(); @@ -932,10 +805,10 @@ void mixer::removePlayHandles( track * _track, playHandle::Type _type ) -AudioDevice * mixer::tryAudioDevices() +AudioBackend * Mixer::tryAudioBackends() { bool success_ful = false; - AudioDevice * dev = NULL; + AudioBackend * dev = NULL; QString dev_name = configManager::inst()->value( "mixer", "audiodev" ); if( dev_name == AudioDummy::name() ) @@ -946,7 +819,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_ALSA if( dev_name == AudioAlsa::name() || dev_name == "" ) { - dev = new AudioAlsa( success_ful, this ); + dev = new AudioAlsa( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioAlsa::name(); @@ -960,7 +833,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_PORTAUDIO if( dev_name == AudioPortAudio::name() || dev_name == "" ) { - dev = new AudioPortAudio( success_ful, this ); + dev = new AudioPortAudio( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioPortAudio::name(); @@ -974,7 +847,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_PULSEAUDIO if( dev_name == AudioPulseAudio::name() || dev_name == "" ) { - dev = new AudioPulseAudio( success_ful, this ); + dev = new AudioPulseAudio( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioPulseAudio::name(); @@ -988,7 +861,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_OSS if( dev_name == AudioOss::name() || dev_name == "" ) { - dev = new AudioOss( success_ful, this ); + dev = new AudioOss( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioOss::name(); @@ -1002,7 +875,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_JACK if( dev_name == AudioJack::name() || dev_name == "" ) { - dev = new AudioJack( success_ful, this ); + dev = new AudioJack( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioJack::name(); @@ -1016,7 +889,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_SDL if( dev_name == AudioSdl::name() || dev_name == "" ) { - dev = new AudioSdl( success_ful, this ); + dev = new AudioSdl( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioSdl::name(); @@ -1027,7 +900,7 @@ AudioDevice * mixer::tryAudioDevices() #endif // add more device-classes here... - //dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, this ); + //dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, audioOutputContext() ); //if( sucess_ful ) //{ // return dev; @@ -1040,13 +913,13 @@ AudioDevice * mixer::tryAudioDevices() m_audioDevName = AudioDummy::name(); - return new AudioDummy( success_ful, this ); + return new AudioDummy( success_ful, audioOutputContext() ); } -MidiClient * mixer::tryMidiClients() +MidiClient * Mixer::tryMidiClients() { QString client_name = configManager::inst()->value( "mixer", "mididev" ); @@ -1111,57 +984,5 @@ MidiClient * mixer::tryMidiClients() - - - - - - -mixer::fifoWriter::fifoWriter( mixer * _mixer, fifo * _fifo ) : - m_mixer( _mixer ), - m_fifo( _fifo ), - m_writing( true ) -{ -} - - - - -void mixer::fifoWriter::finish() -{ - m_writing = false; -} - - - - -void mixer::fifoWriter::run() -{ -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_PTHREAD_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( 0, &mask ); - pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); -#endif -#endif -#endif - - const fpp_t frames = m_mixer->framesPerPeriod(); - while( m_writing ) - { - sampleFrameA * buffer = CPU::allocFrames( frames ); - const sampleFrameA * b = m_mixer->renderNextBuffer(); - CPU::memCpy( buffer, b, frames * sizeof( sampleFrameA ) ); - m_fifo->write( buffer ); - } - - m_fifo->write( NULL ); -} - - - - -#include "moc_mixer.cxx" +#include "moc_Mixer.cxx" diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index ed00ef643..7b5c8a60c 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -24,7 +24,7 @@ #include "Oscillator.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "AutomatableModel.h" @@ -55,9 +55,9 @@ Oscillator::Oscillator( const IntModel * _wave_shape_model, void Oscillator::update( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - if( m_freq >= engine::getMixer()->processingSampleRate() / 2 ) + if( m_freq >= engine::mixer()->processingSampleRate() / 2 ) { - mixer::clearAudioBuffer( _ab, _frames ); + Mixer::clearAudioBuffer( _ab, _frames ); return; } if( m_subOsc != NULL ) @@ -456,7 +456,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, recalcPhase(); const float osc_coeff = m_freq * m_detuning; const float sampleRateCorrection = 44100.0f / - engine::getMixer()->processingSampleRate(); + engine::mixer()->processingSampleRate(); for( fpp_t frame = 0; frame < _frames; ++frame ) { diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 208304fb2..10ab65acb 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -32,7 +32,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "PeakController.h" #include "ControllerDialog.h" #include "plugins/peak_controller_effect/peak_controller_effect.h" diff --git a/src/core/Plugin.cpp b/src/core/Plugin.cpp index 9836bdef2..c50521c37 100644 --- a/src/core/Plugin.cpp +++ b/src/core/Plugin.cpp @@ -29,7 +29,6 @@ #include "Plugin.h" #include "embed.h" #include "engine.h" -#include "mixer.h" #include "config_mgr.h" #include "DummyPlugin.h" #include "AutomatableModel.h" diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 63de13509..887f45f6e 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -77,17 +77,19 @@ FileEncodeDevice __fileEncodeDevices[] = const char * ProjectRenderer::EFF_ext[] = {"wav", "ogg", "mp3", "flac"}; -ProjectRenderer::ProjectRenderer( const mixer::qualitySettings & _qs, +ProjectRenderer::ProjectRenderer( + const AudioOutputContext::QualitySettings & _qs, const OutputSettings & _os, ExportFileFormats _file_format, const QString & _out_file ) : QThread( engine::getMixer() ), m_fileDev( NULL ), - m_qualitySettings( _qs ), - m_oldQualitySettings( engine::getMixer()->currentQualitySettings() ), m_progress( 0 ), m_abort( false ) { + m_context = new AudioOutputContext( engine::getMixer(), + NULL, + _qs ); if( __fileEncodeDevices[_file_format].m_getDevInst == NULL ) { return; @@ -100,13 +102,15 @@ ProjectRenderer::ProjectRenderer( const mixer::qualitySettings & _qs, _os.bitrate, _os.bitrate - 64, _os.bitrate + 64, _os.depth == Depth_32Bit ? 32 : ( _os.depth == Depth_24Bit ? 24 : 16 ), - engine::getMixer() ); + m_context ); if( success_ful == false ) { delete m_fileDev; m_fileDev = NULL; } + m_context->setAudioBackend( m_fileDev ); + } @@ -114,6 +118,7 @@ ProjectRenderer::ProjectRenderer( const mixer::qualitySettings & _qs, ProjectRenderer::~ProjectRenderer() { + delete m_fileDev; } @@ -129,12 +134,12 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( { if( QString( __fileEncodeDevices[idx].m_extension ) == _ext ) { - return( __fileEncodeDevices[idx].m_fileFormat ); + return __fileEncodeDevices[idx].m_fileFormat; } ++idx; } - return( WaveFile ); // default + return WaveFile; // default } @@ -144,11 +149,12 @@ void ProjectRenderer::startProcessing() { if( isReady() ) { + connect( this, SIGNAL( finished() ), this, SLOT( finishProcessing() ) ); + // have to do mixer stuff with GUI-thread-affinity in order to // make slots connected to sampleRateChanged()-signals being // called immediately - engine::getMixer()->setAudioDevice( m_fileDev, - m_qualitySettings, false ); + engine::mixer()->setAudioOutputContext( m_context ); start( #ifndef LMMS_BUILD_WIN32 @@ -160,6 +166,49 @@ void ProjectRenderer::startProcessing() + +void ProjectRenderer::abortProcessing() +{ + m_abort = true; +} + + + + +void ProjectRenderer::updateConsoleProgress() +{ + const int cols = 50; + static int rot = 0; + char buf[80]; + char prog[cols+1]; + + if( m_fileDev == NULL ){ + qWarning("Error occured. Aborting render."); + m_consoleUpdateTimer->stop(); + delete m_consoleUpdateTimer; + // TODO: kill the program. I can't figure out how to do it... + return; + } + + for( int i = 0; i < cols; ++i ) + { + prog[i] = ( i*100/cols <= m_progress ? '-' : ' ' ); + } + prog[cols] = 0; + + const char * activity = (const char *) "|/-\\"; + memset( buf, 0, sizeof( buf ) ); + sprintf( buf, "\r|%s| %3d%% %c ", prog, m_progress, + activity[rot] ); + rot = ( rot+1 ) % 4; + + fprintf( stderr, "%s", buf ); + fflush( stderr ); +} + + + + void ProjectRenderer::run() { #if 0 @@ -194,11 +243,17 @@ void ProjectRenderer::run() } engine::getSong()->stopExport(); +} + + + +void ProjectRenderer::finishProcessing() +{ const QString f = m_fileDev->outputFile(); - engine::getMixer()->restoreAudioDevice(); // also deletes audio-dev - engine::getMixer()->changeQuality( m_oldQualitySettings ); + engine::mixer()->setAudioOutputContext( + engine::mixer()->defaultAudioOutputContext() ); // if the user aborted export-process, the file has to be deleted if( m_abort ) @@ -209,46 +264,5 @@ void ProjectRenderer::run() - -void ProjectRenderer::abortProcessing() -{ - m_abort = true; -} - - - -void ProjectRenderer::updateConsoleProgress() -{ - const int cols = 50; - static int rot = 0; - char buf[80]; - char prog[cols+1]; - - if( m_fileDev == NULL ){ - qWarning("Error occured. Aborting render."); - m_consoleUpdateTimer->stop(); - delete m_consoleUpdateTimer; - // TODO: kill the program. I can't figure out how to do it... - return; - } - - for( int i = 0; i < cols; ++i ) - { - prog[i] = ( i*100/cols <= m_progress ? '-' : ' ' ); - } - prog[cols] = 0; - - const char * activity = (const char *) "|/-\\"; - memset( buf, 0, sizeof( buf ) ); - sprintf( buf, "\r|%s| %3d%% %c ", prog, m_progress, - activity[rot] ); - rot = ( rot+1 ) % 4; - - fprintf( stderr, "%s", buf ); - fflush( stderr ); -} - - - #include "moc_ProjectRenderer.cxx" diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index e712cda80..a711d27cd 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -29,7 +29,7 @@ #endif #include "RemotePlugin.h" -#include "mixer.h" +#include "Mixer.h" #include "engine.h" #include "config_mgr.h" diff --git a/src/core/audio/AudioAlsa.cpp b/src/core/audio/AudioAlsa.cpp index b119e619a..077f5cfef 100644 --- a/src/core/audio/AudioAlsa.cpp +++ b/src/core/audio/AudioAlsa.cpp @@ -40,11 +40,11 @@ -AudioAlsa::AudioAlsa( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( +AudioAlsa::AudioAlsa( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audioalsa", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_handle( NULL ), m_hwParams( NULL ), m_swParams( NULL ), @@ -201,7 +201,7 @@ void AudioAlsa::applyQualitySettings() { if( hqAudio() ) { - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); if( m_handle != NULL ) { @@ -233,8 +233,6 @@ void AudioAlsa::applyQualitySettings() return; } } - - AudioDevice::applyQualitySettings(); } @@ -242,16 +240,15 @@ void AudioAlsa::applyQualitySettings() void AudioAlsa::run() { - sampleFrameA * temp = CPU::allocFrames( - getMixer()->framesPerPeriod() ); + sampleFrameA * temp = CPU::allocFrames( mixer()->framesPerPeriod() ); intSampleFrameA * outbuf = (intSampleFrameA *) CPU::memAlloc( sizeof( intSampleFrameA ) * channels() / - DEFAULT_CHANNELS * getMixer()->framesPerPeriod() ); + DEFAULT_CHANNELS * mixer()->framesPerPeriod() ); int_sample_t * pcmbuf = new int_sample_t[m_periodSize * channels()]; - int outbuf_size = getMixer()->framesPerPeriod() * channels(); + int outbuf_size = mixer()->framesPerPeriod() * channels(); int outbuf_pos = 0; int pcmbuf_size = m_periodSize * channels(); @@ -274,7 +271,7 @@ void AudioAlsa::run() outbuf_size = frames * channels(); CPU::convertToS16( temp, outbuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); } int min_len = qMin( len, outbuf_size - outbuf_pos ); @@ -374,7 +371,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) sampleRate(), 0 ) ) < 0 ) { if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams, - getMixer()->baseSampleRate(), 0 ) ) < 0 ) + mixer()->baseSampleRate(), 0 ) ) < 0 ) { printf( "Could not set sample rate: %s\n", snd_strerror( err ) ); @@ -382,7 +379,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) } } - m_periodSize = getMixer()->framesPerPeriod(); + m_periodSize = mixer()->framesPerPeriod(); m_bufferSize = m_periodSize * 8; dir = 0; err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams, @@ -493,7 +490,7 @@ int AudioAlsa::setSWParams() AudioAlsa::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioAlsa::name(), _parent ) + AudioBackend::setupWidget( AudioAlsa::name(), _parent ) { m_device = new QComboBox( this ); diff --git a/src/core/audio/AudioBackend.cpp b/src/core/audio/AudioBackend.cpp new file mode 100644 index 000000000..0b3607049 --- /dev/null +++ b/src/core/audio/AudioBackend.cpp @@ -0,0 +1,144 @@ +/* + * AudioBackend.cpp - base-class for audio-devices used by LMMS-mixer + * + * Copyright (c) 2004-2009 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "AudioBackend.h" +#include "AudioOutputContext.h" +#include "config_mgr.h" +#include "debug.h" +#include "Cpu.h" + + + +AudioBackend::AudioBackend( const ch_cnt_t _channels, + AudioOutputContext * context ) : + m_supportsCapture( false ), + m_context( context ), + m_sampleRate( mixer()->processingSampleRate() ), + m_channels( _channels ), + m_buffer( CPU::allocFrames( mixer()->framesPerPeriod() ) ) +{ +} + + + + +AudioBackend::~AudioBackend() +{ + CPU::freeFrames( m_buffer ); +} + + + + +int AudioBackend::processNextBuffer() +{ + const int frames = getNextBuffer( m_buffer ); + if( frames ) + { + writeBuffer( m_buffer, frames, mixer()->masterGain() ); + } + return frames; +} + + + + +int AudioBackend::getNextBuffer( sampleFrameA * _ab ) +{ + return outputContext()->getCurrentOutputBuffer( _ab, sampleRate() ); +} + + + + +void AudioBackend::stopProcessing() +{ + // flush AudioOutputContext's FIFO + while( processNextBuffer() ) + { + } +} + + + + +void AudioBackend::applyQualitySettings() +{ +} + + + + +void AudioBackend::registerPort( AudioPort * ) +{ +} + + + + +void AudioBackend::unregisterPort( AudioPort * _port ) +{ +} + + + + +void AudioBackend::renamePort( AudioPort * ) +{ +} + + + + +void AudioBackend::clearS16Buffer( intSampleFrameA * _outbuf, const fpp_t _frames ) +{ + CPU::memClear( _outbuf, _frames * sizeof( *_outbuf ) ); +// memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE ); +} + + + + +bool AudioBackend::hqAudio() const +{ + return configManager::inst()->value( "mixer", "hqaudio" ).toInt(); +} + + + + +const Mixer * AudioBackend::mixer() const +{ + return outputContext()->mixer(); +} + + + + +Mixer * AudioBackend::mixer() +{ + return outputContext()->mixer(); +} + + diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp deleted file mode 100644 index ba38a227f..000000000 --- a/src/core/audio/AudioDevice.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* - * AudioDevice.cpp - base-class for audio-devices used by LMMS-mixer - * - * Copyright (c) 2004-2009 Tobias Doerffel - * - * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "AudioDevice.h" -#include "config_mgr.h" -#include "debug.h" -#include "Cpu.h" - - - -AudioDevice::AudioDevice( const ch_cnt_t _channels, mixer * _mixer ) : - m_supportsCapture( false ), - m_sampleRate( _mixer->processingSampleRate() ), - m_channels( _channels ), - m_mixer( _mixer ), - m_buffer( CPU::allocFrames( getMixer()->framesPerPeriod() ) ) -{ - int error; - if( ( m_srcState = src_new( - getMixer()->currentQualitySettings().libsrcInterpolation(), - SURROUND_CHANNELS, &error ) ) == NULL ) - { - printf( "Error: src_new() failed in audio_device.cpp!\n" ); - } -} - - - - -AudioDevice::~AudioDevice() -{ - src_delete( m_srcState ); - CPU::freeFrames( m_buffer ); - - m_devMutex.tryLock(); - unlock(); -} - - - - -void AudioDevice::processNextBuffer() -{ - const fpp_t frames = getNextBuffer( m_buffer ); - if( frames ) - { - writeBuffer( m_buffer, frames, getMixer()->masterGain() ); - } - else - { - m_inProcess = false; - } -} - - - - -fpp_t AudioDevice::getNextBuffer( sampleFrameA * _ab ) -{ - fpp_t frames = getMixer()->framesPerPeriod(); - sampleFrameA * b = getMixer()->nextBuffer(); - if( !b ) - { - return 0; - } - - // make sure, no other thread is accessing device - lock(); - - // resample if neccessary - if( getMixer()->processingSampleRate() != m_sampleRate ) - { - resample( b, frames, _ab, getMixer()->processingSampleRate(), - m_sampleRate ); - frames = frames * m_sampleRate / - getMixer()->processingSampleRate(); - } - else - { - CPU::memCpy( _ab, b, frames * sizeof( surroundSampleFrame ) ); - } - - // release lock - unlock(); - - if( getMixer()->hasFifoWriter() ) - { - CPU::freeFrames( b ); - } - - return frames; -} - - - - -void AudioDevice::stopProcessing() -{ - if( getMixer()->hasFifoWriter() ) - { - while( m_inProcess ) - { - processNextBuffer(); - } - } -} - - - - -void AudioDevice::applyQualitySettings() -{ - src_delete( m_srcState ); - - int error; - if( ( m_srcState = src_new( - getMixer()->currentQualitySettings().libsrcInterpolation(), - SURROUND_CHANNELS, &error ) ) == NULL ) - { - printf( "Error: src_new() failed in audio_device.cpp!\n" ); - } -} - - - - -void AudioDevice::registerPort( AudioPort * ) -{ -} - - - - -void AudioDevice::unregisterPort( AudioPort * _port ) -{ -} - - - - -void AudioDevice::renamePort( AudioPort * ) -{ -} - - - - -void AudioDevice::resample( const sampleFrame * _src, const fpp_t _frames, - sampleFrame * _dst, - const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ) -{ - if( m_srcState == NULL ) - { - return; - } - m_srcData.input_frames = _frames; - m_srcData.output_frames = _frames; - m_srcData.data_in = (float *) _src[0]; - m_srcData.data_out = _dst[0]; - m_srcData.src_ratio = (double) _dst_sr / _src_sr; - m_srcData.end_of_input = 0; - int error; - if( ( error = src_process( m_srcState, &m_srcData ) ) ) - { - printf( "AudioDevice::resample(): error while resampling: %s\n", - src_strerror( error ) ); - } -} - - - -void AudioDevice::clearS16Buffer( intSampleFrameA * _outbuf, const fpp_t _frames ) -{ - CPU::memClear( _outbuf, _frames * sizeof( *_outbuf ) ); -// memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE ); -} - - - - -bool AudioDevice::hqAudio() const -{ - return configManager::inst()->value( "mixer", "hqaudio" ).toInt(); -} - - diff --git a/src/core/audio/AudioFileDevice.cpp b/src/core/audio/AudioFileDevice.cpp index e57c2a883..e92f41b47 100644 --- a/src/core/audio/AudioFileDevice.cpp +++ b/src/core/audio/AudioFileDevice.cpp @@ -39,8 +39,8 @@ AudioFileDevice::AudioFileDevice( const sample_rate_t _sample_rate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) : - AudioDevice( _channels, _mixer ), + AudioOutputContext * context ) : + AudioBackend( _channels, context ), m_outputFile( _file ), m_useVbr( _use_vbr ), m_nomBitrate( _nom_bitrate ), diff --git a/src/core/audio/AudioFileFlac.cpp b/src/core/audio/AudioFileFlac.cpp index cfcc7fc51..77e2fa731 100644 --- a/src/core/audio/AudioFileFlac.cpp +++ b/src/core/audio/AudioFileFlac.cpp @@ -37,9 +37,9 @@ AudioFileFlac::AudioFileFlac( const sample_rate_t _sample_rate, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, - const int _depth, mixer * _mixer ) : + const int _depth, AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, _depth, _mixer ) + _min_bitrate, _max_bitrate, _depth, context ) { _success_ful = startEncoding(); } diff --git a/src/core/audio/AudioFileMp3.cpp b/src/core/audio/AudioFileMp3.cpp index e9569c99b..72252066e 100644 --- a/src/core/audio/AudioFileMp3.cpp +++ b/src/core/audio/AudioFileMp3.cpp @@ -38,9 +38,9 @@ AudioFileMp3::AudioFileMp3( const sample_rate_t _sample_rate, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, - const int _depth, mixer * _mixer ) : + const int _depth, AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, _depth, _mixer ), + _min_bitrate, _max_bitrate, _depth, context ), m_lgf( NULL ), m_lame( LameLibrary() ), m_outfile( NULL ), diff --git a/src/core/audio/AudioFileOgg.cpp b/src/core/audio/AudioFileOgg.cpp index 3ef2856e7..781a3468a 100644 --- a/src/core/audio/AudioFileOgg.cpp +++ b/src/core/audio/AudioFileOgg.cpp @@ -44,10 +44,10 @@ AudioFileOgg::AudioFileOgg( const sample_rate_t _sample_rate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) : + AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ) + _depth, context ) { m_ok = _success_ful = startEncoding(); } diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index b15ab339e..f52b784dd 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -36,10 +36,10 @@ AudioFileWave::AudioFileWave( const sample_rate_t _sample_rate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) : + AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ) + _depth, context ) { _success_ful = startEncoding(); } @@ -59,7 +59,7 @@ bool AudioFileWave::startEncoding() { m_si.samplerate = sampleRate(); m_si.channels = channels(); - m_si.frames = getMixer()->framesPerPeriod(); + m_si.frames = mixer()->framesPerPeriod(); m_si.sections = 1; m_si.seekable = 0; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 737f5d462..9eb0856f5 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -45,15 +45,15 @@ -AudioJack::AudioJack( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( configManager::inst()->value( +AudioJack::AudioJack( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audiojack", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_client( NULL ), m_active( false ), m_stopSemaphore( 1 ), - m_outBuf( CPU::allocFrames( getMixer()->framesPerPeriod() ) ), + m_outBuf( CPU::allocFrames( mixer()->framesPerPeriod() ) ), m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) { @@ -210,7 +210,7 @@ void AudioJack::startProcessing() // try to sync JACK's and LMMS's buffer-size -// jack_set_buffer_size( m_client, getMixer()->framesPerPeriod() ); +// jack_set_buffer_size( m_client, mixer()->framesPerPeriod() ); @@ -255,15 +255,13 @@ void AudioJack::applyQualitySettings() { if( hqAudio() ) { - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); if( jack_get_sample_rate( m_client ) != sampleRate() ) { setSampleRate( jack_get_sample_rate( m_client ) ); } } - - AudioDevice::applyQualitySettings(); } @@ -343,7 +341,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) #ifdef AUDIO_PORT_SUPPORT const Uint32 frames = qMin( _nframes, - getMixer()->framesPerPeriod() ); + mixer()->framesPerPeriod() ); for( jackPortMap::iterator it = m_portMap.begin(); it != m_portMap.end(); ++it ) { @@ -372,7 +370,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) _nframes, m_framesToDoInCurBuf - m_framesDoneInCurBuf ); - const float gain = getMixer()->masterGain(); + const float gain = mixer()->masterGain(); for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { jack_default_audio_sample_t * o = outbufs[chnl]; @@ -434,7 +432,7 @@ void AudioJack::shutdownCallback( void * _udata ) AudioJack::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioJack::name(), _parent ) + AudioBackend::setupWidget( AudioJack::name(), _parent ) { QString cn = configManager::inst()->value( "audiojack", "clientname" ); if( cn.isEmpty() ) diff --git a/src/core/audio/AudioOss.cpp b/src/core/audio/AudioOss.cpp index 81cbfc05d..05dbf560b 100644 --- a/src/core/audio/AudioOss.cpp +++ b/src/core/audio/AudioOss.cpp @@ -74,11 +74,11 @@ -AudioOss::AudioOss( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( +AudioOss::AudioOss( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audiooss", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_convertEndian( false ) { _success_ful = false; @@ -106,7 +106,7 @@ AudioOss::AudioOss( bool & _success_ful, mixer * _mixer ) : int frag_spec; for( frag_spec = 0; static_cast( 0x01 << frag_spec ) < - getMixer()->framesPerPeriod() * channels() * + mixer()->framesPerPeriod() * channels() * BYTES_PER_INT_SAMPLE; ++frag_spec ) { @@ -178,7 +178,7 @@ AudioOss::AudioOss( bool & _success_ful, mixer * _mixer ) : } if( value != sampleRate() ) { - value = getMixer()->baseSampleRate(); + value = mixer()->baseSampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); @@ -271,7 +271,7 @@ void AudioOss::applyQualitySettings() { if( hqAudio() ) { - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); unsigned int value = sampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) @@ -282,7 +282,7 @@ void AudioOss::applyQualitySettings() } if( value != sampleRate() ) { - value = getMixer()->baseSampleRate(); + value = mixer()->baseSampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); @@ -292,8 +292,6 @@ void AudioOss::applyQualitySettings() setSampleRate( value ); } } - - AudioDevice::applyQualitySettings(); } @@ -302,10 +300,10 @@ void AudioOss::applyQualitySettings() void AudioOss::run() { sampleFrameA * temp = CPU::allocFrames( - getMixer()->framesPerPeriod() ); + mixer()->framesPerPeriod() ); intSampleFrameA * outbuf = (intSampleFrameA *) CPU::memAlloc( sizeof( intSampleFrameA ) * - getMixer()->framesPerPeriod() ); + mixer()->framesPerPeriod() ); while( true ) { @@ -316,7 +314,7 @@ void AudioOss::run() } int bytes = CPU::convertToS16( temp, outbuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); if( write( m_audioFD, outbuf, bytes ) != bytes ) { @@ -332,7 +330,7 @@ void AudioOss::run() AudioOss::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioOss::name(), _parent ) + AudioBackend::setupWidget( AudioOss::name(), _parent ) { m_device = new QLineEdit( probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); diff --git a/src/core/audio/AudioOutputContext.cpp b/src/core/audio/AudioOutputContext.cpp new file mode 100644 index 000000000..ae3187e60 --- /dev/null +++ b/src/core/audio/AudioOutputContext.cpp @@ -0,0 +1,311 @@ +/* + * AudioOutputContext.cpp - centralize all audio output related functionality + * + * Copyright (c) 2009 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "AudioBackend.h" +#include "AudioOutputContext.h" +#include "Cpu.h" + +#include "config_mgr.h" +#include "engine.h" + +AudioOutputContext::BufferFifo::BufferFifo( int _size, int _bufferSize ) : + m_readerSem( _size ), + m_writerSem( _size ), + m_readerIndex( 0 ), + m_writerIndex( 0 ), + m_size( _size ), + m_bufferSize( _bufferSize ) +{ + m_buffers = new sampleFrameA *[m_size]; + for( int i = 0; i < m_size; ++i ) + { + m_buffers[i] = CPU::allocFrames( m_bufferSize ); + } + + m_bufferStates = new BufferState[m_size]; + + m_readerSem.acquire( _size ); +} + + + +AudioOutputContext::BufferFifo::~BufferFifo() +{ + for( int i = 0; i < m_size; ++i ) + { + CPU::freeFrames( m_buffers[i] ); + } + + delete[] m_buffers; + delete[] m_bufferStates; + + m_readerSem.release( m_size ); +} + + + + +void AudioOutputContext::BufferFifo::write( sampleFrameA * _buffer ) +{ + m_writerSem.acquire(); + + if( _buffer != NULL ) + { + CPU::memCpy( m_buffers[m_writerIndex], _buffer, + m_bufferSize * sizeof( sampleFrameA ) ); + m_bufferStates[m_writerIndex] = Running; + } + else + { + m_bufferStates[m_writerIndex] = NullBuffer; + } + + m_writerIndex = ( m_writerIndex + 1 ) % m_size; + + m_readerSem.release(); +} + + + + +void AudioOutputContext::BufferFifo::startRead() +{ + m_readerSem.acquire(); +} + + + + +void AudioOutputContext::BufferFifo::finishRead() +{ + m_readerIndex = ( m_readerIndex + 1 ) % m_size; + m_writerSem.release(); +} + + + + + + + +AudioOutputContext::AudioOutputContext( Mixer * mixer, + AudioBackend * audioBackend, + const QualitySettings & qualitySettings ) : + m_mixer( mixer ), + m_qualitySettings( qualitySettings ), + m_audioBackend( audioBackend ), + m_fifo( NULL ), + m_fifoWriter( NULL ) +{ + int error; + if( ( m_srcState = src_new( + qualitySettings.libsrcInterpolation(), + SURROUND_CHANNELS, &error ) ) == NULL ) + { + qWarning( "src_new() failed in AudioOutputContext::AudioOutputContext()" ); + } + //m_audioBackend->applyQualitySettings(); + + int framesPerPeriod = m_mixer->framesPerPeriod(); + + // just rendering? + if( !engine::hasGUI() ) + { + m_fifo = new BufferFifo( 1, framesPerPeriod ); + } + else if( configManager::inst()->value( "mixer", "framesperaudiobuffer" + ).toInt() >= 32 ) + { + framesPerPeriod = + (fpp_t) configManager::inst()->value( "mixer", + "framesperaudiobuffer" ).toInt(); + + if( framesPerPeriod > DEFAULT_BUFFER_SIZE ) + { + m_fifo = new BufferFifo( framesPerPeriod / DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE ); + } + else + { + m_fifo = new BufferFifo( 1, framesPerPeriod ); + } + } + else + { + configManager::inst()->setValue( "mixer", + "framesperaudiobuffer", + QString::number( framesPerPeriod ) ); + m_fifo = new BufferFifo( 1, framesPerPeriod ); + } + +} + + + + +AudioOutputContext::~AudioOutputContext() +{ + while( m_fifo->isEmpty() == false ) + { + m_fifo->startRead(); + m_fifo->finishRead(); + } + delete m_fifo; + + src_delete( m_srcState ); +} + + + + +void AudioOutputContext::startProcessing() +{ + if( !isProcessing() ) + { + m_fifoWriter = new FifoWriter( this ); + m_fifoWriter->start( QThread::HighPriority ); + + m_audioBackend->startProcessing(); + } +} + + + + +void AudioOutputContext::stopProcessing() +{ + if( isProcessing() ) + { + m_fifoWriter->finish(); + m_audioBackend->stopProcessing(); + m_fifoWriter->wait(); + + delete m_fifoWriter; + m_fifoWriter = NULL; + } +} + + + + +bool AudioOutputContext::isProcessing() const +{ + return m_fifoWriter && m_fifoWriter->isRunning(); +} + + + + +int AudioOutputContext::getCurrentOutputBuffer( sampleFrameA * _destBuf, + sample_rate_t _destSampleRate ) +{ + int frames = mixer()->framesPerPeriod(); + m_fifo->startRead(); + if( m_fifo->currentReadBufferState() == BufferFifo::NullBuffer ) + { + m_fifo->finishRead(); + return 0; + } + sampleFrameA * srcBuf = m_fifo->currentReadBuffer(); + + if( mixer()->processingSampleRate() != _destSampleRate ) + { + if( m_srcState == NULL ) + { + m_fifo->finishRead(); + return 0; + } + m_srcData.input_frames = frames; + m_srcData.output_frames = frames; + m_srcData.data_in = (float *) srcBuf; + m_srcData.data_out = (float *) _destBuf; + m_srcData.src_ratio = (double) _destSampleRate / + mixer()->processingSampleRate(); + m_srcData.end_of_input = 0; + int error; + if( ( error = src_process( m_srcState, &m_srcData ) ) ) + { + qWarning( "AudioBackend::resample(): error while resampling: %s", + src_strerror( error ) ); + } + frames = frames * _destSampleRate / mixer()->processingSampleRate(); + } + else + { + CPU::memCpy( _destBuf, srcBuf, frames * sizeof( sampleFrameA ) ); + } + + // tell BufferFifo to release current read buffer + m_fifo->finishRead(); + + return frames; +} + + + + + + +AudioOutputContext::FifoWriter::FifoWriter( AudioOutputContext * context ) : + m_context( context ), + m_writing( true ) +{ +} + + + + +void AudioOutputContext::FifoWriter::finish() +{ + m_writing = false; +} + + + + +void AudioOutputContext::FifoWriter::run() +{ +#if 0 +#ifdef LMMS_BUILD_LINUX +#ifdef LMMS_HAVE_PTHREAD_H + cpu_set_t mask; + CPU_ZERO( &mask ); + CPU_SET( 0, &mask ); + pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); +#endif +#endif +#endif + + while( m_writing ) + { + m_context->fifo()->write( m_context->mixer()->renderNextBuffer() ); + } + + // write a NULL in order to signal the AudioBackend that the FifoWriter has + // finished + m_context->fifo()->write( NULL ); +} + + + diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 2b4301178..8129fbfb0 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -23,7 +23,8 @@ */ #include "AudioPort.h" -#include "AudioDevice.h" +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "EffectChain.h" #include "engine.h" #include "Cpu.h" @@ -88,11 +89,13 @@ void AudioPort::setExtOutputEnabled( bool _enabled ) m_extOutputEnabled = _enabled; if( m_extOutputEnabled ) { - engine::getMixer()->audioDev()->registerPort( this ); + engine::mixer()->audioOutputContext()-> + audioBackend()->registerPort( this ); } else { - engine::getMixer()->audioDev()->unregisterPort( this ); + engine::mixer()->audioOutputContext()-> + audioBackend()->unregisterPort( this ); } } } @@ -103,7 +106,7 @@ void AudioPort::setExtOutputEnabled( bool _enabled ) void AudioPort::setName( const QString & _name ) { m_name = _name; - engine::getMixer()->audioDev()->renamePort( this ); + engine::mixer()->audioOutputContext()->audioBackend()->renamePort( this ); } diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index 3e0713252..e2742d9b5 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -50,7 +50,7 @@ void AudioPortAudioSetupUtil::updateChannels() AudioPortAudio::AudioPortAudio( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( + AudioBackend( tLimit( configManager::inst()->value( "audioportaudio", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), @@ -284,8 +284,6 @@ void AudioPortAudio::applyQualitySettings() return; } } - - audioDevice::applyQualitySettings(); } int AudioPortAudio::process_callback( diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 539b1fba5..6bc621b20 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -46,11 +46,11 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) -AudioPulseAudio::AudioPulseAudio( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( +AudioPulseAudio::AudioPulseAudio( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audiopa", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_s( NULL ), m_quit( false ), m_convertEndian( false ) @@ -119,11 +119,9 @@ void AudioPulseAudio::applyQualitySettings() { if( hqAudio() ) { -// setSampleRate( engine::getMixer()->processingSampleRate() ); +// setSampleRate( mixer()->processingSampleRate() ); } - - AudioDevice::applyQualitySettings(); } @@ -139,12 +137,12 @@ static void stream_state_callback( pa_stream *s, void * userdata ) break; case PA_STREAM_READY: - qDebug( "Stream successfully created\n" ); + qDebug( "Stream successfully created" ); break; case PA_STREAM_FAILED: default: - qCritical( "Stream errror: %s\n", + qCritical( "Stream errror: %s", pa_strerror(pa_context_errno( pa_stream_get_context( s ) ) ) ); } @@ -166,7 +164,7 @@ static void context_state_callback(pa_context *c, void *userdata) case PA_CONTEXT_READY: { pa_cvolume cv; - qDebug( "Connection established.\n" ); + qDebug( "Connection established." ); _this->m_s = pa_stream_new( c, "lmms", &_this->m_sampleSpec, NULL); pa_stream_set_state_callback( _this->m_s, stream_state_callback, _this ); pa_stream_set_write_callback( _this->m_s, stream_write_callback, _this ); @@ -181,7 +179,8 @@ static void context_state_callback(pa_context *c, void *userdata) buffer_attr.minreq = (uint32_t)(-1); buffer_attr.fragsize = (uint32_t)(-1); - double latency = (double)( engine::getMixer()->framesPerPeriod() ) / + double latency = (double)( ( (const AudioPulseAudio *) _this )-> + mixer()->framesPerPeriod() ) / (double)_this->sampleRate(); // ask PulseAudio for the desired latency (which might not be approved) @@ -201,7 +200,7 @@ static void context_state_callback(pa_context *c, void *userdata) case PA_CONTEXT_FAILED: default: - qCritical( "Connection failure: %s\n", pa_strerror( pa_context_errno( c ) ) ); + qCritical( "Connection failure: %s", pa_strerror( pa_context_errno( c ) ) ); } } @@ -213,7 +212,7 @@ void AudioPulseAudio::run() pa_mainloop * mainLoop = pa_mainloop_new(); if( !mainLoop ) { - qCritical( "pa_mainloop_new() failed.\n" ); + qCritical( "pa_mainloop_new() failed." ); return; } pa_mainloop_api * mainloop_api = pa_mainloop_get_api( mainLoop ); @@ -250,7 +249,7 @@ void AudioPulseAudio::run() void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) { - const fpp_t fpp = getMixer()->framesPerPeriod(); + const fpp_t fpp = mixer()->framesPerPeriod(); sampleFrameA * temp = CPU::allocFrames( fpp ); Sint16 * pcmbuf = (Sint16*)CPU::memAlloc( fpp * channels() * sizeof(Sint16) ); @@ -267,7 +266,7 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) int bytes = CPU::convertToS16( temp, (intSampleFrameA *) pcmbuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); if( bytes > 0 ) { @@ -285,7 +284,7 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioPulseAudio::name(), _parent ) + AudioBackend::setupWidget( AudioPulseAudio::name(), _parent ) { m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp index 4601182a5..d280c0468 100644 --- a/src/core/audio/AudioSampleRecorder.cpp +++ b/src/core/audio/AudioSampleRecorder.cpp @@ -26,14 +26,14 @@ #include "AudioSampleRecorder.h" #include "sample_buffer.h" -#include "debug.h" +#include "Cpu.h" AudioSampleRecorder::AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, - mixer * _mixer ) : - AudioDevice( _channels, _mixer ), + AudioOutputContext * context ) : + AudioBackend( _channels, context ), m_buffers() { _success_ful = true; @@ -46,7 +46,7 @@ AudioSampleRecorder::~AudioSampleRecorder() { while( !m_buffers.empty() ) { - delete[] m_buffers.front().first; + CPU::freeFrames( m_buffers.front().first ); m_buffers.erase( m_buffers.begin() ); } } @@ -72,9 +72,9 @@ void AudioSampleRecorder::createSampleBuffer( sampleBuffer * * _sample_buf ) { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in - sampleFrame * data = new sampleFrame[frames]; + sampleFrameA * data = CPU::allocFrames( frames ); // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; + sampleFrameA * data_ptr = data; #ifdef LMMS_DEBUG assert( data != NULL ); @@ -83,30 +83,24 @@ void AudioSampleRecorder::createSampleBuffer( sampleBuffer * * _sample_buf ) for( BufferList::ConstIterator it = m_buffers.begin(); it != m_buffers.end(); ++it ) { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); + CPU::memCpy( data_ptr, ( *it ).first, ( *it ).second * + sizeof( sampleFrameA ) ); data_ptr += ( *it ).second; } // create according sample-buffer out of big buffer *_sample_buf = new sampleBuffer( data, frames ); ( *_sample_buf )->setSampleRate( sampleRate() ); - delete[] data; + CPU::freeFrames( data ); } -void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab, +void AudioSampleRecorder::writeBuffer( const sampleFrameA * srcBuf, const fpp_t _frames, const float ) { - sampleFrame * buf = new sampleFrame[_frames]; - for( fpp_t frame = 0; frame < _frames; ++frame ) - { - for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) - { - buf[frame][chnl] = _ab[frame][chnl]; - } - } + sampleFrameA * buf = CPU::allocFrames( _frames ); + CPU::memCpy( buf, srcBuf, _frames*sizeof( sampleFrameA ) ); m_buffers.push_back( qMakePair( buf, _frames ) ); } diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 78affdb9a..bc62ffee0 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -38,16 +38,16 @@ -AudioSdl::AudioSdl( bool & _success_ful, mixer * _mixer ) : - AudioDevice( DEFAULT_CHANNELS, _mixer ), - m_outBuf( CPU::allocFrames( getMixer()->framesPerPeriod() ) ), +AudioSdl::AudioSdl( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( DEFAULT_CHANNELS, context ), + m_outBuf( CPU::allocFrames( mixer()->framesPerPeriod() ) ), m_convertedBufPos( 0 ), m_convertEndian( false ), m_stopSemaphore( 1 ) { _success_ful = false; - m_convertedBufSize = getMixer()->framesPerPeriod() * + m_convertedBufSize = mixer()->framesPerPeriod() * sizeof( intSampleFrameA ); m_convertedBuf = (intSampleFrameA *) CPU::memAlloc( m_convertedBufSize ); @@ -63,7 +63,7 @@ AudioSdl::AudioSdl( bool & _success_ful, mixer * _mixer ) : // of system, so we don't have // to convert the buffers m_audioHandle.channels = channels(); - m_audioHandle.samples = qMax( 1024, getMixer()->framesPerPeriod()*2 ); + m_audioHandle.samples = qMax( 1024, mixer()->framesPerPeriod()*2 ); m_audioHandle.callback = sdlAudioCallback; m_audioHandle.userdata = this; @@ -131,7 +131,7 @@ void AudioSdl::applyQualitySettings() { SDL_CloseAudio(); - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); m_audioHandle.freq = sampleRate(); @@ -143,8 +143,6 @@ void AudioSdl::applyQualitySettings() qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); } } - - AudioDevice::applyQualitySettings(); } @@ -186,7 +184,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) CPU::convertToS16( m_outBuf, m_convertedBuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); } const int min_len = qMin( _len, m_convertedBufSize @@ -203,7 +201,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) AudioSdl::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioSdl::name(), _parent ) + AudioBackend::setupWidget( AudioSdl::name(), _parent ) { QString dev = configManager::inst()->value( "audiosdl", "device" ); m_device = new QLineEdit( dev, this ); diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 6606619c9..0bb323b5e 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -37,7 +37,7 @@ #include "InstrumentTrack.h" #include "ladspa_2_lmms.h" #include "MainWindow.h" -#include "mixer.h" +#include "Mixer.h" #include "pattern.h" #include "piano_roll.h" #include "ProjectJournal.h" @@ -56,7 +56,7 @@ bool engine::s_hasGUI = true; bool engine::s_suppressMessages = false; float engine::s_framesPerTick; -mixer * engine::s_mixer = NULL; +Mixer * engine::s_mixer = NULL; FxMixer * engine::s_fxMixer = NULL; FxMixerView * engine::s_fxMixerView = NULL; MainWindow * engine::s_mainWindow = NULL; @@ -89,9 +89,11 @@ void engine::init( const bool _has_gui ) initPluginFileHandling(); s_projectJournal = new ProjectJournal; - s_mixer = new mixer; + s_mixer = new Mixer; + s_song = new song; + s_mixer->initDevices(); // init resource framework s_workingDirResourceDB = @@ -119,8 +121,6 @@ void engine::init( const bool _has_gui ) s_projectJournal->setJournalling( true ); - s_mixer->initDevices(); - s_midiControlListener = new MidiControlListener(); s_automationRecorder = new AutomationRecorder; diff --git a/src/core/main.cpp b/src/core/main.cpp index 41227025f..ec5b46b63 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -126,7 +126,8 @@ int main( int argc, char * * argv ) new QApplication( argc, argv ) ; - mixer::qualitySettings qs( mixer::qualitySettings::Mode_HighQuality ); + AudioOutputContext::QualitySettings qs( + AudioOutputContext::QualitySettings::Preset_HighQuality ); ProjectRenderer::OutputSettings os( 44100, false, 160, ProjectRenderer::Depth_16Bit ); ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; @@ -284,19 +285,23 @@ int main( int argc, char * * argv ) const QString ip = QString( argv[i + 1] ); if( ip == "linear" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_Linear; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_Linear ); } else if( ip == "sincfastest" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_SincFastest; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_SincFastest ); } else if( ip == "sincmedium" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_SincMedium; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_SincMedium ); } else if( ip == "sincbest" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_SincBest; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_SincBest ); } else { @@ -314,21 +319,25 @@ int main( int argc, char * * argv ) switch( o ) { case 1: - qs.oversampling = mixer::qualitySettings::Oversampling_None; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_None ); + break; case 2: - qs.oversampling = mixer::qualitySettings::Oversampling_2x; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_2x ); + break; case 4: - qs.oversampling = mixer::qualitySettings::Oversampling_4x; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_4x ); + break; case 8: - qs.oversampling = mixer::qualitySettings::Oversampling_8x; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_8x ); + break; default: - printf( "\nInvalid oversampling %s.\n\n" + printf( "\nInvalid oversampling %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); - return( EXIT_FAILURE ); + return EXIT_FAILURE; } ++i; } diff --git a/src/core/midi/MidiControlListener.cpp b/src/core/midi/MidiControlListener.cpp index f014cfc56..4a089f888 100644 --- a/src/core/midi/MidiControlListener.cpp +++ b/src/core/midi/MidiControlListener.cpp @@ -30,7 +30,6 @@ #include #include "MidiControlListener.h" -#include "mixer.h" #include "MidiClient.h" #include "MidiPort.h" #include "engine.h" diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index ba74a9e62..5cb60c61e 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -29,7 +29,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "MidiClient.h" #include "MidiController.h" #include "automation_recorder.h" diff --git a/src/core/sample_buffer.cpp b/src/core/sample_buffer.cpp index 5958ea82c..4051524fa 100644 --- a/src/core/sample_buffer.cpp +++ b/src/core/sample_buffer.cpp @@ -24,7 +24,7 @@ #include "sample_buffer.h" -#include "mixer.h" +#include "Mixer.h" #include diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index 6ac5f6d7f..b8c8ccf8b 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -146,11 +146,12 @@ void ExportProjectDialog::startBtnClicked() ui->progressBar->setEnabled( true ); - mixer::qualitySettings qs = mixer::qualitySettings( - static_cast( - ui->interpolationCB->currentIndex() ), - static_cast( - ui->oversamplingCB->currentIndex() ), + AudioOutputContext::QualitySettings qs = + AudioOutputContext::QualitySettings( + static_cast( + ui->interpolationCB->currentIndex() ), + static_cast( + ui->oversamplingCB->currentIndex() ), ui->sampleExactControllersCB->isChecked(), ui->aliasFreeOscillatorsCB->isChecked() ); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 453193dbd..307e96bf3 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -56,7 +56,7 @@ #include "plugin_browser.h" #include "SideBar.h" #include "config_mgr.h" -#include "mixer.h" +#include "Mixer.h" #include "project_notes.h" #include "setup_dialog.h" #include "AudioDummy.h" @@ -1470,9 +1470,9 @@ void MainWindow::browseHelp() void MainWindow::setHighQuality( bool _hq ) { - engine::getMixer()->changeQuality( mixer::qualitySettings( - _hq ? mixer::qualitySettings::Mode_HighQuality : - mixer::qualitySettings::Mode_Draft ) ); + /*engine::getMixer()->changeQuality( Mixer::qualitySettings( + _hq ? Mixer::qualitySettings::Mode_HighQuality : + Mixer::qualitySettings::Mode_Draft ) );*/ } diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 10d783f69..05c813101 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -39,7 +39,6 @@ #include "tab_button.h" #include "tab_widget.h" #include "gui_templates.h" -#include "mixer.h" #include "ProjectJournal.h" #include "config_mgr.h" #include "embed.h" diff --git a/src/gui/song_editor.cpp b/src/gui/song_editor.cpp index 5bf826549..697b2b8af 100644 --- a/src/gui/song_editor.cpp +++ b/src/gui/song_editor.cpp @@ -35,6 +35,7 @@ #include +#include "AudioOutputContext.h" #include "song_editor.h" #include "combobox.h" #include "embed.h" @@ -43,7 +44,7 @@ #include "timeline.h" #include "tool_button.h" #include "tooltip.h" -#include "AudioDevice.h" +#include "AudioBackend.h" #include "piano_roll.h" @@ -130,7 +131,7 @@ songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) : m_recordButton->setDisabled( true ); // disable record buttons if capturing is not supported - if( !engine::getMixer()->audioDev()->supportsCapture() ) + if( !engine::mixer()->audioOutputContext()->audioBackend()->supportsCapture() ) { m_recordButton->setDisabled( true ); m_recordAccompanyButton->setDisabled( true ); diff --git a/src/gui/widgets/EnvelopeAndLfoView.cpp b/src/gui/widgets/EnvelopeAndLfoView.cpp index cd68ad36c..dbc1f48c7 100644 --- a/src/gui/widgets/EnvelopeAndLfoView.cpp +++ b/src/gui/widgets/EnvelopeAndLfoView.cpp @@ -33,7 +33,7 @@ #include "gui_templates.h" #include "knob.h" #include "led_checkbox.h" -#include "mixer.h" +#include "Mixer.h" #include "mmp.h" #include "Oscillator.h" #include "pixmap_button.h" @@ -482,7 +482,7 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) int graphYBase = LFO_GRAPH_Y + 3 + lfoGraphHeight / 2; const float framesForGraph = SECS_PER_LFO_OSCILLATION * - engine::getMixer()->baseSampleRate() / 10; + engine::mixer()->baseSampleRate() / 10; const float lfoGrayAmount = fabsf( m_lfoAmountKnob->value() ); p.setPen( QPen( QColor::fromHsvF( diff --git a/src/gui/widgets/InstrumentMidiIOView.cpp b/src/gui/widgets/InstrumentMidiIOView.cpp index 7db26499b..d8572a03d 100644 --- a/src/gui/widgets/InstrumentMidiIOView.cpp +++ b/src/gui/widgets/InstrumentMidiIOView.cpp @@ -33,7 +33,7 @@ #include "gui_templates.h" #include "lcd_spinbox.h" #include "MidiClient.h" -#include "mixer.h" +#include "Mixer.h" #include "tooltip.h" @@ -93,7 +93,7 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget * _parent ) : m_outputProgramSpinBox, SLOT( setEnabled( bool ) ) ); - if( !engine::getMixer()->midiClient()->isRaw() ) + if( !engine::mixer()->midiClient()->isRaw() ) { m_rpBtn = new QToolButton( m_midiInputGroupBox ); m_rpBtn->setText( tr( "MIDI devices to receive MIDI events from" ) ); diff --git a/src/gui/widgets/cpuload_widget.cpp b/src/gui/widgets/cpuload_widget.cpp index 5856bfd6f..7b4db3a29 100644 --- a/src/gui/widgets/cpuload_widget.cpp +++ b/src/gui/widgets/cpuload_widget.cpp @@ -2,8 +2,8 @@ * cpuload_widget.cpp - widget for displaying CPU-load (partly based on * Hydrogen's CPU-load-widget) * - * Copyright (c) 2005-2007 Tobias Doerffel - * + * Copyright (c) 2005-2009 Tobias Doerffel + * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or @@ -29,7 +29,7 @@ #include "cpuload_widget.h" #include "embed.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" cpuloadWidget::cpuloadWidget( QWidget * _parent ) : @@ -90,7 +90,7 @@ void cpuloadWidget::paintEvent( QPaintEvent * ) void cpuloadWidget::updateCpuLoad() { // smooth load-values a bit - Uint8 new_load = ( m_currentLoad + engine::getMixer()->cpuLoad() ) / 2; + int new_load = ( m_currentLoad + engine::mixer()->cpuLoad() ) / 2; if( new_load != m_currentLoad ) { m_currentLoad = new_load; diff --git a/src/gui/widgets/visualization_widget.cpp b/src/gui/widgets/visualization_widget.cpp index dc017300f..e1de1d384 100644 --- a/src/gui/widgets/visualization_widget.cpp +++ b/src/gui/widgets/visualization_widget.cpp @@ -29,6 +29,7 @@ #include "visualization_widget.h" #include "gui_templates.h" #include "MainWindow.h" +#include "Mixer.h" #include "embed.h" #include "engine.h" #include "tooltip.h" @@ -40,16 +41,16 @@ visualizationWidget::visualizationWidget( const QPixmap & _bg, QWidget * _p, visualizationTypes _vtype ) : QWidget( _p ), s_background( _bg ), - m_points( new QPointF[engine::getMixer()->framesPerPeriod()] ), + m_points( new QPointF[engine::mixer()->framesPerPeriod()] ), m_active( false ) { setFixedSize( s_background.width(), s_background.height() ); setAttribute( Qt::WA_OpaquePaintEvent, true ); - const fpp_t frames = engine::getMixer()->framesPerPeriod(); + const fpp_t frames = engine::mixer()->framesPerPeriod(); m_buffer = new sampleFrame[frames]; - engine::getMixer()->clearAudioBuffer( m_buffer, frames ); + engine::mixer()->clearAudioBuffer( m_buffer, frames ); toolTip::add( this, tr( "click to enable/disable visualization of " @@ -72,12 +73,11 @@ void visualizationWidget::updateAudioBuffer() { if( !engine::getSong()->isExporting() ) { - engine::getMixer()->lock(); - const surroundSampleFrame * c = engine::getMixer()-> - currentReadBuffer(); - const fpp_t fpp = engine::getMixer()->framesPerPeriod(); + engine::mixer()->lock(); + const surroundSampleFrame * c = engine::mixer()->currentReadBuffer(); + const fpp_t fpp = engine::mixer()->framesPerPeriod(); memcpy( m_buffer, c, sizeof( surroundSampleFrame ) * fpp ); - engine::getMixer()->unlock(); + engine::mixer()->unlock(); } } @@ -92,7 +92,7 @@ void visualizationWidget::setActive( bool _active ) connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) ); - connect( engine::getMixer(), + connect( engine::mixer(), SIGNAL( nextAudioBuffer() ), this, SLOT( updateAudioBuffer() ) ); } @@ -101,7 +101,7 @@ void visualizationWidget::setActive( bool _active ) disconnect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) ); - disconnect( engine::getMixer(), + disconnect( engine::mixer(), SIGNAL( nextAudioBuffer() ), this, SLOT( updateAudioBuffer() ) ); // we have to update (remove last waves), @@ -121,7 +121,7 @@ void visualizationWidget::paintEvent( QPaintEvent * ) if( m_active && !engine::getSong()->isExporting() ) { - float master_output = engine::getMixer()->masterGain(); + float master_output = engine::mixer()->masterGain(); int w = width()-4; const float half_h = -( height() - 6 ) / 3.0 * master_output - 1; int x_base = 2; @@ -131,10 +131,10 @@ void visualizationWidget::paintEvent( QPaintEvent * ) const fpp_t frames = - engine::getMixer()->framesPerPeriod(); + engine::mixer()->framesPerPeriod(); const float max_level = qMax( - mixer::peakValueLeft( m_buffer, frames ), - mixer::peakValueRight( m_buffer, frames ) ); + Mixer::peakValueLeft( m_buffer, frames ), + Mixer::peakValueRight( m_buffer, frames ) ); // and set color according to that... LmmsStyle::ColorRole levelColor; @@ -165,7 +165,7 @@ void visualizationWidget::paintEvent( QPaintEvent * ) { m_points[frame] = QPointF( x_base + (float) frame * xd, - y_base + ( mixer::clip( + y_base + ( Mixer::clip( m_buffer[frame][ch] ) * half_h ) ); } diff --git a/src/tracks/bb_track.cpp b/src/tracks/bb_track.cpp index 11a933bb2..96e920efa 100644 --- a/src/tracks/bb_track.cpp +++ b/src/tracks/bb_track.cpp @@ -33,7 +33,7 @@ #include "embed.h" #include "engine.h" #include "gui_templates.h" -#include "mixer.h" +#include "Mixer.h" #include "rename_dialog.h" #include "song.h" #include "song_editor.h" diff --git a/src/tracks/pattern.cpp b/src/tracks/pattern.cpp index 35a7e42d5..e713a6f90 100644 --- a/src/tracks/pattern.cpp +++ b/src/tracks/pattern.cpp @@ -33,6 +33,7 @@ #include #include +#include "AudioOutputContext.h" #include "pattern.h" #include "InstrumentTrack.h" #include "templates.h" @@ -735,15 +736,14 @@ patternFreezeThread::~patternFreezeThread() void patternFreezeThread::run() { - // create and install audio-sample-recorder + AudioOutputContext context( engine::mixer(), NULL, + engine::mixer()->defaultAudioOutputContext()->qualitySettings() ); + + // create and install AudioSampleRecorder bool b; - // we cannot create local copy, because at a later stage - // mixer::restoreAudioDevice(...) deletes old audio-dev and thus - // AudioSampleRecorder would be destroyed two times... - AudioSampleRecorder * freeze_recorder = new AudioSampleRecorder( - DEFAULT_CHANNELS, b, - engine::getMixer() ); - engine::getMixer()->setAudioDevice( freeze_recorder ); + AudioSampleRecorder freezeRecorder( DEFAULT_CHANNELS, b, &context ); + context.setAudioBackend( &freezeRecorder ); + engine::mixer()->setAudioOutputContext( &context ); // prepare stuff for playing correct things later engine::getSong()->playPattern( m_pattern, false ); @@ -761,7 +761,7 @@ void patternFreezeThread::run() while( ppp < m_pattern->length() && m_pattern->m_freezeAborted == false ) { - freeze_recorder->processNextBuffer(); + freezeRecorder.processNextBuffer(); m_statusDlg->setProgress( ppp * 100 / m_pattern->length() ); } m_statusDlg->setProgress( 100 ); @@ -769,7 +769,7 @@ void patternFreezeThread::run() while( engine::getMixer()->hasPlayHandles() && m_pattern->m_freezeAborted == false ) { - freeze_recorder->processNextBuffer(); + freezeRecorder.processNextBuffer(); } @@ -782,12 +782,13 @@ void patternFreezeThread::run() // create final sample-buffer if freezing was successful if( m_pattern->m_freezeAborted == false ) { - freeze_recorder->createSampleBuffer( + freezeRecorder.createSampleBuffer( &m_pattern->m_frozenPattern ); } // restore original audio-device - engine::getMixer()->restoreAudioDevice(); + engine::mixer()->setAudioOutputContext( + engine::mixer()->defaultAudioOutputContext() ); m_statusDlg->setProgress( -1 ); // we're finished From 03d3548ba1807ca8356133f0322be949c035ee9f Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 29 Nov 2009 15:29:46 +0100 Subject: [PATCH 30/34] ProjectRenderer: renamed OutputSettings to EncoderSettings + Doxygen comments Renamed the ProjectRenderer::OutputSettings structure to ProjectRenderer::EncoderSettings to better reflect its meaning. Additionally added some basic Doxygen comments. --- include/ProjectRenderer.h | 59 +++++++++++++++++++++------------ src/core/ProjectRenderer.cpp | 24 ++++++++------ src/core/main.cpp | 11 +++--- src/gui/ExportProjectDialog.cpp | 4 +-- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 68bcf1b7c..4c01d809a 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -31,38 +31,41 @@ class QTimer; +/*! \brief The ProjectRenderer class provides functionality to render current Song into a file. */ class ProjectRenderer : public QThread { Q_OBJECT public: + /*! Lists all supported output file formats. */ enum ExportFileFormats { - WaveFile, - OggFile, - Mp3File, - FlacFile, + WaveFile, /*!< Uncompressed WAV file */ + OggFile, /*!< Vorbis-encoded OGG file */ + Mp3File, /*!< MP3 file encoded via LAME */ + FlacFile, /*!< Free Lossless Audio Codec */ NumFileFormats } ; static const char * EFF_ext[]; + /*! Lists all supported sample type depths. */ enum Depths { - Depth_16Bit, - Depth_24Bit, - Depth_32Bit, + Depth_16Bit, /*!< 16 bit signed integer */ + Depth_24Bit, /*!< 24 bit floating point */ + Depth_32Bit, /*!< 32 bit floating point */ NumDepths } ; - struct OutputSettings + /*! Settings for the output file encoder. */ + struct EncoderSettings { - sample_rate_t samplerate; - bool vbr; - int bitrate; - Depths depth; + sample_rate_t samplerate; /*!< Desired output sample rate */ + bool vbr; /*!< Use variable bitrate encoding */ + int bitrate; /*!< Desired bitrate (kbps) */ + Depths depth; /*!< Depth of samples */ - OutputSettings( sample_rate_t _sr, bool _vbr, int _bitrate, - Depths _d ) : + EncoderSettings( sample_rate_t _sr, bool _vbr, int _bitrate, Depths _d ) : samplerate( _sr ), vbr( _vbr ), bitrate( _bitrate ), @@ -71,30 +74,40 @@ public: } } ; - - ProjectRenderer( const AudioOutputContext::QualitySettings & _qs, - const OutputSettings & _os, - ExportFileFormats _file_format, - const QString & _out_file ); + /*! \brief Constructs a ProjectRenderer object with given settings. + * + * \param qualitySettings The desired quality settings for the AudioOutputContext + * \param encoderSettings The desired settings for the output file encoder + * \param fileFormat One of the file formats listed in the ExportFileFormats enumeration + * \param outFile The output file name + */ + ProjectRenderer( const AudioOutputContext::QualitySettings & qualitySettings, + const EncoderSettings & encoderSettings, + ExportFileFormats fileFormat, + const QString & outFile ); virtual ~ProjectRenderer(); + /*! \brief Returns whether the ProjectRenderer was initialized properly. */ bool isReady() const { return m_fileDev != NULL; } - static ExportFileFormats getFileFormatFromExtension( - const QString & _ext ); + static ExportFileFormats getFileFormatFromExtension( const QString & _ext ); - void setConsoleUpdateTimer(QTimer * t) + void setConsoleUpdateTimer( QTimer * t ) { m_consoleUpdateTimer = t; } + public slots: + /*! \brief Sets according AudioOutputContext for Mixer and starts render thread. */ void startProcessing(); + /*! \brief Aborts the processing and cleans up resources. */ void abortProcessing(); + /*! \brief Prints current render progress to the console. */ void updateConsoleProgress(); @@ -103,6 +116,7 @@ signals: private slots: + /*! \brief Finalizes the render process and restores Mixer's AudioOutputContext. */ void finishProcessing(); @@ -120,6 +134,7 @@ private: } ; +/*! \brief Holds information about a certain file encoder. */ struct FileEncodeDevice { ProjectRenderer::ExportFileFormats m_fileFormat; diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 887f45f6e..d5bcc926f 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -79,9 +79,9 @@ const char * ProjectRenderer::EFF_ext[] = {"wav", "ogg", "mp3", "flac"}; ProjectRenderer::ProjectRenderer( const AudioOutputContext::QualitySettings & _qs, - const OutputSettings & _os, - ExportFileFormats _file_format, - const QString & _out_file ) : + const EncoderSettings & es, + ExportFileFormats fileFormat, + const QString & outFile ) : QThread( engine::getMixer() ), m_fileDev( NULL ), m_progress( 0 ), @@ -90,18 +90,18 @@ ProjectRenderer::ProjectRenderer( m_context = new AudioOutputContext( engine::getMixer(), NULL, _qs ); - if( __fileEncodeDevices[_file_format].m_getDevInst == NULL ) + if( __fileEncodeDevices[fileFormat].m_getDevInst == NULL ) { return; } bool success_ful = false; - m_fileDev = __fileEncodeDevices[_file_format].m_getDevInst( - _os.samplerate, DEFAULT_CHANNELS, success_ful, - _out_file, _os.vbr, - _os.bitrate, _os.bitrate - 64, _os.bitrate + 64, - _os.depth == Depth_32Bit ? 32 : - ( _os.depth == Depth_24Bit ? 24 : 16 ), + m_fileDev = __fileEncodeDevices[fileFormat].m_getDevInst( + es.samplerate, DEFAULT_CHANNELS, success_ful, + outFile, es.vbr, + es.bitrate, es.bitrate - 64, es.bitrate + 64, + es.depth == Depth_32Bit ? 32 : + ( es.depth == Depth_24Bit ? 24 : 16 ), m_context ); if( success_ful == false ) { @@ -119,6 +119,7 @@ ProjectRenderer::ProjectRenderer( ProjectRenderer::~ProjectRenderer() { delete m_fileDev; + delete m_context; } @@ -182,7 +183,8 @@ void ProjectRenderer::updateConsoleProgress() char buf[80]; char prog[cols+1]; - if( m_fileDev == NULL ){ + if( m_fileDev == NULL ) + { qWarning("Error occured. Aborting render."); m_consoleUpdateTimer->stop(); delete m_consoleUpdateTimer; diff --git a/src/core/main.cpp b/src/core/main.cpp index ec5b46b63..f9aade288 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -22,7 +22,6 @@ * */ - #include #include #include @@ -128,8 +127,8 @@ int main( int argc, char * * argv ) AudioOutputContext::QualitySettings qs( AudioOutputContext::QualitySettings::Preset_HighQuality ); - ProjectRenderer::OutputSettings os( 44100, false, 160, - ProjectRenderer::Depth_16Bit ); + ProjectRenderer::EncoderSettings es( 44100, false, 160, + ProjectRenderer::Depth_16Bit ); ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; @@ -251,7 +250,7 @@ int main( int argc, char * * argv ) sample_rate_t sr = QString( argv[i + 1] ).toUInt(); if( sr >= 44100 && sr <= 192000 ) { - os.samplerate = sr; + es.samplerate = sr; } else { @@ -268,7 +267,7 @@ int main( int argc, char * * argv ) int br = QString( argv[i + 1] ).toUInt(); if( br >= 64 && br <= 384 ) { - os.bitrate = br; + es.bitrate = br; } else { @@ -515,7 +514,7 @@ int main( int argc, char * * argv ) if( !render_out.isEmpty() ) { // create renderer - ProjectRenderer * r = new ProjectRenderer( qs, os, eff, + ProjectRenderer * r = new ProjectRenderer( qs, es, eff, render_out + QString( ProjectRenderer::EFF_ext[eff] ) ); QCoreApplication::instance()->connect( r, SIGNAL( finished() ), SLOT( quit() ) ); diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index b8c8ccf8b..3d898e93e 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -155,13 +155,13 @@ void ExportProjectDialog::startBtnClicked() ui->sampleExactControllersCB->isChecked(), ui->aliasFreeOscillatorsCB->isChecked() ); - ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( + ProjectRenderer::EncoderSettings es = ProjectRenderer::EncoderSettings( ui->samplerateCB->currentText().section( " ", 0, 0 ).toUInt(), false, ui->bitrateCB->currentText().section( " ", 0, 0 ).toUInt(), static_cast( ui->depthCB->currentIndex() ) ); - m_renderer = new ProjectRenderer( qs, os, ft, m_fileName ); + m_renderer = new ProjectRenderer( qs, es, ft, m_fileName ); if( m_renderer->isReady() ) { updateTitleBar( 0 ); From c9802d8a26ed19e3843daf437a8137d12a010964 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 29 Nov 2009 15:37:42 +0100 Subject: [PATCH 31/34] ProjectRenderer: start thread with normal priority There's no need to start the ProjectRenderer thread with high priority anymore as the actual rendering is done on the other side of the FIFO. The ProjectRenderer just waits for new data in the FIFO and encodes them as they arrive. --- src/core/ProjectRenderer.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index d5bcc926f..3ee3fad79 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -22,7 +22,6 @@ * */ - #include #include @@ -157,11 +156,7 @@ void ProjectRenderer::startProcessing() // called immediately engine::mixer()->setAudioOutputContext( m_context ); - start( -#ifndef LMMS_BUILD_WIN32 - QThread::HighPriority -#endif - ); + start(); } } From 9bb3ab5f16c4585975ec61413133da9f70093518 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 29 Nov 2009 23:57:30 +0100 Subject: [PATCH 32/34] ProjectRenderer: lock Mixer while calling Song::{start,stop}Export() We have to lock Mixer when touching Song's state via Song::startExport() and Song::stopExport() in ProjectRenderer::run() as the FIFO writer thread may call Mixer::renderNextBuffer() (which calls Song::doActions()) simultaneously. Fixes a random segfault when exporting project. This was a new bug as the ProjectRenderer does not operate FIFO-less anymore. --- src/core/Mixer.cpp | 3 +-- src/core/ProjectRenderer.cpp | 11 +++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 14313505b..d7e93adc5 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -495,8 +495,7 @@ sampleFrameA * Mixer::renderNextBuffer() MicroTimer timer; static song::playPos last_metro_pos = -1; - song::playPos p = engine::getSong()->getPlayPos( - song::Mode_PlayPattern ); + song::playPos p = engine::getSong()->getPlayPos( song::Mode_PlayPattern ); if( engine::getSong()->playMode() == song::Mode_PlayPattern && engine::getPianoRoll()->isRecording() == true && p != last_metro_pos && p.getTicks() % diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 3ee3fad79..1633a4ec1 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -59,9 +59,9 @@ FileEncodeDevice __fileEncodeDevices[] = ".mp3", &AudioFileMp3::getInst }, { ProjectRenderer::FlacFile, QT_TRANSLATE_NOOP( "ProjectRenderer", "FLAC File (*.flac)" ), - ".flac", + ".flac", #ifdef LMMS_HAVE_FLAC - &AudioFileFlac::getInst + &AudioFileFlac::getInst #else NULL #endif @@ -219,7 +219,12 @@ void ProjectRenderer::run() #endif #endif + // have to lock Mixer when touching Song's state as the FIFO writer thread + // may call Mixer::renderNextBuffer() (which calls Song::doActions()) + // simultaneously + engine::mixer()->lock(); engine::getSong()->startExport(); + engine::mixer()->unlock(); song::playPos & pp = engine::getSong()->getPlayPos( song::Mode_PlaySong ); @@ -239,7 +244,9 @@ void ProjectRenderer::run() } } + engine::mixer()->lock(); engine::getSong()->stopExport(); + engine::mixer()->unlock(); } From c11c228437c4c49226048144ff3b114117a49372 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 30 Nov 2009 01:37:53 +0100 Subject: [PATCH 33/34] LV2Browser: fixed compilation I somehow forgot to migrate LV2Browser plugin while reworking Mixer. Should compile again now. --- plugins/lv2_browser/lv2_description.cpp | 7 ++++--- plugins/lv2_browser/lv2_port_dialog.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/lv2_browser/lv2_description.cpp b/plugins/lv2_browser/lv2_description.cpp index ffb4b1480..f0c9175b7 100644 --- a/plugins/lv2_browser/lv2_description.cpp +++ b/plugins/lv2_browser/lv2_description.cpp @@ -33,9 +33,10 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" +#include "AudioOutputContext.h" +#include "Mixer.h" #include "engine.h" -#include "mixer.h" @@ -81,7 +82,7 @@ lv2Description::lv2Description( QWidget * _parent, if( description->type == _type && ( _type != VALID || - description->inputChannels <= engine::getMixer()->audioDev()->channels() + description->inputChannels <= engine::mixer()->audioOutputContext()->audioBackend()->channels() ) ) { diff --git a/plugins/lv2_browser/lv2_port_dialog.cpp b/plugins/lv2_browser/lv2_port_dialog.cpp index 295a376a2..5c0e37887 100644 --- a/plugins/lv2_browser/lv2_port_dialog.cpp +++ b/plugins/lv2_browser/lv2_port_dialog.cpp @@ -30,7 +30,7 @@ #include "embed.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" lv2PortDialog::lv2PortDialog( const lv2_key_t & _key ) From 48d7e7cf50e0bb38abfdc679745ec9034ba6e2d3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Nov 2009 18:54:58 -0700 Subject: [PATCH 34/34] Bugfix: crash dealing with clearing the project FxMixerView was being cleared before fxMixer which caused a crash. Fixed. --- src/core/song.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/song.cpp b/src/core/song.cpp index 0d2d95558..9b421a052 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -729,6 +729,8 @@ void song::clearProject() } engine::getMixer()->lock(); + engine::fxMixer()->clear(); + if( engine::getBBEditor() ) { engine::getBBEditor()->clearAllTracks(); @@ -737,15 +739,18 @@ void song::clearProject() { engine::getSongEditor()->clearAllTracks(); } + + // depends on the fxMixer being cleared if( engine::fxMixerView() ) { engine::fxMixerView()->clear(); } + QCoreApplication::sendPostedEvents(); engine::getBBTrackContainer()->clearAllTracks(); clearAllTracks(); - engine::fxMixer()->clear(); + if( engine::getAutomationEditor() ) {