From a955fb755a9b99d4a43fcbd35200d54328117d3a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 8 Jan 2014 23:19:07 +0100 Subject: [PATCH 1/7] FxMixer, Mixer, FxMixerView: backported FX send support from master branch There once have been huge efforts to implement FX send support in the master branch. In order to make it available on a stable base here's a backport which is non-trivial as there have been major rewrites of the mixer's worker thread architecture. There still seem to be bugs which we have to fix before merging into stable branch. Thanks to Andrew Kelley for the original work. --- data/themes/default/mixer_send_off.png | Bin 0 -> 467 bytes data/themes/default/mixer_send_on.png | Bin 0 -> 471 bytes data/themes/default/send_bg_arrow.png | Bin 0 -> 267 bytes include/AudioPort.h | 11 +- include/FxLine.h | 66 +++ include/FxMixer.h | 106 +++-- include/FxMixerView.h | 70 ++- include/MixerWorkerThread.h | 118 +++++ include/SendButtonIndicator.h | 32 ++ include/ThreadableJob.h | 84 ++++ include/play_handle.h | 17 +- plugins/flp_import/FlpImport.cpp | 24 +- src/core/FxMixer.cpp | 573 ++++++++++++++++++++---- src/core/Mixer.cpp | 243 +--------- src/core/MixerWorkerThread.cpp | 165 +++++++ src/core/audio/AudioPort.cpp | 13 +- src/gui/FxMixerView.cpp | 496 ++++++++++++-------- src/gui/widgets/FxLine.cpp | 147 ++++++ src/gui/widgets/SendButtonIndicator.cpp | 53 +++ src/tracks/InstrumentTrack.cpp | 5 +- 20 files changed, 1652 insertions(+), 571 deletions(-) create mode 100644 data/themes/default/mixer_send_off.png create mode 100644 data/themes/default/mixer_send_on.png create mode 100644 data/themes/default/send_bg_arrow.png create mode 100644 include/FxLine.h create mode 100644 include/MixerWorkerThread.h create mode 100644 include/SendButtonIndicator.h create mode 100644 include/ThreadableJob.h create mode 100644 src/core/MixerWorkerThread.cpp create mode 100644 src/gui/widgets/FxLine.cpp create mode 100644 src/gui/widgets/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/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/AudioPort.h b/include/AudioPort.h index 47129aede61..735c313b81a 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -1,7 +1,7 @@ /* * AudioPort.h - base-class for objects providing sound at a port * - * Copyright (c) 2005-2009 Tobias Doerffel + * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -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 @@ class AudioPort bool processEffects(); + // ThreadableJob stuff + virtual void doProcessing( sampleFrame * ); + virtual bool requiresProcessing() const + { + return true; + } + enum bufferUsages { diff --git a/include/FxLine.h b/include/FxLine.h new file mode 100644 index 00000000000..55f2daa7f99 --- /dev/null +++ b/include/FxLine.h @@ -0,0 +1,66 @@ +/* + * FxLine.h - FX line widget + * + * Copyright (c) 2009 Andrew Kelley + * Copyright (c) 2014 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 _FX_LINE_H +#define _FX_LINE_H + +#include +#include + +#include "knob.h" +#include "lcd_spinbox.h" +#include "SendButtonIndicator.h" + +class FxMixerView; +class SendButtonIndicator; + +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; } + void setChannelIndex(int index); + + knob * m_sendKnob; + SendButtonIndicator * m_sendBtn; + +private: + FxMixerView * m_mv; + lcdSpinBox * m_lcd; + + + int m_channelIndex; + +} ; + + +#endif // FXLINE_H diff --git a/include/FxMixer.h b/include/FxMixer.h index 10b6b2cc602..8ff396cc66d 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -1,7 +1,7 @@ /* * FxMixer.h - effect-mixer for LMMS * - * Copyright (c) 2008-2009 Tobias Doerffel + * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -29,46 +29,58 @@ #include "Mixer.h" #include "EffectChain.h" #include "JournallingObject.h" +#include "ThreadableJob.h" -const int NumFxChannels = 64; -struct FxChannel +class FxChannel : public ThreadableJob { - FxChannel( Model * _parent ); - ~FxChannel(); - - EffectChain m_fxChain; - bool m_used; - bool m_stillRunning; - float m_peakLeft; - float m_peakRight; - sampleFrame * m_buffer; - BoolModel m_muteModel; - FloatModel m_volumeModel; - QString m_name; - QMutex m_lock; + public: + FxChannel( int idx, Model * _parent ); + virtual ~FxChannel(); -} ; + EffectChain m_fxChain; + + // 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; + 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; + QVector m_sendAmount; + + // 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 ); +}; -class FxMixer : public JournallingObject, public Model + +class EXPORT FxMixer : public JournallingObject, public Model { public: FxMixer(); 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 ); - - void clear(); - virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent ); virtual void loadSettings( const QDomElement & _this ); @@ -79,17 +91,55 @@ class FxMixer : public JournallingObject, public Model 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 + // 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); + + // 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); + + // add a new channel to the Fx Mixer. + // returns the index of the channel that was just added + int createChannel(); + + // 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); + + // 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; + + // 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/FxMixerView.h b/include/FxMixerView.h index dab34caaf7c..6449a03060f 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -26,59 +26,91 @@ #define _FX_MIXER_VIEW_H #include +#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, +class EXPORT FxMixerView : public QWidget, public ModelView, public SerializingObjectHook { Q_OBJECT public: + struct FxChannelView + { + FxChannelView(QWidget * _parent, FxMixerView * _mv, int _chIndex ); + + FxLine * m_fxLine; + 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 ); - FxLine * currentFxLine() + 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 index); + + // 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); + + // make sure the display syncs up with the fx mixer. + // useful for loading projects + void refreshDisplay(); + private slots: void updateFaders(); - + void addNewChannel(); private: - struct FxChannelView - { - FxLine * m_fxLine; - EffectRackView * m_rackView; - pixmapButton * m_muteBtn; - 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; + QWidget * m_channelAreaWidget; + EffectRackView * m_rackView; + + void updateMaxChannelSelector(); } ; #endif diff --git a/include/MixerWorkerThread.h b/include/MixerWorkerThread.h new file mode 100644 index 00000000000..199c3888420 --- /dev/null +++ b/include/MixerWorkerThread.h @@ -0,0 +1,118 @@ +/* + * MixerWorkerThread.h - declaration of class MixerWorkerThread + * + * Copyright (c) 2009-2014 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 "ThreadableJob.h" +#include "Mixer.h" + + +class MixerWorkerThread : public QThread +{ +public: + // internal representation of the job queue - all functions are thread-safe + class JobQueue + { + public: + enum OperationMode + { + Static, // no jobs added while processing queue + Dynamic // jobs can be added while processing queue + } ; + + JobQueue() : + m_items(), + m_queueSize( 0 ), + m_itemsDone( 0 ), + m_opMode( Static ) + { + } + + 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; + + } ; + + + MixerWorkerThread( Mixer* mixer ); + virtual ~MixerWorkerThread(); + + virtual void quit(); + + static void resetJobQueue( JobQueue::OperationMode _opMode = + JobQueue::Static ) + { + 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 startAndWaitForJobs(); + + +private: + virtual void run(); + + static JobQueue globalJobQueue; + static QWaitCondition * queueReadyWaitCond; + static QList workerThreads; + + sampleFrame * m_workingBuf; + volatile bool m_quit; + +} ; + + +#endif diff --git a/include/SendButtonIndicator.h b/include/SendButtonIndicator.h new file mode 100644 index 00000000000..2b081979146 --- /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/include/ThreadableJob.h b/include/ThreadableJob.h new file mode 100644 index 00000000000..1eb1e16121d --- /dev/null +++ b/include/ThreadableJob.h @@ -0,0 +1,84 @@ +/* + * ThreadableJob.h - declaration of class ThreadableJob + * + * Copyright (c) 2009-2014 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 "lmms_basics.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* workingBuffer = NULL ) + { + if( m_state.testAndSetOrdered( Queued, InProgress ) ) + { + doProcessing( workingBuffer ); + m_state = Done; + } + } + + virtual bool requiresProcessing() const = 0; + + +protected: + virtual void doProcessing( sampleFrame* workingBuffer) = 0; + + QAtomicInt m_state; + +} ; + +#endif diff --git a/include/play_handle.h b/include/play_handle.h index 27aa2757341..ed8fc188aa2 100644 --- a/include/play_handle.h +++ b/include/play_handle.h @@ -1,7 +1,7 @@ /* * play_handle.h - base-class playHandle - core of rendering engine * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -28,12 +28,13 @@ #include #include +#include "ThreadableJob.h" #include "lmms_basics.h" class track; -class playHandle +class playHandle : public ThreadableJob { public: enum types @@ -70,6 +71,18 @@ class playHandle 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( void ) const = 0; diff --git a/plugins/flp_import/FlpImport.cpp b/plugins/flp_import/FlpImport.cpp index dd539df0070..a61daef6491 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" @@ -96,7 +98,7 @@ extern QString outstring; } - +const int NumFLFxChannels = 64; static void dump_mem( const void * buffer, uint n_bytes ) { @@ -534,7 +536,7 @@ struct FL_Project int currentPattern; int activeEditPattern; - FL_EffectChannel effectChannels[NumFxChannels+1]; + FL_EffectChannel effectChannels[NumFLFxChannels+1]; int currentEffectChannel; QString projectNotes; @@ -884,7 +886,7 @@ bool FlpImport::tryImport( trackContainer * _tc ) break; case FLP_EffectChannelMuted: -if( p.currentEffectChannel <= NumFxChannels ) +if( p.currentEffectChannel <= NumFLFxChannels ) { p.effectChannels[p.currentEffectChannel].isMuted = ( data & 0x08 ) > 0 ? false : true; @@ -1121,7 +1123,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; } @@ -1344,7 +1346,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; } @@ -1405,9 +1407,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 ); @@ -1644,7 +1652,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 ) @@ -1704,7 +1712,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 864364cf50a..d41276d17bc 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -1,10 +1,8 @@ -#ifndef SINGLE_SOURCE_COMPILE - /* * FxMixer.cpp - effect mixer for LMMS * * Copyright (c) 2008-2011 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 @@ -24,17 +22,20 @@ * */ - #include #include "FxMixer.h" +#include "MixerWorkerThread.h" +#include "MixHelpers.h" #include "Effect.h" #include "song.h" +#include "InstrumentTrack.h" +#include "bb_track_container.h" + -FxChannel::FxChannel( Model * _parent ) : +FxChannel::FxChannel( int idx, Model * _parent ) : m_fxChain( NULL ), - m_used( false ), m_stillRunning( false ), m_peakLeft( 0.0f ), m_peakRight( 0.0f ), @@ -42,7 +43,9 @@ 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_channelIndex( idx ), + m_queued( false ) { engine::mixer()->clearAudioBuffer( m_buffer, engine::mixer()->framesPerPeriod() ); @@ -59,81 +62,373 @@ FxChannel::~FxChannel() +void FxChannel::doProcessing( sampleFrame * _buf ) +{ + FxMixer * fxm = engine::fxMixer(); + const fpp_t fpp = engine::mixer()->framesPerPeriod(); + + // 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; + + // SMF: OK, due to the fact, that the data from the audio-tracks has been + // written into our buffer already, all which needs to be done at this + // stage is to process inter-channel sends. I really don't like the idea + // of using threads for this -- it just doesn't make any sense and wastes + // cpu-cylces... so I just go through every child of this channel and + // call the acc. doProcessing() directly. + + if( m_muteModel.value() == false ) + { + // OK, we are not muted, so we go recursively through all the channels + // which send to us (our children)... + foreach( fx_ch_t senderIndex, m_receives ) + { + FxChannel * sender = fxm->effectChannel( senderIndex ); + + // wait for the sender job - either it's just been queued yet, + // then ThreadableJob::process() will process it now within this + // thread - otherwise it has been is is being processed by another + // thread and we just have to wait for it to finish + while( sender->state() != ThreadableJob::Done ) + { + sender->process(); + } + // get the send level... + const float amt = + fxm->channelSendModel( senderIndex, m_channelIndex )->value(); -FxMixer::FxMixer() : - JournallingObject(), - Model( NULL ) -{ - for( int i = 0; i < NumFxChannels+1; ++i ) + // mix it's output with this one's output + sampleFrame * ch_buf = sender->m_buffer; + const float v = sender->m_volumeModel.value() * amt; + 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; + } + } + } + + const float v = m_volumeModel.value(); + + if( !engine::getSong()->isFreezingPattern() ) { - m_fxChannels[i] = new FxChannel( this ); + m_fxChain.startRunning(); + m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp ); + m_peakLeft = engine::mixer()->peakValueLeft( _buf, fpp ) * v; + m_peakRight = engine::mixer()->peakValueRight( _buf, fpp ) * v; } - // reset name etc. - clear(); } +FxMixer::FxMixer() : + JournallingObject(), + Model( NULL ), + m_fxChannels() +{ + // create master channel + createChannel(); +} + + FxMixer::~FxMixer() { - for( int i = 0; i < NumFxChannels+1; ++i ) + for( int i = 0; i < m_fxChannels.size(); ++i ) { + for( int j = 0; j < m_fxChannels[i]->m_sendAmount.size(); ++j) + { + delete m_fxChannels[i]->m_sendAmount[j]; + } delete m_fxChannels[i]; } } +int FxMixer::createChannel() +{ + const int index = m_fxChannels.size(); + // create new channel + m_fxChannels.push_back( new FxChannel( index, this ) ); -void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) + // reset channel state + clearChannel( index ); + + return index; +} + + +void FxMixer::deleteChannel(int index) { - if( m_fxChannels[_ch]->m_muteModel.value() == false ) + 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(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) { - m_fxChannels[_ch]->m_lock.lock(); - sampleFrame * buf = m_fxChannels[_ch]->m_buffer; - for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); - ++f ) + 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); + } + + } } - m_fxChannels[_ch]->m_used = true; - m_fxChannels[_ch]->m_lock.unlock(); } + + // 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 + delete m_fxChannels[index]; + m_fxChannels.remove(index); +} + + + +void FxMixer::moveChannelLeft(int index) +{ + // can't move master or first channel + if( index <= 1 || index >= m_fxChannels.size() ) + { + return; + } + + // channels to swap + 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; } +void FxMixer::moveChannelRight(int index) +{ + moveChannelLeft(index+1); +} + + + +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); + +} + + -void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) +// delete the connection made by createChannelSend +void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) { - if( m_fxChannels[_ch]->m_muteModel.value() == false && - ( m_fxChannels[_ch]->m_used || - m_fxChannels[_ch]->m_stillRunning || - _ch == 0 ) ) + // 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 + delete from->m_sendAmount[i]; + from->m_sendAmount.remove(i); + from->m_sends.remove(i); + break; + } + } + + // find and delete the receive entry + for(int i=0; im_receives.size(); ++i) { - if( _buf == NULL ) + if( to->m_receives[i] == fromChannel ) { - _buf = m_fxChannels[_ch]->m_buffer; + // delete this index + to->m_receives.remove(i); + break; } - const fpp_t f = engine::mixer()->framesPerPeriod(); - if( !engine::getSong()->isFreezingPattern() ) + } +} + + +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] ) ) { - 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::mixer()->peakValueLeft( _buf, f ) * - m_fxChannels[_ch]->m_volumeModel.value(); - m_fxChannels[_ch]->m_peakRight = engine::mixer()->peakValueRight( _buf, f ) * - m_fxChannels[_ch]->m_volumeModel.value(); + return true; } - m_fxChannels[_ch]->m_used = true; } - else + + 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) +{ + FxChannel * from = m_fxChannels[fromChannel]; + for(int i=0; im_sends.size(); ++i){ + if( from->m_sends[i] == toChannel ) + return from->m_sendAmount[i]; + } + return NULL; +} + + + +void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) +{ + // SMF: it seems like here the track-channels are mixed in... but from where + // is this called and when and why...?!? + // + // OK, found it (git grep is your friend...): This is the next part, + // where there is a mix between push and pull model inside the core, as + // the audio-tracks *push* their data into the fx-channels hopefully just + // before the Mixer-Channels are processed... Sorry to say this: but this + // took me senseless hours to find out and is silly, too... + + if( m_fxChannels[_ch]->m_muteModel.value() == false ) { - m_fxChannels[_ch]->m_peakLeft = - m_fxChannels[_ch]->m_peakRight = 0.0f; + m_fxChannels[_ch]->m_lock.lock(); + MixHelpers::add( m_fxChannels[_ch]->m_buffer, _buf, engine::mixer()->framesPerPeriod() ); + m_fxChannels[_ch]->m_lock.unlock(); } } @@ -148,47 +443,58 @@ void FxMixer::prepareMasterMix() - -void FxMixer::masterMix( sampleFrame * _buf ) +void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) { - const int fpp = engine::mixer()->framesPerPeriod(); - memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); + FxChannel * thisCh = m_fxChannels[_ch]; - for( int i = 1; i < NumFxChannels+1; ++i ) + // if we're muted or this channel is seen already, discount it + if( thisCh->m_muteModel.value() || thisCh->m_queued ) { - 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::mixer()->clearAudioBuffer( ch_buf, - engine::mixer()->framesPerPeriod() ); - m_fxChannels[i]->m_used = false; - } + return; } - processChannel( 0, _buf ); - - if( m_fxChannels[0]->m_muteModel.value() ) + foreach( const int senderIndex, thisCh->m_receives ) { - engine::mixer()->clearAudioBuffer( _buf, - engine::mixer()->framesPerPeriod() ); - return; + addChannelLeaf( senderIndex, _buf ); } - const float v = m_fxChannels[0]->m_volumeModel.value(); - for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); ++f ) + // add this channel to job list + thisCh->m_queued = true; + MixerWorkerThread::addJob( thisCh ); +} + + + +void FxMixer::masterMix( sampleFrame * _buf ) +{ + const int fpp = engine::mixer()->framesPerPeriod(); + + // 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( MixerWorkerThread::JobQueue::Dynamic ); + addChannelLeaf( 0, _buf ); + while( m_fxChannels[0]->state() != ThreadableJob::Done ) { - _buf[f][0] *= v; - _buf[f][1] *= v; + MixerWorkerThread::startAndWaitForJobs(); } + //m_fxChannels[0]->doProcessing( NULL ); + + const float v = m_fxChannels[0]->m_volumeModel.value(); + MixHelpers::addMultiplied( _buf, m_fxChannels[0]->m_buffer, v, fpp ); m_fxChannels[0]->m_peakLeft *= engine::mixer()->masterGain(); m_fxChannels[0]->m_peakRight *= engine::mixer()->masterGain(); + + // clear all channel buffers and + // reset channel process state + for( int i = 0; i < numChannels(); ++i) + { + engine::mixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer, engine::mixer()->framesPerPeriod() ); + m_fxChannels[i]->reset(); + m_fxChannels[i]->m_queued = false; + } } @@ -196,60 +502,139 @@ void FxMixer::masterMix( sampleFrame * _buf ) void FxMixer::clear() { - for( int i = 0; i <= NumFxChannels; ++i ) + while( m_fxChannels.size() > 1 ) { - 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 ); - + deleteChannel(1); } + + clearChannel(0); } +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 ) { + 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"); + } } } +// make sure we have at least num channels +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 <= NumFxChannels; ++i ) + 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; i __fx_channel_jobs( NumFxChannels ); - static void aligned_free( void * _buf ) @@ -91,210 +90,6 @@ static void * aligned_malloc( int _bytes ) -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; - - AtomicInt done; - } ; - - - struct JobQueue - { -#define JOB_QUEUE_SIZE 1024 - JobQueue() : - queueSize( 0 ) - { - } - - JobQueueItem items[JOB_QUEUE_SIZE]; - int queueSize; - AtomicInt itemsDone; - } ; - - static JobQueue s_jobQueue; - - MixerWorkerThread( int _worker_num, Mixer* mixer ) : - QThread( mixer ), - m_workingBuf( (sampleFrame *) aligned_malloc( - mixer->framesPerPeriod() * - sizeof( sampleFrame ) ) ), - m_workerNum( _worker_num ), - m_quit( false ), - m_mixer( mixer ), - m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) - { - } - - virtual ~MixerWorkerThread() - { - aligned_free( m_workingBuf ); - } - - virtual void quit() - { - m_quit = true; - } - - void processJobQueue(); - - -private: - virtual void run() - { -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_SCHED_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( m_workerNum, &mask ); - sched_setaffinity( 0, sizeof( mask ), &mask ); -#endif -#endif -#endif - 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() : m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), @@ -321,11 +116,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() ) { @@ -372,7 +162,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 ); @@ -390,14 +180,13 @@ 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(); } - START_JOBS(); + + MixerWorkerThread::startAndWaitForJobs(); + for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->wait( 500 ); @@ -618,11 +407,8 @@ const surroundSampleFrame * 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(); @@ -647,20 +433,11 @@ const surroundSampleFrame * 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(); - - - // STAGE 3: process effects in FX mixer - FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, - MixerWorkerThread::EffectChannel,1); - START_JOBS(); - WAIT_FOR_JOBS(); + MixerWorkerThread::fillJobQueue >( m_audioPorts ); + MixerWorkerThread::startAndWaitForJobs(); - // STAGE 4: do master mix in FX mixer + // STAGE 3: do master mix in FX mixer engine::fxMixer()->masterMix( m_writeBuf ); unlock(); diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp new file mode 100644 index 00000000000..143fffa846b --- /dev/null +++ b/src/core/MixerWorkerThread.cpp @@ -0,0 +1,165 @@ +/* + * MixerWorkerThread.cpp - implementation of MixerWorkerThread + * + * Copyright (c) 2009-2014 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 "engine.h" + + +MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue; +QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL; +QList MixerWorkerThread::workerThreads; + + + +// 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( new sampleFrame[mixer->framesPerPeriod()] ), + 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(); +} + + + + +MixerWorkerThread::~MixerWorkerThread() +{ + delete[] m_workingBuf; + + workerThreads.removeAll( this ); +} + + + + +void MixerWorkerThread::quit() +{ + m_quit = true; + resetJobQueue(); +} + + + + +void MixerWorkerThread::startAndWaitForJobs() +{ + 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(); +} + + + + +void MixerWorkerThread::run() +{ + QMutex m; + while( m_quit == false ) + { + m.lock(); + queueReadyWaitCond->wait( &m ); + globalJobQueue.run( m_workingBuf ); + m.unlock(); + } +} + + diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 5071cc3697f..64cf6cfd223 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -1,7 +1,7 @@ /* * AudioPort.cpp - base-class for objects providing sound at a port * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -25,6 +25,7 @@ #include "AudioPort.h" #include "AudioDevice.h" #include "EffectChain.h" +#include "FxMixer.h" #include "engine.h" @@ -121,3 +122,13 @@ 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/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 834a4dbcc39..453e25a2622 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -22,6 +22,9 @@ * */ +#include +#include + #include #include #include @@ -31,80 +34,19 @@ #include #include #include +#include +#include +#include #include "FxMixerView.h" -#include "fader.h" -#include "EffectRackView.h" +#include "knob.h" #include "engine.h" #include "embed.h" #include "MainWindow.h" -#include "LcdWidget.h" #include "gui_templates.h" -#include "tooltip.h" -#include "pixmap_button.h" - - - -class FxLine : public QWidget -{ -public: - FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name ) : - QWidget( _parent ), - m_mv( _mv ), - m_name( _name ) - { - setFixedSize( 32, 232 ); - setAttribute( Qt::WA_OpaquePaintEvent, true ); - setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); - } - - virtual void paintEvent( QPaintEvent * ) - { - QPainter p( this ); - p.fillRect( rect(), QColor( 72, 76, 88 ) ); - p.setPen( QColor( 40, 42, 48 ) ); - p.drawRect( 0, 0, width()-2, height()-2 ); - p.setPen( QColor( 108, 114, 132 ) ); - p.drawRect( 1, 1, width()-2, height()-2 ); - p.setPen( QColor( 20, 24, 32 ) ); - p.drawRect( 0, 0, width()-1, height()-1 ); - - p.rotate( -90 ); - p.setPen( m_mv->currentFxLine() == this ? - QColor( 0, 255, 0 ) : Qt::white ); - p.setFont( pointSizeF( font(), 7.5f ) ); - p.drawText( -90, 20, m_name ); - } - - 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(); - } - } - - -private: - FxMixerView * m_mv; - QString & m_name; - -} ; - - - +#include "InstrumentTrack.h" +#include "song.h" +#include "bb_track_container.h" FxMixerView::FxMixerView() : QWidget(), @@ -114,117 +56,81 @@ FxMixerView::FxMixerView() : FxMixer * m = engine::fxMixer(); m->setHook( this ); - QPalette pal = palette(); - pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) ); - setPalette( pal ); + //QPalette pal = palette(); + //pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) ); + //setPalette( pal ); setAutoFillBackground( true ); - setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum ); 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 ); - // main-layout QHBoxLayout * ml = new QHBoxLayout; - 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]; + ml->addWidget( masterView->m_fxLine, 0, Qt::AlignTop ); + + QSize fxLineSize = masterView->m_fxLine->size(); - QHBoxLayout * banks[NumFxChannels/16]; - for( int i = 0; i < NumFxChannels/16; ++i ) + // 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 ); + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); + chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); } - for( int i = 0; i < NumFxChannels+1; ++i ) + // add the scrolling section to the main layout + // class solely for scroll area to pass key presses down + class ChannelArea : public QScrollArea { - 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 ); - } - LcdWidget* l = new LcdWidget( 2, cv->m_fxLine ); - l->setValue( i ); - l->move( 3, 4 ); - 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 ); - 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 ) + public: + ChannelArea(QWidget * parent, FxMixerView * mv) : + QScrollArea(parent), m_mv(mv) {} + ~ChannelArea() {} + virtual void keyPressEvent(QKeyEvent * e) { - 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); + m_mv->keyPressEvent(e); } - l->addSpacing( 10 ); - ml->addLayout( l ); - connect( g, SIGNAL( buttonClicked( int ) ), - m_fxLineBanks, SLOT( setCurrentIndex( int ) ) ); - } - } - - ml->addLayout( m_fxLineBanks ); - ml->addLayout( m_fxRacksLayout ); + private: + FxMixerView * m_mv; + }; + channelArea = new ChannelArea(this, this); + channelArea->setWidget(m_channelAreaWidget); + 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 + 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, 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(); - m_fxLineBanks->setCurrentIndex( 0 ); - setCurrentFxLine( m_fxChannelViews[0].m_fxLine ); - // timer for updating faders connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( updateFaders() ) ); @@ -234,10 +140,10 @@ 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); + layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); + subWin->layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); @@ -246,14 +152,74 @@ FxMixerView::FxMixerView() : setModel( m ); } +FxMixerView::~FxMixerView() +{ +} + +void FxMixerView::addNewChannel() +{ + // add new fx mixer channel and redraw the form. + FxMixer * mix = engine::fxMixer(); -FxMixerView::~FxMixerView() + int newChannelIndex = mix->createChannel(); + m_fxChannelViews.push_back(new FxChannelView(m_channelAreaWidget, this, + newChannelIndex)); + chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); + + updateFxLine(newChannelIndex); + + updateMaxChannelSelector(); +} + + +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); + } + + updateMaxChannelSelector(); } +// update the and max. channel number for every instrument +void FxMixerView::updateMaxChannelSelector() +{ + 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()->setRange(0, + m_fxChannelViews.size()-1,1); + } + } + } +} void FxMixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) @@ -270,72 +236,238 @@ 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" ) ); +} void FxMixerView::setCurrentFxLine( FxLine * _line ) { + // select m_currentFxLine = _line; - for( int i = 0; i < NumFxChannels+1; ++i ) + m_rackView->setModel( &engine::fxMixer()->m_fxChannels[_line->channelIndex()]->m_fxChain ); + + // set up send knob + for(int i = 0; i < m_fxChannelViews.size(); ++i) { - if( m_fxChannelViews[i].m_fxLine == _line ) + updateFxLine(i); + } +} + + +void FxMixerView::updateFxLine(int index) +{ + FxMixer * mix = engine::fxMixer(); + + // does current channel send to this channel? + 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 + thisLine->m_sendKnob->setVisible(false); + } + else + { + // it does send, show knob and connect + thisLine->m_sendKnob->setVisible(true); + thisLine->m_sendKnob->setModel(sendModel); + } + + // 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::deleteChannel(int index) +{ + // can't delete master + if( index == 0 ) return; + + // remember selected line + int selLine = m_currentFxLine->channelIndex(); + + // 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]; + m_channelAreaWidget->adjustSize(); + + // make sure every channel knows what index it is + for(int i=0; i index ) { - m_fxRacksLayout->setCurrentIndex( i ); + m_fxChannelViews[i]->m_fxLine->setChannelIndex(i-1); } - m_fxChannelViews[i].m_fxLine->update(); } + m_fxChannelViews.remove(index); + + // select the next channel + if( selLine >= m_fxChannelViews.size() ) + { + selLine = m_fxChannelViews.size()-1; + } + setCurrentFxLine(selLine); + + updateMaxChannelSelector(); } -void FxMixerView::setCurrentFxLine( int _line ) +void FxMixerView::moveChannelLeft(int index) { - if ( _line >= 0 && _line < NumFxChannels+1 ) + // can't move master or first channel left or last channel right + if( index <= 1 || index >= 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 ) { - setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); - - m_bankButtons->button( (_line-1) / 16 )->click(); + // 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::clear() + + +void FxMixerView::keyPressEvent(QKeyEvent * e) +{ + switch(e->key()) + { + case Qt::Key_Delete: + deleteChannel(m_currentFxLine->channelIndex()); + break; + case Qt::Key_Left: + if( e->modifiers() & Qt::AltModifier ) + { + 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; + } +} + + + +void FxMixerView::setCurrentFxLine( int _line ) { - for( int i = 0; i <= NumFxChannels; ++i ) + if( _line >= 0 && _line < m_fxChannelViews.size() ) { - m_fxChannelViews[i].m_rackView->clearViews(); + setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); } } +void FxMixerView::clear() +{ + m_rackView->clearViews(); + refreshDisplay(); +} + + + 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(); + 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/widgets/FxLine.cpp b/src/gui/widgets/FxLine.cpp new file mode 100644 index 00000000000..b90f647b57c --- /dev/null +++ b/src/gui/widgets/FxLine.cpp @@ -0,0 +1,147 @@ +/* + * FxLine.cpp - FX line widget + * + * Copyright (c) 2009 Andrew Kelley + * Copyright (c) 2014 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 "FxLine.h" + +#include +#include +#include +#include + +#include "FxMixer.h" +#include "FxMixerView.h" +#include "embed.h" +#include "engine.h" +#include "SendButtonIndicator.h" +#include "gui_templates.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 + 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(); +} + + +static void drawFxLine( QPainter* p, const QWidget *fxLine, const QString& name, bool isActive, bool sendToThis ) +{ + int width = fxLine->rect().width(); + int height = fxLine->rect().height(); + + p->fillRect( fxLine->rect(), QColor( 72, 76, 88 ) ); + p->setPen( QColor( 40, 42, 48 ) ); + p->drawRect( 0, 0, width-2, height-2 ); + p->setPen( QColor( 108, 114, 132 ) ); + p->drawRect( 1, 1, width-2, height-2 ); + 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( isActive ? QColor( 0, 255, 0 ) : Qt::white ); + p->setFont( pointSizeF( fxLine->font(), 7.5f ) ); + p->drawText( -145, 20, name ); + +} + +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 ); + 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/widgets/SendButtonIndicator.cpp b/src/gui/widgets/SendButtonIndicator.cpp new file mode 100644 index 00000000000..a932f136ac0 --- /dev/null +++ b/src/gui/widgets/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 ); +} diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 3387f585c20..7c90d5f0d4b 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -2,7 +2,7 @@ * InstrumentTrack.cpp - implementation of instrument-track-class * (window + data-structures) * - * Copyright (c) 2004-2013 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -106,13 +106,14 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) : m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_pitchModel( 0, -100, 100, 1, this, tr( "Pitch" ) ), - m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ), + m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), m_instrument( NULL ), m_soundShaping( this ), m_arpeggiator( this ), m_chordCreator( this ), m_piano( this ) { + m_effectChannelModel.setRange( 0, engine::fxMixer()->numChannels()-1, 1); m_baseNoteModel.setInitValue( DefaultKey ); connect( &m_baseNoteModel, SIGNAL( dataChanged() ), this, SLOT( updateBaseNote() ) ); From 5b480dd89824c6cf56fbb0f3b60ff8c7c746d559 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 14 Jan 2014 18:04:51 +0100 Subject: [PATCH 2/7] FxMixer: rewrote loop for adjusting FX channel models in deleteChannel() --- src/core/FxMixer.cpp | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index d41276d17bc..982620c9875 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -171,31 +171,26 @@ 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(); + TrackContainer::TrackList tracks; + tracks += engine::getSong()->tracks(); + tracks += engine::getBBTrackContainer()->tracks(); - QVector trackLists[] = {songTrackList, bbTrackList}; - for(int tl=0; tl<2; ++tl) + foreach( track* t, tracks ) { - QVector trackList = trackLists[tl]; - for(int i=0; itype() == track::InstrumentTrack ) { - if( trackList[i]->type() == track::InstrumentTrack ) + InstrumentTrack* inst = dynamic_cast( t ); + int val = inst->effectChannelModel()->value(0); + if( val == index ) { - 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); - } - + // 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); } } } From 27b51c250865c42633f98d98742cf9d7f903931b Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 14 Jan 2014 18:17:51 +0100 Subject: [PATCH 3/7] Song: added missing refresh of FxMixerView when loading project Missed this call when backporting. --- src/core/song.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/song.cpp b/src/core/song.cpp index a2c5457aae1..2af716a48ba 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1154,6 +1154,11 @@ void song::loadProject( const QString & _file_name ) else if( node.nodeName() == engine::fxMixer()->nodeName() ) { engine::fxMixer()->restoreState( node.toElement() ); + if( engine::hasGUI() ) + { + // refresh FxMixerView + engine::fxMixerView()->refreshDisplay(); + } } else if( engine::hasGUI() ) { From dbb4a8e6ab63ceb08731e0928ca0175e6c9d5b57 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 14 Jan 2014 18:18:59 +0100 Subject: [PATCH 4/7] FxMixerView: fixed clear() Before calling refreshDisplay() we have to actually clear the backend model. --- src/gui/FxMixerView.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 453e25a2622..d8382ed9d75 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -437,6 +437,9 @@ void FxMixerView::setCurrentFxLine( int _line ) void FxMixerView::clear() { m_rackView->clearViews(); + + engine::fxMixer()->clear(); + refreshDisplay(); } From 037b977137980f777652e7830569085272f418db Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 15 Jan 2014 21:51:35 +0200 Subject: [PATCH 5/7] Ported the FX mixer UI changes from stable-0.4 --- src/gui/widgets/FxLine.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/gui/widgets/FxLine.cpp b/src/gui/widgets/FxLine.cpp index b90f647b57c..681810aa8c2 100644 --- a/src/gui/widgets/FxLine.cpp +++ b/src/gui/widgets/FxLine.cpp @@ -86,12 +86,25 @@ static void drawFxLine( QPainter* p, const QWidget *fxLine, const QString& name, int width = fxLine->rect().width(); int height = fxLine->rect().height(); - p->fillRect( fxLine->rect(), QColor( 72, 76, 88 ) ); - p->setPen( QColor( 40, 42, 48 ) ); + QColor bg_color = QApplication::palette().color( QPalette::Active, + QPalette::Background ); + QColor sh_color = QApplication::palette().color( QPalette::Active, + QPalette::Shadow ); + QColor te_color = QApplication::palette().color( QPalette::Active, + QPalette::Text ); + QColor bt_color = QApplication::palette().color( QPalette::Active, + QPalette::BrightText ); + + + p->fillRect( fxLine->rect(), isActive ? bg_color.lighter(130) : bg_color ); + + p->setPen( bg_color.darker(130) ); p->drawRect( 0, 0, width-2, height-2 ); - p->setPen( QColor( 108, 114, 132 ) ); + + p->setPen( bg_color.lighter(150) ); p->drawRect( 1, 1, width-2, height-2 ); - p->setPen( QColor( 20, 24, 32 ) ); + + p->setPen( isActive ? sh_color : bg_color.darker(130) ); p->drawRect( 0, 0, width-1, height-1 ); // draw the mixer send background @@ -103,8 +116,13 @@ static void drawFxLine( QPainter* p, const QWidget *fxLine, const QString& name, // draw the channel name p->rotate( -90 ); - p->setPen( isActive ? QColor( 0, 255, 0 ) : Qt::white ); - p->setFont( pointSizeF( fxLine->font(), 7.5f ) ); + + p->setFont( pointSizeF( fxLine->font(), 7.5f ) ); + p->setPen( sh_color ); + p->drawText( -146, 21, name ); + + p->setPen( isActive ? bt_color : te_color ); + p->drawText( -145, 20, name ); } From dddf5eb61c7b5cbb168e485be53860deed7a950a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Fri, 17 Jan 2014 19:53:05 +0100 Subject: [PATCH 6/7] FxMixer: ignore mute state in addChannelLeaf() to fix lockup Even if we do not process the FX chain later, we at least have to enqueue all related FX channels to the job queue in order to prevent a lockup. --- src/core/FxMixer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 982620c9875..5739cc2a6bc 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -443,7 +443,7 @@ void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) FxChannel * thisCh = m_fxChannels[_ch]; // if we're muted or this channel is seen already, discount it - if( thisCh->m_muteModel.value() || thisCh->m_queued ) + if( thisCh->m_queued ) { return; } From 12939c24df48047e8b75b2cd1730a3593a37803d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Fri, 17 Jan 2014 20:01:24 +0100 Subject: [PATCH 7/7] InstrumentTrack: set range of FX channel model to infinite when loading settings When loading a project, instrument tracks usually are instantiated/loaded before the FX mixer settings are loaded which results in an empty FX mixer and thus 0 FX channels. The actual FX channel value for the instrument track would be lost that way. Therefore set a big value which is being corrected later by the FxMixerView for all instruments. --- src/tracks/InstrumentTrack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 41419255175..a848b0dc1e9 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -803,6 +803,7 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & _this ) } m_pitchModel.loadSettings( _this, "pitch" ); + m_effectChannelModel.setRange( 0, INT_MAX ); m_effectChannelModel.loadSettings( _this, "fxch" ); if( _this.hasAttribute( "baseoct" ) )