Merge remote-tracking branch 'origin/stable-0.4-new-fx-mixer'

Conflicts:
	include/PlayHandle.h
	src/core/FxMixer.cpp
	src/core/Mixer.cpp
	src/gui/FxMixerView.cpp
	src/tracks/InstrumentTrack.cpp
This commit is contained in:
Tobias Doerffel
2014-03-26 11:21:15 +01:00
21 changed files with 1678 additions and 608 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

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 "LcdWidget.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;
LcdWidget* 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

@@ -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
@@ -72,6 +73,18 @@ public:
return m_type;
}
// required for ThreadableJob
virtual void doProcessing( sampleFrame* buffer )
{
play( buffer );
}
virtual bool requiresProcessing() const
{
return !isFinished();
}
virtual void play( sampleFrame* buffer ) = 0;
virtual bool isFinished( void ) const = 0;

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

@@ -27,6 +27,7 @@
#include <QtGui/QProgressDialog>
#include <QtCore/QDir>
#include <QtCore/QBuffer>
#include <QtCore/QDebug>
#include "FlpImport.h"
#include "NotePlayHandle.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 )
{
@@ -537,7 +539,7 @@ struct FL_Project
int currentPattern;
int activeEditPattern;
FL_EffectChannel effectChannels[NumFxChannels+1];
FL_EffectChannel effectChannels[NumFLFxChannels+1];
int currentEffectChannel;
QString projectNotes;
@@ -887,7 +889,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;
@@ -1124,7 +1126,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;
}
@@ -1347,7 +1349,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;
}
@@ -1408,9 +1410,15 @@ else
// now create a project from FL_Project data structure
engine::getSong()->clearProject();
// configure the mixer
for( int i=0; i<NumFLFxChannels; ++i )
{
engine::fxMixer()->createChannel();
}
engine::fxMixerView()->refreshDisplay();
// set global parameters
engine::getSong()->setMasterVolume( p.mainVolume );
engine::getSong()->setMasterPitch( p.mainPitch );
@@ -1647,7 +1655,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 )
@@ -1707,7 +1715,7 @@ p->putValue( jt->pos, value, false );
break;
}
if( effName.isEmpty() || it->fxChannel < 0 ||
it->fxChannel > NumFxChannels )
it->fxChannel > NumFLFxChannels )
{
continue;
}

View File

