diff --git a/data/themes/default/mixer_send_off.png b/data/themes/default/mixer_send_off.png new file mode 100644 index 000000000..6f426c36f Binary files /dev/null and b/data/themes/default/mixer_send_off.png differ diff --git a/data/themes/default/mixer_send_on.png b/data/themes/default/mixer_send_on.png new file mode 100644 index 000000000..6861c7acd Binary files /dev/null and b/data/themes/default/mixer_send_on.png differ diff --git a/data/themes/default/send_bg_arrow.png b/data/themes/default/send_bg_arrow.png new file mode 100644 index 000000000..fde514da6 Binary files /dev/null and b/data/themes/default/send_bg_arrow.png differ diff --git a/include/AudioPort.h b/include/AudioPort.h index 47129aede..735c313b8 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 @@ public: 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 000000000..55f2daa7f --- /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 10b6b2cc6..8ff396cc6 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(); + public: + FxChannel( int idx, Model * _parent ); + virtual ~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; + 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 @@ 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 + // 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 dab34caaf..6449a0306 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 000000000..199c38884 --- /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 000000000..2b0819791 --- /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 000000000..1eb1e1612 --- /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 27aa27573..ed8fc188a 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 @@ 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( void ) const = 0; diff --git a/plugins/flp_import/FlpImport.cpp b/plugins/flp_import/FlpImport.cpp index dd539df00..a61daef64 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 864364cf5..d41276d17 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(); + + // 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_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; + } +} + FxMixer::FxMixer() : JournallingObject(), - Model( NULL ) + Model( NULL ), + m_fxChannels() { - for( int i = 0; i < NumFxChannels+1; ++i ) - { - m_fxChannels[i] = new FxChannel( this ); - } - // reset name etc. - clear(); + // 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]; } } - -void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) +int FxMixer::createChannel() { - if( m_fxChannels[_ch]->m_muteModel.value() == false ) + const int index = m_fxChannels.size(); + // create new channel + m_fxChannels.push_back( new FxChannel( index, this ) ); + + // reset channel state + clearChannel( index ); + + return index; +} + + +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(); + + 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::processChannel( fx_ch_t _ch, sampleFrame * _buf ) +void FxMixer::moveChannelLeft(int index) { - if( m_fxChannels[_ch]->m_muteModel.value() == false && - ( m_fxChannels[_ch]->m_used || - m_fxChannels[_ch]->m_stillRunning || - _ch == 0 ) ) + // can't move master or first channel + if( index <= 1 || index >= m_fxChannels.size() ) { - if( _buf == NULL ) - { - _buf = m_fxChannels[_ch]->m_buffer; - } - const fpp_t f = engine::mixer()->framesPerPeriod(); - if( !engine::getSong()->isFreezingPattern() ) - { - 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(); - } - m_fxChannels[_ch]->m_used = true; + return; } - else + + // 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) { - m_fxChannels[_ch]->m_peakLeft = - m_fxChannels[_ch]->m_peakRight = 0.0f; + 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); + +} + + + +// 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 + 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( to->m_receives[i] == fromChannel ) + { + // delete this index + to->m_receives.remove(i); + break; + } + } +} + + +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) +{ + 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_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::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 ) + { + return; + } + + foreach( const int senderIndex, thisCh->m_receives ) + { + addChannelLeaf( senderIndex, _buf ); + } + + // add this channel to job list + thisCh->m_queued = true; + MixerWorkerThread::addJob( thisCh ); +} + + void FxMixer::masterMix( sampleFrame * _buf ) { const int fpp = engine::mixer()->framesPerPeriod(); - memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); - for( int i = 1; i < NumFxChannels+1; ++i ) + // 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 ) { - 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; - } - } - - processChannel( 0, _buf ); - - if( m_fxChannels[0]->m_muteModel.value() ) - { - engine::mixer()->clearAudioBuffer( _buf, - engine::mixer()->framesPerPeriod() ); - return; + MixerWorkerThread::startAndWaitForJobs(); } + //m_fxChannels[0]->doProcessing( NULL ); const float v = m_fxChannels[0]->m_volumeModel.value(); - for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); ++f ) - { - _buf[f][0] *= v; - _buf[f][1] *= v; - } + 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(); + 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 engine::fxMixer()->masterMix( m_writeBuf ); unlock(); diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp new file mode 100644 index 000000000..143fffa84 --- /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 5071cc369..64cf6cfd2 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 834a4dbcc..453e25a26 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); - QHBoxLayout * banks[NumFxChannels/16]; - for( int i = 0; i < NumFxChannels/16; ++i ) + // 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(); + + // 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 ) ) ); - } - } + 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); - ml->addLayout( m_fxLineBanks ); - ml->addLayout( m_fxRacksLayout ); + // 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,15 +152,75 @@ FxMixerView::FxMixerView() : setModel( m ); } - - - 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(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,18 +236,189 @@ 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::moveChannelLeft(int index) +{ + // 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 ) + { + // 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()) + { + 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; } } @@ -289,23 +426,18 @@ void FxMixerView::setCurrentFxLine( FxLine * _line ) void FxMixerView::setCurrentFxLine( int _line ) { - if ( _line >= 0 && _line < NumFxChannels+1 ) + if( _line >= 0 && _line < m_fxChannelViews.size() ) { - setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); - - m_bankButtons->button( (_line-1) / 16 )->click(); + setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); } } - void FxMixerView::clear() { - for( int i = 0; i <= NumFxChannels; ++i ) - { - m_fxChannelViews[i].m_rackView->clearViews(); - } + m_rackView->clearViews(); + refreshDisplay(); } @@ -314,28 +446,28 @@ 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(); + 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 000000000..b90f647b5 --- /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 000000000..a932f136a --- /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 3387f585c..7c90d5f0d 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() ) );