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.
This commit is contained in:
Tobias Doerffel
2014-01-08 23:19:07 +01:00
parent 0ff1f91c1b
commit a955fb755a
20 changed files with 1660 additions and 579 deletions

View File

@@ -1,7 +1,7 @@
/*
* AudioPort.h - base-class for objects providing sound at a port
*
* Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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
{

66
include/FxLine.h Normal file
View File

@@ -0,0 +1,66 @@
/*
* FxLine.h - FX line widget
*
* Copyright (c) 2009 Andrew Kelley <superjoe30/at/gmail/dot/com>
* Copyright (c) 2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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 <QWidget>
#include <QLabel>
#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

View File

@@ -1,7 +1,7 @@
/*
* FxMixer.h - effect-mixer for LMMS
*
* Copyright (c) 2008-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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<fx_ch_t> m_sends;
QVector<FloatModel *> m_sendAmount;
// pointers to other channels that send to this one
QVector<fx_ch_t> 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<FxChannel *> 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;

View File

@@ -26,59 +26,91 @@
#define _FX_MIXER_VIEW_H
#include <QtGui/QWidget>
#include <QtGui/QHBoxLayout>
#include <QtGui/QScrollArea>
#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<FxChannelView *> 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

118
include/MixerWorkerThread.h Normal file
View File

@@ -0,0 +1,118 @@
/*
* MixerWorkerThread.h - declaration of class MixerWorkerThread
*
* Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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 <QtCore/QAtomicPointer>
#include <QtCore/QThread>
#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<ThreadableJob> 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<typename T>
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<MixerWorkerThread *> workerThreads;
sampleFrame * m_workingBuf;
volatile bool m_quit;
} ;
#endif

View File

@@ -0,0 +1,32 @@
#ifndef SENDBUTTONINDICATOR_H
#define SENDBUTTONINDICATOR_H
#include <QDebug>
#include <QtGui/QLabel>
#include <QtGui/QPixmap>
#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

84
include/ThreadableJob.h Normal file
View File

@@ -0,0 +1,84 @@
/*
* ThreadableJob.h - declaration of class ThreadableJob
*
* Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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 <QtCore/QAtomicInt>
#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<ProcessingState>( (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

View File

@@ -1,7 +1,7 @@
/*
* play_handle.h - base-class playHandle - core of rendering engine
*
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
@@ -28,12 +28,13 @@
#include <QtCore/QThread>
#include <QtCore/QVector>
#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;