@@ -22,17 +22,20 @@
*
*/
#include <QtXml/QDomElement>
#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 ),
@@ -40,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() );
@@ -57,90 +62,365 @@ FxChannel::~FxChannel()
void FxChannel::doProcessing( sampleFrame * _buf )
{
FxMixer * fxm = engine::fxMixer();
const fpp_t fpp = engine::mixer()->framesPerPeriod();
// <tobydox> ignore the passed _buf
// <tobydox> always use m_buffer
// <tobydox> this is just an auxilliary buffer if doProcessing()
// needs one for processing while running
// <tobydox> particularly important for playHandles, so Instruments
// can operate on this buffer the whole time
// <tobydox> 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();
m_fxChain.startRunning();
m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp, true );
m_peakLeft = qMax( m_peakLeft, engine::mixer()->peakValueLeft( _buf, fpp ) * v );
m_peakRight = qMax( 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
TrackContainer::TrackList tracks;
tracks += engine::getSong()->tracks();
tracks += engine::getBBTrackContainer()->tracks();
foreach( track* t, tracks )
{
m_fxChannels[_ch]->m_lock.lock();
sampleFrame * buf = m_fxChannels[_ch]->m_buffer;
for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); ++f )
if( t->type() == track::InstrumentTrack )
{
buf[f][0] += _buf[f][0];
buf[f][1] += _buf[f][1];
InstrumentTrack* inst = dynamic_cast<InstrumentTrack *>( t );
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; i<m_fxChannels[index]->m_sends.size(); ++i)
{
deleteChannelSend(index, m_fxChannels[index]->m_sends[i]);
}
for(int i=0; i<m_fxChannels[index]->m_receives.size(); ++i)
{
deleteChannelSend(m_fxChannels[index]->m_receives[i], index);
}
for(int i=0; i<m_fxChannels.size(); ++i)
{
// for every send/receive, adjust for the channel index change
for(int j=0; j<m_fxChannels[i]->m_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; j<m_fxChannels[i]->m_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();
// only start effects if sound was mixed to this FX channel before
if( m_fxChannels[_ch]->m_used )
{
m_fxChannels[_ch]->m_fxChain.startRunning();
}
// process FX chain
m_fxChannels[_ch]->m_stillRunning = m_fxChannels[_ch]->m_fxChain.processAudioBuffer( _buf, f, m_fxChannels[_ch]->m_used );
float peakLeft = engine::mixer()->peakValueLeft( _buf, f ) * m_fxChannels[_ch]->m_volumeModel.value();
float peakRight = engine::mixer()->peakValueRight( _buf, f ) * m_fxChannels[_ch]->m_volumeModel.value();
if( peakLeft > m_fxChannels[_ch]->m_peakLeft )
{
m_fxChannels[_ch]->m_peakLeft = peakLeft;
}
if( peakRight > m_fxChannels[_ch]->m_peakRight )
{
m_fxChannels[_ch]->m_peakRight = peakRight;
}
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<track *> songTrackList = engine::getSong()->tracks();
QVector<track *> bbTrackList = engine::getBBTrackContainer()->tracks();
QVector<track *> trackLists[] = {songTrackList, bbTrackList};
for(int tl=0; tl<2; ++tl)
{
m_fxChannels[_ch]->m_peakLeft = m_fxChannels[_ch]->m_peakRight = 0.0f;
QVector<track *> trackList = trackLists[tl];
for(int i=0; i<trackList.size(); ++i)
{
if( trackList[i]->type() == 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; i<m_fxChannels.size(); ++i)
{
// for every send/receive, adjust for the channel index change
for(int j=0; j<m_fxChannels[i]->m_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; j<m_fxChannels[i]->m_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; i<from->m_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; i<from->m_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; i<to->m_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; i<m_fxChannels[sendTo]->m_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; i<from->m_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();
}
}
@@ -155,47 +435,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_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;
}
}
@@ -203,58 +494,138 @@ 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; i<ch->m_sends.size(); ++i)
{
deleteChannelSend(index, ch->m_sends[i]);
}
// add send to master
createChannelSend(index, 0);
}
// delete receives
for( int i=0; i<ch->m_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<chData.length(); ++i )
{
QDomElement chDataItem = chData.at(i).toElement();
if( chDataItem.nodeName() == QString( "send" ) )
{
thereIsASend = true;
int sendTo = chDataItem.attribute( "channel" ).toInt();
allocateChannelsTo( sendTo) ;
float amount = chDataItem.attribute( "amount" ).toFloat();
createChannelSend( num, sendTo, amount );
}
}
node = node.nextSibling();
}
// check for old format. 65 fx channels and no explicit sends.
if( ! thereIsASend && m_fxChannels.size() == 65 ) {
// create a send from every channel into master
for( int i=1; i<m_fxChannels.size(); ++i )
{
createChannelSend(i, 0);
}
}
emit dataChanged();
}

View File

