From 45c4b7b824de19fbdc3bbbc3907dd6bb81151483 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Sep 2009 17:53:59 -0700 Subject: [PATCH 01/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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/43] 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 58f53d9f5b78454cb6ee07bdbaf8d0400636ad30 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 14 Nov 2009 00:32:54 +0100 Subject: [PATCH 28/43] PreferencesDialog: initial draft Here's an initial draft for the new preferences dialog. The basics of the GUI are done, however there's absolutely no functionality yet. --- data/themes/default/folder-64.png | Bin 0 -> 1216 bytes .../default/preferences-desktop-sound.png | Bin 0 -> 4930 bytes data/themes/default/preferences-system.png | Bin 0 -> 6296 bytes data/themes/default/setup-midi.png | Bin 0 -> 8289 bytes data/themes/default/setup-plugins.png | Bin 0 -> 4236 bytes data/themes/default/style.css | 11 + include/MainWindow.h | 1 + include/PreferencesDialog.h | 43 + src/gui/Forms/PreferencesDialog.ui | 776 ++++++++++++++++++ src/gui/MainWindow.cpp | 13 + src/gui/PreferencesDialog.cpp | 55 ++ 11 files changed, 899 insertions(+) create mode 100644 data/themes/default/folder-64.png create mode 100644 data/themes/default/preferences-desktop-sound.png create mode 100644 data/themes/default/preferences-system.png create mode 100755 data/themes/default/setup-midi.png create mode 100644 data/themes/default/setup-plugins.png create mode 100644 include/PreferencesDialog.h create mode 100644 src/gui/Forms/PreferencesDialog.ui create mode 100644 src/gui/PreferencesDialog.cpp diff --git a/data/themes/default/folder-64.png b/data/themes/default/folder-64.png new file mode 100644 index 0000000000000000000000000000000000000000..6a93840b0dd113ebc1643ba2d17527fcf8a8d931 GIT binary patch literal 1216 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 AjQ{`u literal 0 HcmV?d00001 diff --git a/data/themes/default/preferences-system.png b/data/themes/default/preferences-system.png new file mode 100644 index 0000000000000000000000000000000000000000..9ea5cec4472df7f11ead994b9a3248251f38bdc7 GIT binary patch literal 6296 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 + * + * 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 _PREFERENCES_DIALOG_H +#define _PREFERENCES_DIALOG_H + +#include + +namespace Ui { class PreferencesDialog; } + +class PreferencesDialog : public QDialog +{ +public: + PreferencesDialog(); + +private: + Ui::PreferencesDialog * ui; + +} ; + +#endif + diff --git a/src/gui/Forms/PreferencesDialog.ui b/src/gui/Forms/PreferencesDialog.ui new file mode 100644 index 000000000..f9df36a99 --- /dev/null +++ b/src/gui/Forms/PreferencesDialog.ui @@ -0,0 +1,776 @@ + + + PreferencesDialog + + + + 0 + 0 + 577 + 478 + + + + Preferences + + + + + + + + + + 0 + 0 + + + + + 101 + 470 + + + + + 101 + 16777215 + + + + ::item { width:87; } + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + 64 + 64 + + + + 4 + + + QListView::IconMode + + + -1 + + + + General + + + ItemIsSelectable|ItemIsUserCheckable|ItemIsEnabled + + + + + Directories + + + ItemIsSelectable|ItemIsUserCheckable|ItemIsEnabled + + + + + Audio + + + ItemIsSelectable|ItemIsUserCheckable|ItemIsEnabled + + + + + MIDI + + + ItemIsSelectable|ItemIsUserCheckable|ItemIsEnabled + + + + + Plugins + + + ItemIsSelectable|ItemIsUserCheckable|ItemIsEnabled + + + + + + + + 0 + + + + + 16 + + + + + + 14 + 75 + true + + + + General settings + + + + + + + User interface + + + + + + Enable tooltips + + + + + + + Show volume as dbV + + + + + + + Show welcome screen + + + + + + + + + + Online resources + + + + 10 + + + + + Enable online resources + + + + + + + Enable uploads + + + + + + + Username + + + + + + + false + + + + + + + Password + + + + + + + false + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 16 + + + + + + 14 + 75 + true + + + + Audio settings + + + + + + + General + + + + 10 + + + 10 + + + + + Buffer size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Sample rate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 44100 Hz + + + + + 48000 Hz + + + + + 88200 Hz + + + + + 96000 Hz + + + + + + + + + 0 + 0 + + + + 3 + + + + 32 + + + + + 64 + + + + + 128 + + + + + 256 (default) + + + + + 512 + + + + + 1024 + + + + + 2048 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + Audio engine + + + + 10 + + + 10 + + + + + Audio engine + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + Device + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + 16 + + + + + + 14 + 75 + true + + + + MIDI settings + + + + + + + MIDI engine + + + + 10 + + + 10 + + + + + MIDI engine + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + Device + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + 0 + 100 + + + + MIDI remote control + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 16 + + + + + + 14 + 75 + true + + + + Plugins settings + + + + + + + + 24 + 24 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PreferencesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PreferencesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + enableUploadsCheckBox + toggled(bool) + usernameEdit + setEnabled(bool) + + + 299 + 253 + + + 336 + 283 + + + + + enableUploadsCheckBox + toggled(bool) + passwordEdit + setEnabled(bool) + + + 299 + 253 + + + 336 + 317 + + + + + enableOnlineResourcesCheckBox + toggled(bool) + enableUploadsCheckBox + setEnabled(bool) + + + 299 + 226 + + + 299 + 253 + + + + + configPageSelector + currentRowChanged(int) + configPages + setCurrentIndex(int) + + + 54 + 238 + + + 340 + 224 + + + + + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index dfb101be6..453193dbd 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -51,6 +51,7 @@ #include "engine.h" #include "FxMixerView.h" #include "AboutDialog.h" +#include "PreferencesDialog.h" #include "ControllerRackView.h" #include "plugin_browser.h" #include "SideBar.h" @@ -257,6 +258,10 @@ void MainWindow::finalize() edit_menu->addAction( embed::getIconPixmap( "setup_general" ), tr( "Settings" ), this, SLOT( showSettingsDialog() ) ); + edit_menu->addSeparator(); + edit_menu->addAction( embed::getIconPixmap( "setup_general" ), + tr( "Preferences (premature dialog)" ), + this, SLOT( showPreferencesDialog() ) ); m_toolsMenu = new QMenu( this ); @@ -1050,6 +1055,14 @@ void MainWindow::showSettingsDialog() +void MainWindow::showPreferencesDialog() +{ + PreferencesDialog().exec(); +} + + + + void MainWindow::aboutLMMS() { AboutDialog().exec(); diff --git a/src/gui/PreferencesDialog.cpp b/src/gui/PreferencesDialog.cpp new file mode 100644 index 000000000..6214051f8 --- /dev/null +++ b/src/gui/PreferencesDialog.cpp @@ -0,0 +1,55 @@ +/* + * PreferencesDialog.cpp - implementation of PreferencesDialog + * + * 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 "PreferencesDialog.h" +#include "embed.h" +#include "engine.h" +#include "MainWindow.h" +#include "ui_PreferencesDialog.h" + + +PreferencesDialog::PreferencesDialog() : + QDialog( engine::mainWindow() ), + ui( new Ui::PreferencesDialog ) +{ + ui->setupUi( this ); + + // set up icons in page selector view on the left side + static const char * icons[] = { + "preferences-system", + "folder-64", + "preferences-desktop-sound", + "setup-midi", + "setup-plugins" + } ; + for( int i = 0; i < qMin( sizeof( icons ), + ui->configPageSelector->count() ); ++i ) + { + ui->configPageSelector->item( i )->setIcon( + embed::getIconPixmap( icons[i] ) ); + } +} + + + From 953522f34a7fb751d487abe0b3c05afe8603c36d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 13 Nov 2009 18:43:08 -0700 Subject: [PATCH 29/43] 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 168805745eb94b99ec211b5cb547a3417ed4dbe5 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Fri, 20 Nov 2009 22:28:47 +0100 Subject: [PATCH 30/43] AudioPulseAudio: fixed improper shutdown + free allocated resources There have been some problems with the threading logic in the AudioPulseAudio backend resulting in an endless loop when quitting LMMS. Furthermore allocated PulseAudio resources were not freed properly. Thanks to Armin Kazmi for pointing out this issue. --- include/AudioPulseAudio.h | 1 + src/core/audio/AudioPulseAudio.cpp | 48 ++++++++++++++++-------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index 99cc9e462..33a11da1f 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -79,6 +79,7 @@ private: virtual void applyQualitySettings(); virtual void run(); + volatile bool m_quit; bool m_convertEndian; diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 0e6ea6871..14a0a3b86 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -51,6 +51,7 @@ AudioPulseAudio::AudioPulseAudio( bool & _success_ful, mixer * _mixer ) : DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_s( NULL ), + m_quit( false ), m_convertEndian( false ) { _success_ful = false; @@ -68,11 +69,6 @@ AudioPulseAudio::AudioPulseAudio( bool & _success_ful, mixer * _mixer ) : AudioPulseAudio::~AudioPulseAudio() { stopProcessing(); - - if( m_s != NULL ) - { - pa_stream_unref( m_s ); - } } @@ -195,38 +191,45 @@ static void context_state_callback(pa_context *c, void *userdata) void AudioPulseAudio::run() { - pa_mainloop * m = NULL; - - - if (!(m = pa_mainloop_new())) { + pa_mainloop * mainLoop = pa_mainloop_new(); + if( !mainLoop ) + { qCritical( "pa_mainloop_new() failed.\n" ); return; } - pa_mainloop_api * mainloop_api = pa_mainloop_get_api(m); + pa_mainloop_api * mainloop_api = pa_mainloop_get_api( mainLoop ); - pa_context *context = pa_context_new(mainloop_api, "lmms"); + pa_context *context = pa_context_new( mainloop_api, "lmms" ); if ( context == NULL ) { - qCritical( "pa_context_new() failed." ); + qCritical( "pa_context_new() failed." ); return; } - pa_context_set_state_callback(context, context_state_callback, this ); - /* Connect the context */ - pa_context_connect(context, NULL, (pa_context_flags) 0, NULL); + pa_context_set_state_callback( context, context_state_callback, this ); + // connect the context + pa_context_connect( context, NULL, (pa_context_flags) 0, NULL ); - int ret; - /* Run the main loop */ - if (pa_mainloop_run(m, &ret) < 0) + // run the main loop + int ret = 0; + m_quit = false; + while( m_quit == false && pa_mainloop_iterate( mainLoop, 1, &ret ) >= 0 ) { - qCritical( "pa_mainloop_run() failed.\n" ); } + + pa_stream_disconnect( m_s ); + pa_stream_unref( m_s ); + + pa_context_disconnect( context ); + pa_context_unref( context ); + + pa_mainloop_free( mainLoop ); } -void AudioPulseAudio::streamWriteCallback(pa_stream *s, size_t length) +void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) { const fpp_t fpp = getMixer()->framesPerPeriod(); sampleFrameA * temp = CPU::allocFrames( fpp ); @@ -234,12 +237,13 @@ void AudioPulseAudio::streamWriteCallback(pa_stream *s, size_t length) sizeof(Sint16) ); size_t fd = 0; - while( fd < length/4 ) + while( fd < length/4 && m_quit == false ) { const fpp_t frames = getNextBuffer( temp ); if( !frames ) { - return; + m_quit = true; + break; } int bytes = CPU::convertToS16( temp, (intSampleFrameA *) pcmbuf, From 92047f5e9e03ed564410ab177e0a489c311c78fc Mon Sep 17 00:00:00 2001 From: Armin Kazmi Date: Fri, 20 Nov 2009 22:47:35 +0100 Subject: [PATCH 31/43] AudioPulseAudio: fixed latency and underrun problems Try to adjust latency of PulseAudio according to our settings of mixer so it does not have such a bad latency anymore. Furthermore force PulseAudio to play silence instead of rewinding streams in case of underruns. --- src/core/audio/AudioPulseAudio.cpp | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 14a0a3b86..539b1fba5 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -35,6 +35,7 @@ #include "gui_templates.h" #include "templates.h" #include "Cpu.h" +#include "engine.h" static void stream_write_callback(pa_stream *s, size_t length, void *userdata) @@ -169,11 +170,29 @@ static void context_state_callback(pa_context *c, void *userdata) _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 ); - pa_stream_connect_playback( _this->m_s, NULL, NULL, - (pa_stream_flags) 0, - pa_cvolume_set( &cv, _this->m_sampleSpec.channels, - PA_VOLUME_NORM ), - NULL ); + + pa_buffer_attr buffer_attr; + + buffer_attr.maxlength = (uint32_t)(-1); + + // play silence in case of buffer underun instead of using default rewind + buffer_attr.prebuf = 0; + + buffer_attr.minreq = (uint32_t)(-1); + buffer_attr.fragsize = (uint32_t)(-1); + + double latency = (double)( engine::getMixer()->framesPerPeriod() ) / + (double)_this->sampleRate(); + + // ask PulseAudio for the desired latency (which might not be approved) + buffer_attr.tlength = pa_usec_to_bytes( latency * PA_USEC_PER_MSEC, + &_this->m_sampleSpec ); + + pa_stream_connect_playback( _this->m_s, NULL, &buffer_attr, + PA_STREAM_ADJUST_LATENCY, + pa_cvolume_set( &cv, _this->m_sampleSpec.channels, + PA_VOLUME_NORM ), + NULL ); break; } From 79566abf1be79c1ae054ca80563c83bf731b4737 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 23 Nov 2009 00:19:08 +0100 Subject: [PATCH 32/43] Added Doxygen configuration file Added Doxygen configuration file so one can instantly test whether he added proper comments in Doxygen style. --- Doxyfile | 1514 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1514 insertions(+) create mode 100644 Doxyfile diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 000000000..0586f6041 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1514 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = LMMS + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = src/core src/gui src/tracks include/ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.cpp *.c *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = YES + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES From 74bd0ab9af8a2f389fb75f3c8fa8c6103049dfc3 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 24 Nov 2009 00:49:26 +0100 Subject: [PATCH 33/43] ResourceAction: added Doxygen comments Documented the ResourceAction class and all its members. --- include/ResourceAction.h | 57 ++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/include/ResourceAction.h b/include/ResourceAction.h index 0a627ceef..81ba85a0e 100644 --- a/include/ResourceAction.h +++ b/include/ResourceAction.h @@ -30,36 +30,59 @@ class trackContainer; class ResourceItem; +/*! \brief The ResourceAction class provides centralized functionality for all actions + * related to a ResourceItem. + * + * These actions are for example loading projects, samples, presets, + * plugin-specific presets etc. Using this class we can avoid duplicated + * functionality in ResourcePreviewer, ResourceBrowser, TrackContainerView, + * InstrumentTrack & Co. + */ + class ResourceAction { public: + /*! Lists all supported actions. */ enum Actions { - EditProperties, - LoadProject, - LoadInNewTrackSongEditor, - LoadInNewTrackBBEditor, - LoadInActiveInstrumentTrack, - DownloadIntoCollection, - UploadToWWW, - DeleteLocalResource, - ImportFile, + EditProperties, /*!< Open a dialog to edit properties of the ResourceItem */ + LoadProject, /*!< Load the project represented by the ResourceItem */ + LoadInNewTrackSongEditor, /*!< Load preset, sample etc. in a new track in Song Editor */ + LoadInNewTrackBBEditor, /*!< Load preset, sample etc. in a new track in BB Editor */ + LoadInActiveInstrumentTrack,/*!< Load preset, sample etc. in active instrument track */ + DownloadIntoCollection, /*!< Download the resource into local collection */ + UploadToWWW, /*!< Upload the resource to Web */ + DeleteLocalResource, /*!< Delete local resource (=file) */ + ImportFile, /*!< Try to import the resource via import filter plugins */ NumActions } ; typedef Actions Action; - ResourceAction( const ResourceItem * _item, - Action _action = NumActions ) : - m_action( _action ), - m_item( _item ) + /*! \brief Constructs a ResourceAction object. + * \param item The ResourceItem the action is about + * \param action An optional action from the Action enumeration used for the defaultTrigger() method + */ + ResourceAction( const ResourceItem * item, + Action action = NumActions ) : + m_action( action ), + m_item( item ) { } bool loadProject(); - bool loadByPlugin( InstrumentTrack * _target ); - bool loadPreset( InstrumentTrack * _target ); - bool importProject( trackContainer * _target ); - // most actions can be triggered without any further information + bool loadByPlugin( InstrumentTrack * target ); + bool loadPreset( InstrumentTrack * target ); + bool importProject( trackContainer * target ); + + /*! \brief Triggers the action passed to the constructor without any further options. + * + * Most actions can be triggered without any further information. + * This allows simple but powerful code constructs: + * \code + * ResourceAction( myItem, ResourceAction::LoadProject ).defaultTrigger(); + * \endcode + * \return true if the operation succeeded, otherwise false. + */ bool defaultTrigger(); From 9cabd559dcdd1df8c05f0194af2a06b85f8d387a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 24 Nov 2009 00:50:10 +0100 Subject: [PATCH 34/43] ResourceItem: added Doxygen comments Documented the ResourceItem class and all its members. --- include/ResourceItem.h | 109 ++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 33 deletions(-) diff --git a/include/ResourceItem.h b/include/ResourceItem.h index a3ca8795e..2da1436ba 100644 --- a/include/ResourceItem.h +++ b/include/ResourceItem.h @@ -35,59 +35,96 @@ #include "TreeRelation.h" +/*! \brief The ResourceItem class provides information about a local/remote file/directory. + * + * All relevant properties of a file or directory are stored within a + * ResourceItem and can be accessed easily. All resources are identified by + * a unique hash (based on file content or (absolute) directory name). + * + * ResourceItems are managed within a ResourceDB. Reading and writing resource + * data is abstracted into the ResourceProvider class. + * + * The ResourceItem class does not provide any actual functionality. + * Use ResourceAction or more high level classes like ResourcePreviewer and + * ResourceBrowser. + */ + class EXPORT ResourceItem { public: + /*! A relation specifies how ResourceItems are organized among each other. + * See documentation of TreeRelation for details. */ typedef TreeRelation Relation; + /*! Lists all supported base directories for ResourceItems. */ enum BaseDirectories { - BaseRoot, - BaseWorkingDir, - BaseDataDir, - BaseHome, - BaseURL, + BaseRoot, /*!< Item is relative to root directory */ + BaseWorkingDir, /*!< Item is relative to working directory */ + BaseDataDir, /*!< Item is relative to LMMS' data directory */ + BaseHome, /*!< Item is relative to user's home directory */ + BaseURL, /*!< Item is relative to the URL of the ResourceProvider */ NumBaseDirectories } ; typedef BaseDirectories BaseDirectory; + /*! Lists all supported ResourceItem types. */ enum Types { - TypeUnknown, - TypeDirectory, - TypeSample, - TypePreset, - TypePluginSpecificResource, - TypeProject, - TypeMidiFile, - TypeForeignProject, - TypePlugin, - TypeImage, + TypeUnknown, /*!< No known resource type */ + TypeDirectory, /*!< Item is a directory */ + TypeSample, /*!< Item is a supported sample file */ + TypePreset, /*!< Item is a LMMS-specific preset */ + TypePluginSpecificResource, /* Item is a file supported by one of the available plugins */ + TypeProject, /*!< Item is a LMMS project */ + TypeMidiFile, /*!< Item is a MIDI file (and can be imported via MIDI import filter) */ + TypeForeignProject, /*!< Item is any other kind of project which can be imported via an ImportFilter plugin */ + TypePlugin, /*!< Item is a Plugin binary */ + TypeImage, /*!< Item is an image */ NumTypes } ; typedef Types Type; - ResourceItem( ResourceProvider * _provider, - const QString & _name, + /*! \brief Constructs a ResourceItem object. + * \param provider The provider this item belongs to + * \param name The name used to identify the item towards the user + * \param baseDir The base directory this item is relative to + * \param path The path from base directory to this item + * \param hash A unique hash based on file content, pass QString::null to compute it automatically + * \param author A string describing the author + * \param tags A comma-separated list of tags for this item + * \param size The size of the item, pass -1 to compute it automatically + * \param lastMod The date and time of the last modification of the item + */ + ResourceItem( ResourceProvider * provider, + const QString & name, Type _type, - BaseDirectory _base_dir = BaseWorkingDir, - const QString & _path = QString::null, - const QString & _hash = QString::null, - const QString & _author = QString::null, - const QString & _tags = QString::null, - int _size = -1, - const QDateTime & _last_mod = QDateTime() ); - // copy constructor - ResourceItem( const ResourceItem & _item ); + BaseDirectory baseDir = BaseWorkingDir, + const QString & path = QString::null, + const QString & hash = QString::null, + const QString & author = QString::null, + const QString & tags = QString::null, + int size = -1, + const QDateTime & lastMod = QDateTime() ); + /*! \brief Copy constructor. */ + ResourceItem( const ResourceItem & item ); - inline void setHidden( bool _h, const QAbstractItemModel * _model ) + /*! \brief Sets hidden property for the given item model + * \param hidden A boolean specifying the desired value + * \param model A pointer to a QAbstractItemModel (allows to use this item + * for multiple models and views) */ + inline void setHidden( bool hidden, const QAbstractItemModel * model ) { - m_hidden[_model] = _h; + m_hidden[model] = hidden; } - inline bool isHidden( const QAbstractItemModel * _model ) const + /*! \brief Returns whether item is hidden for the given item model + * \param model A pointer to a QAbstractItemModel (allows to use this item + * for multiple models and views) + * \return true if the item is hidden, false otherwise */ + inline bool isHidden( const QAbstractItemModel * model ) const { - return m_hidden[_model]; + return m_hidden[model]; } @@ -206,6 +243,8 @@ public: return m_provider->dataSize( this ); } + /*! \brief Fetch data (contents) of the resource via ResourceProvider. + * \return QByteArray with complete contents of the resource */ QByteArray fetchData( int _maxSize = -1 ) const { return m_provider->fetchData( this ); @@ -213,15 +252,19 @@ public: void reload(); - // returns true if all given keywords match name, tags etc. + /*! \brief Returns, whether keywords match certain properties. + * \return true if all given keywords match name, tags etc. */ bool keywordMatch( const QStringList & _keywords ) const; - // return true, if given ResourceItem is equal + /*! \brief Tests for equality with another ResourceItem. + * \return true, if given ResourceItem is equal */ bool operator==( const ResourceItem & _other ) const; - // rates equality with given item + /*! \brief Rates equality with another ResourceItem. + * \return An integer specifying how close the two ResourceItems are (between 0 and about 250) */ int equalityLevel( const ResourceItem & _other ) const; + /*! \brief Guesses resource type by various criteria */ Type guessType() const; static const char * mimeKey() From c517f1fa5a1ff2af04b4d23af748518f7f78a97c Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 24 Nov 2009 12:40:56 +0100 Subject: [PATCH 35/43] Mixer, FifoBuffer, AudioDevice: removed memory allocation/free cycles There's really no need to allocate a buffer each period, push it to the FifoBuffer and free it when fetching the buffer in AudioDevice::getNextBuffer(). Instead keep the pointer in FifoBuffer's pool and reuse it. --- include/fifo_buffer.h | 12 +++++++++++- src/core/audio/AudioDevice.cpp | 5 ----- src/core/mixer.cpp | 6 +++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/fifo_buffer.h b/include/fifo_buffer.h index 4bf908611..22dbcca80 100644 --- a/include/fifo_buffer.h +++ b/include/fifo_buffer.h @@ -41,6 +41,10 @@ public: m_size( _size ) { m_buffer = new T[_size]; + for( int i = 0; i < m_size; ++i ) + { + m_buffer[i] = NULL; + } m_readerSem.acquire( _size ); } @@ -50,9 +54,15 @@ public: m_readerSem.release( m_size ); } - void write( T _element ) + T nextWriteBuffer() { m_writerSem.acquire(); + T buf = m_buffer[m_writerIndex]; + return buf; + } + + void write( T _element ) + { m_buffer[m_writerIndex++] = _element; m_writerIndex %= m_size; m_readerSem.release(); diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index ba38a227f..18ad1a668 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -104,11 +104,6 @@ fpp_t AudioDevice::getNextBuffer( sampleFrameA * _ab ) // release lock unlock(); - if( getMixer()->hasFifoWriter() ) - { - CPU::freeFrames( b ); - } - return frames; } diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index b82d6886f..310ec289a 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -1151,8 +1151,12 @@ void mixer::fifoWriter::run() const fpp_t frames = m_mixer->framesPerPeriod(); while( m_writing ) { - sampleFrameA * buffer = CPU::allocFrames( frames ); const sampleFrameA * b = m_mixer->renderNextBuffer(); + sampleFrameA * buffer = m_fifo->nextWriteBuffer(); + if( buffer == NULL ) + { + buffer = CPU::allocFrames( frames ); + } CPU::memCpy( buffer, b, frames * sizeof( sampleFrameA ) ); m_fifo->write( buffer ); } From dc561a07e7677f2922a38c8cdf7ea4158686197d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Thu, 26 Nov 2009 01:36:36 +0100 Subject: [PATCH 36/43] Revert "Mixer, FifoBuffer, AudioDevice: removed memory allocation/free cycles" This reverts commit c517f1fa5a1ff2af04b4d23af748518f7f78a97c. The commit was not very helpful and introduced new xrun problems. Instead I'll be rewriting the part of LMMS where Mixer, Mixer's quality settings, Buffer FIFO and AudioDevice are sticked together. --- include/fifo_buffer.h | 12 +----------- src/core/audio/AudioDevice.cpp | 5 +++++ src/core/mixer.cpp | 6 +----- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/include/fifo_buffer.h b/include/fifo_buffer.h index 22dbcca80..4bf908611 100644 --- a/include/fifo_buffer.h +++ b/include/fifo_buffer.h @@ -41,10 +41,6 @@ public: m_size( _size ) { m_buffer = new T[_size]; - for( int i = 0; i < m_size; ++i ) - { - m_buffer[i] = NULL; - } m_readerSem.acquire( _size ); } @@ -54,15 +50,9 @@ public: m_readerSem.release( m_size ); } - T nextWriteBuffer() - { - m_writerSem.acquire(); - T buf = m_buffer[m_writerIndex]; - return buf; - } - void write( T _element ) { + m_writerSem.acquire(); m_buffer[m_writerIndex++] = _element; m_writerIndex %= m_size; m_readerSem.release(); diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index 18ad1a668..ba38a227f 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -104,6 +104,11 @@ fpp_t AudioDevice::getNextBuffer( sampleFrameA * _ab ) // release lock unlock(); + if( getMixer()->hasFifoWriter() ) + { + CPU::freeFrames( b ); + } + return frames; } diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index 310ec289a..b82d6886f 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -1151,12 +1151,8 @@ void mixer::fifoWriter::run() const fpp_t frames = m_mixer->framesPerPeriod(); while( m_writing ) { + sampleFrameA * buffer = CPU::allocFrames( frames ); const sampleFrameA * b = m_mixer->renderNextBuffer(); - sampleFrameA * buffer = m_fifo->nextWriteBuffer(); - if( buffer == NULL ) - { - buffer = CPU::allocFrames( frames ); - } CPU::memCpy( buffer, b, frames * sizeof( sampleFrameA ) ); m_fifo->write( buffer ); } From 63cb220090714c599393dac3d55b5edfc60df7c1 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Thu, 26 Nov 2009 01:41:57 +0100 Subject: [PATCH 37/43] ResourceDB: added Doxygen comments Documented the ResourceDB class and all its members. --- include/ResourceDB.h | 70 +++++++++++++++++++++++------- src/core/LocalResourceProvider.cpp | 2 +- src/core/ResourceDB.cpp | 12 ++--- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/include/ResourceDB.h b/include/ResourceDB.h index f7711c639..29be71e98 100644 --- a/include/ResourceDB.h +++ b/include/ResourceDB.h @@ -34,20 +34,45 @@ #include "ResourceItem.h" +/*! \brief The ResourceDB class organizes and caches ResourceItems. + * + * ResourceItem sets are organized in the ResourceDB::ItemHashMap. This + * allows fast lookup of ResourceItems by hash string. ResourceItems are added + * and removed by a ResourceProvider. + * + * The ResourceItem array can be cached to a file and restored later. This is + * essential as otherwise e.g. the LocalResourceProvider would have to re-read + * all directories and files and recompute hash strings based on file contents. + * + * One can also descend the hierarchical ResourceItem tree by starting from + * ResourceDB::topLevelNode(). + */ + class EXPORT ResourceDB : public QObject { Q_OBJECT public: + /*! A QHash instantiation used for storing ResourceItems and allow lookup + * by hash string */ typedef QHash ItemHashMap; - ResourceDB( ResourceProvider * _provider ); + /*! \brief Constructs a ResourceDB object. + * \param provider The ResourceProvider that will manage ResourceItems in this DB + */ + ResourceDB( ResourceProvider * provider ); ~ResourceDB(); + /*! \brief Initializes ResourceDB object, i.e. restore cache and update itself via the ResourceProvider. */ void init(); - void load( const QString & _file ); - void save( const QString & _file ); + /*! \brief Dumps all ResourceItems with their relations to a file. + * \param file The filename to save data to */ + void load( const QString & file ); + + /*! \brief Restores ResourceItems with their relations from a file. + * \param file The filename to load data from */ + void save( const QString & file ); inline ResourceProvider * provider() { @@ -69,21 +94,31 @@ public: return &m_topLevelNode; } - // similiar to items()[_hash] but faster and returns NULL if not found - const ResourceItem * itemByHash( const QString & _hash ) const; + /*! \brief Looks up a ResourceItem by hash string - similiar to items()[hash] + * but faster and returns NULL if not found. + * \param hash The hash string that is searched for + * \return A const pointer to the matching ResourceItem */ + const ResourceItem * itemByHash( const QString & hash ) const; - // return a list of ResourceItems who somehow match the given keywords - ResourceItemList matchItems( const QStringList & _keyWords ); + /*! \brief Return a list of ResourceItems which somehow match the given keywords. + * \param keywords A list of keywords that are searched for + * \return A ResourceItemList which the result */ + ResourceItemList matchItems( const QStringList & keywords ); - // return an item which matches a resource desceibed in _item as - // good as possible - const ResourceItem * nearestMatch( const ResourceItem & _item ); + /*! \brief Returns a ResourceItem which matches a given ResourceItem best. + * \param item A ResourceItem to search the best match for + * \return A const pointer to the best marching ResourceItem in the DB */ + const ResourceItem * nearestMatch( const ResourceItem & item ); - // add given item to DB - void addItem( ResourceItem * _newItem ); + /*! \brief Adds given ResourceItem to DB. + * \param newItem The ResourceItem to be added */ + void addItem( ResourceItem * newItem ); - void recursiveRemoveItems( ResourceItem::Relation * parent, - bool removeTopLevelParent = true ); + /*! \brief Remove items recursively starting from a certain ResourceItem::Relation + * \param parent The parent relation to start from with removing + * \param removeParent A boolean which specifies whether to also remove the parent item */ + void removeItemsRecursively( ResourceItem::Relation * parent, + bool removeParent = true ); private: @@ -140,9 +175,12 @@ private: signals: + /*! \brief Forwarded signal of ResourceProvider::itemsChanged() */ void itemsChanged(); - void directoryItemAdded( const QString & _path ); - void directoryItemRemoved( const QString & _path ); + /*! \brief Emitted whenever a ResourceItem of type ResourceItem::TypeDirectory is added */ + void directoryItemAdded( const QString & path ); + /*! \brief Emitted whenever a ResourceItem of type ResourceItem::TypeDirectory is removed */ + void directoryItemRemoved( const QString & path ); } ; diff --git a/src/core/LocalResourceProvider.cpp b/src/core/LocalResourceProvider.cpp index 603836983..f017f2be6 100644 --- a/src/core/LocalResourceProvider.cpp +++ b/src/core/LocalResourceProvider.cpp @@ -278,7 +278,7 @@ qDebug() << "read dir" << d.canonicalPath(); { ResourceItem::Relation * item = *it; it = curParent->children().erase( it ); - database()->recursiveRemoveItems( item ); + database()->removeItemsRecursively( item ); } else { diff --git a/src/core/ResourceDB.cpp b/src/core/ResourceDB.cpp index 5d9d285e7..3d91f7897 100644 --- a/src/core/ResourceDB.cpp +++ b/src/core/ResourceDB.cpp @@ -98,7 +98,7 @@ void ResourceDB::init() void ResourceDB::load( const QString & _file ) { - recursiveRemoveItems( topLevelNode(), false ); + removeItemsRecursively( topLevelNode(), false ); multimediaProject m( _file ); @@ -283,7 +283,7 @@ void ResourceDB::addItem( ResourceItem * newItem ) ResourceItem::Relation * oldRelation = oldItem->relation(); if( oldRelation ) { - recursiveRemoveItems( oldRelation, false ); + removeItemsRecursively( oldRelation, false ); delete oldRelation; } if( oldItem->type() == ResourceItem::TypeDirectory ) @@ -299,8 +299,8 @@ void ResourceDB::addItem( ResourceItem * newItem ) -void ResourceDB::recursiveRemoveItems( ResourceItem::Relation * parent, - bool removeTopLevelParent ) +void ResourceDB::removeItemsRecursively( ResourceItem::Relation * parent, + bool removeParent ) { if( !parent ) { @@ -309,10 +309,10 @@ void ResourceDB::recursiveRemoveItems( ResourceItem::Relation * parent, while( !parent->children().isEmpty() ) { - recursiveRemoveItems( parent->children().front() ); + removeItemsRecursively( parent->children().front() ); } - if( removeTopLevelParent && parent->item() ) + if( removeParent && parent->item() ) { if( parent->item()->type() == ResourceItem::TypeDirectory ) { From 20589f19e4e90513befe1c7e102013bcd542deea Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 29 Nov 2009 01:33:25 +0100 Subject: [PATCH 38/43] 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 39/43] 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 40/43] 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 41/43] 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 42/43] 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 43/43] 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() ) {