@@ -27,6 +27,7 @@
#include "Mixer.h"
#include "FxMixer.h"
#include "MixHelpers.h"
#include "MixerWorkerThread.h"
#include "song.h"
#include "templates.h"
#include "EnvelopeAndLfoParameters.h"
@@ -57,8 +58,6 @@
#include "MidiDummy.h"
static QVector<fx_ch_t> __fx_channel_jobs( NumFxChannels );
static void aligned_free( void * _buf )
@@ -90,212 +89,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();
int workerNum() const
{
return m_workerNum;
}
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 ),
m_workingBuf( NULL ),
@@ -321,11 +114,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 +160,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 +178,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 );
@@ -617,9 +404,8 @@ const surroundSampleFrame * Mixer::renderNextBuffer()
// STAGE 1: run and render all play handles
FILL_JOB_QUEUE(PlayHandleList,m_playHandles,MixerWorkerThread::PlayHandle, !( *it )->isFinished());
START_JOBS();
WAIT_FOR_JOBS();
MixerWorkerThread::fillJobQueue<PlayHandleList>( m_playHandles );
MixerWorkerThread::startAndWaitForJobs();
// removed all play handles which are done
for( PlayHandleList::Iterator it = m_playHandles.begin();
@@ -644,20 +430,11 @@ const surroundSampleFrame * Mixer::renderNextBuffer()
// STAGE 2: process effects of all instrument- and sampletracks
FILL_JOB_QUEUE(QVector<AudioPort*>,m_audioPorts,
MixerWorkerThread::AudioPortEffects,1);
START_JOBS();
WAIT_FOR_JOBS();
MixerWorkerThread::fillJobQueue<QVector<AudioPort *> >( m_audioPorts );
MixerWorkerThread::startAndWaitForJobs();
// STAGE 3: process effects in FX mixer
FILL_JOB_QUEUE_PARAM(QVector<fx_ch_t>,__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();

View File

@@ -0,0 +1,165 @@
/*
* MixerWorkerThread.cpp - implementation of 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.
*
*/
#include "MixerWorkerThread.h"
#include "engine.h"
MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue;
QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL;
QList<MixerWorkerThread *> 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();
}
}

View File

@@ -1,7 +1,7 @@
/*
* AudioPort.cpp - base-class for objects providing sound at a port
*
* 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
*
@@ -25,6 +25,7 @@
#include "AudioPort.h"
#include "AudioDevice.h"
#include "EffectChain.h"
#include "FxMixer.h"
#include "engine.h"
@@ -118,3 +119,13 @@ bool AudioPort::processEffects()
}
void AudioPort::doProcessing( sampleFrame * )
{
const bool me = processEffects();
if( me || m_bufferUsage != NoUsage )
{
engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() );
nextPeriod();
}
}

View File

@@ -930,6 +930,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() )
{

View File

@@ -22,6 +22,9 @@
*
*/
#include <QtGlobal>
#include <QDebug>
#include <QtGui/QButtonGroup>
#include <QtGui/QInputDialog>
#include <QtGui/QLayout>
@@ -31,96 +34,19 @@
#include <QtGui/QPushButton>
#include <QtGui/QToolButton>
#include <QtGui/QStackedLayout>
#include <QtGui/QScrollArea>
#include <QtGui/QStyle>
#include <QtGui/QKeyEvent>
#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 );
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( rect(),
m_mv->currentFxLine() == this ? bg_color.lighter(130) : bg_color );
p.setPen( bg_color.darker(130) );
p.drawRect( 0, 0, width()-2, height()-2 );
p.setPen( bg_color.lighter(150) );
p.drawRect( 1, 1, width()-2, height()-2 );
p.setPen( m_mv->currentFxLine() == this ? sh_color : bg_color.darker(130) );
p.drawRect( 0, 0, width()-1, height()-1 );
p.rotate( -90 );
p.setFont( pointSizeF( font(), 7.5f ) );
p.setPen( sh_color );
p.drawText( -91, 21, m_name );
p.setPen( m_mv->currentFxLine() == this ? bt_color : te_color );
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() :
@@ -131,121 +57,82 @@ 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 );
cv->m_rackView->setMinimumWidth( 244 );
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() ) );
@@ -255,10 +142,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 );
@@ -267,15 +154,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; i<m_fxChannelViews.size(); ++i )
{
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];
}
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<track *> songTrackList = engine::getSong()->tracks();
QVector<track *> bbTrackList = engine::getBBTrackContainer()->tracks();
QVector<track *> trackLists[] = {songTrackList, bbTrackList};
for(int tl=0; tl<2; ++tl)
{
QVector<track *> trackList = trackLists[tl];
for(int i=0; i<trackList.size(); ++i)
{
if( trackList[i]->type() == track::InstrumentTrack )
{
InstrumentTrack * inst = (InstrumentTrack *) trackList[i];
inst->effectChannelModel()->setRange(0,
m_fxChannelViews.size()-1,1);
}
}
}
}
void FxMixerView::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
@@ -291,18 +238,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<m_fxChannelViews.size(); ++i)
{
if( 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;
}
}
@@ -310,23 +428,21 @@ 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();
engine::fxMixer()->clear();
refreshDisplay();
}
@@ -335,29 +451,29 @@ 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->m_fxChannels[i]->m_peakLeft );
m_fxChannelViews[i]->m_fader->setPeak_L( m->m_fxChannels[i]->m_peakLeft );
m->m_fxChannels[i]->m_peakLeft = 0;
}
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->m_fxChannels[i]->m_peakRight );
m_fxChannelViews[i]->m_fader->setPeak_R( m->m_fxChannels[i]->m_peakRight );
m->m_fxChannels[i]->m_peakRight = 0;
}
else
{
m_fxChannelViews[i].m_fader->setPeak_R( opr/fall_off );
m_fxChannelViews[i]->m_fader->setPeak_R( opr/fall_off );
}
}
}

163
src/gui/widgets/FxLine.cpp Normal file
View File

@@ -0,0 +1,163 @@
/*
* FxLine.cpp - 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.
*
*/
#include "FxLine.h"
#include <QDebug>
#include <QtGui/QInputDialog>
#include <QtGui/QPainter>
#include <QtGui/QLineEdit>
#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 LcdWidget( 2, this );
m_lcd->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->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();
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( bg_color.lighter(150) );
p->drawRect( 1, 1, width-2, height-2 );
p->setPen( isActive ? sh_color : bg_color.darker(130) );
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->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 );
}
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"

View File

@@ -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 );
}

View File

@@ -106,7 +106,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ),
m_pitchModel( 0, MinPitchDefault, MaxPitchDefault, 1, this, tr( "Pitch" ) ),
m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ),
m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ),
m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ),
m_instrument( NULL ),
m_soundShaping( this ),
m_arpeggio( this ),
@@ -115,15 +115,13 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
{
m_pitchModel.setCenterValue( 0 );
m_panningModel.setCenterValue( DefaultPanning );
m_baseNoteModel.setInitValue( DefaultKey );
connect( &m_baseNoteModel, SIGNAL( dataChanged() ),
this, SLOT( updateBaseNote() ) );
connect( &m_pitchModel, SIGNAL( dataChanged() ),
this, SLOT( updatePitch() ) );
connect( &m_pitchRangeModel, SIGNAL( dataChanged() ),
this, SLOT( updatePitchRange() ) );
connect( &m_baseNoteModel, SIGNAL( dataChanged() ), this, SLOT( updateBaseNote() ) );
connect( &m_pitchModel, SIGNAL( dataChanged() ), this, SLOT( updatePitch() ) );
connect( &m_pitchRangeModel, SIGNAL( dataChanged() ), this, SLOT( updatePitchRange() ) );
m_effectChannelModel.setRange( 0, engine::fxMixer()->numChannels()-1, 1);
for( int i = 0; i < NumKeys; ++i )
{
@@ -706,6 +704,7 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement
m_panningModel.loadSettings( thisElement, "pan" );
m_pitchRangeModel.loadSettings( thisElement, "pitchrange" );
m_pitchModel.loadSettings( thisElement, "pitch" );
m_effectChannelModel.setRange( 0, INT_MAX );
m_effectChannelModel.loadSettings( thisElement, "fxch" );
m_baseNoteModel.loadSettings( thisElement, "basenote" );