Merge branch 'master' into mixer-new-fifo-arch
Conflicts: include/Mixer.h src/core/Mixer.cpp src/core/audio/AudioPort.cpp src/core/main.cpp
This commit is contained in:
BIN
data/themes/default/mixer_send_off.png
Normal file
BIN
data/themes/default/mixer_send_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 467 B |
BIN
data/themes/default/mixer_send_on.png
Normal file
BIN
data/themes/default/mixer_send_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 471 B |
BIN
data/themes/default/send_bg_arrow.png
Normal file
BIN
data/themes/default/send_bg_arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 267 B |
@@ -27,13 +27,12 @@
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QMutexLocker>
|
||||
|
||||
#include "Mixer.h"
|
||||
|
||||
class EffectChain;
|
||||
|
||||
class AudioPort
|
||||
class AudioPort : public ThreadableJob
|
||||
{
|
||||
public:
|
||||
AudioPort( const QString & _name, bool _has_effect_chain = true );
|
||||
@@ -109,6 +108,13 @@ public:
|
||||
|
||||
bool processEffects();
|
||||
|
||||
// ThreadableJob stuff
|
||||
virtual void doProcessing( sampleFrame * );
|
||||
virtual bool requiresProcessing() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
enum bufferUsages
|
||||
{
|
||||
|
||||
@@ -225,6 +225,9 @@ protected:
|
||||
|
||||
float fittedValue( float _value ) const;
|
||||
|
||||
float m_minValue;
|
||||
float m_maxValue;
|
||||
float m_value;
|
||||
|
||||
private:
|
||||
void linkModel( AutomatableModel * _model );
|
||||
@@ -232,10 +235,7 @@ private:
|
||||
|
||||
|
||||
DataType m_dataType;
|
||||
float m_value;
|
||||
float m_initValue;
|
||||
float m_minValue;
|
||||
float m_maxValue;
|
||||
float m_step;
|
||||
float m_range;
|
||||
|
||||
@@ -280,8 +280,7 @@ signals:
|
||||
inline type maxValue() const \
|
||||
{ \
|
||||
return AutomatableModel::maxValue<type>(); \
|
||||
} \
|
||||
|
||||
}
|
||||
|
||||
// some typed AutomatableModel-definitions
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ typedef void (*BufApplyGainFunc)( sampleFrameA * RP _dst,
|
||||
typedef void (*BufMixFunc)( sampleFrameA * RP _dst,
|
||||
const sampleFrameA * RP _src,
|
||||
int _frames );
|
||||
typedef void (*BufMixCoeffFunc)( sampleFrameA * RP _dst,
|
||||
const sampleFrameA * RP _src,
|
||||
float _coeff, int _frames );
|
||||
typedef void (*BufMixLRCoeffFunc)( sampleFrameA * RP _dst,
|
||||
const sampleFrameA * RP _src,
|
||||
float _left, float _right,
|
||||
@@ -81,6 +84,7 @@ extern MemCpyFunc memCpy;
|
||||
extern MemClearFunc memClear;
|
||||
extern BufApplyGainFunc bufApplyGain;
|
||||
extern BufMixFunc bufMix;
|
||||
extern BufMixCoeffFunc bufMixCoeff;
|
||||
extern BufMixLRCoeffFunc bufMixLRCoeff;
|
||||
extern UnalignedBufMixLRCoeffFunc unalignedBufMixLRCoeff;
|
||||
extern BufWetDryMixFunc bufWetDryMix;
|
||||
|
||||
41
include/FxLine.h
Normal file
41
include/FxLine.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef FXLINE_H
|
||||
#define FXLINE_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
|
||||
@@ -31,26 +31,41 @@
|
||||
#include "JournallingObject.h"
|
||||
|
||||
|
||||
const int NumFxChannels = 64;
|
||||
|
||||
|
||||
struct FxChannel
|
||||
class FxChannel : public ThreadableJob
|
||||
{
|
||||
FxChannel( Model * _parent );
|
||||
~FxChannel();
|
||||
public:
|
||||
FxChannel( Model * _parent );
|
||||
~FxChannel();
|
||||
|
||||
EffectChain m_fxChain;
|
||||
bool m_used;
|
||||
bool m_stillRunning;
|
||||
float m_peakLeft;
|
||||
float m_peakRight;
|
||||
sampleFrame * m_buffer;
|
||||
BoolModel m_muteModel;
|
||||
FloatModel m_volumeModel;
|
||||
QString m_name;
|
||||
QMutex m_lock;
|
||||
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 );
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -61,14 +76,10 @@ public:
|
||||
virtual ~FxMixer();
|
||||
|
||||
void mixToChannel( const sampleFrame * _buf, fx_ch_t _ch );
|
||||
void processChannel( fx_ch_t _ch, sampleFrame * _buf = NULL );
|
||||
|
||||
void prepareMasterMix();
|
||||
void masterMix( sampleFrame * _buf );
|
||||
|
||||
|
||||
void clear();
|
||||
|
||||
virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent );
|
||||
virtual void loadSettings( const QDomElement & _this );
|
||||
|
||||
@@ -79,17 +90,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;
|
||||
|
||||
@@ -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,
|
||||
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
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define _MAIN_WINDOW_H
|
||||
|
||||
#include <QtCore/QBasicTimer>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QList>
|
||||
#include <QtGui/QMainWindow>
|
||||
#include <QtGui/QWhatsThis>
|
||||
@@ -208,6 +209,7 @@ private:
|
||||
QList<PluginView *> m_tools;
|
||||
|
||||
QBasicTimer m_updateTimer;
|
||||
QTimer m_autoSaveTimer;
|
||||
|
||||
ResourceBrowser * m_resourceBrowser;
|
||||
|
||||
@@ -244,6 +246,8 @@ private slots:
|
||||
void playAndRecord();
|
||||
void stop();
|
||||
|
||||
void autoSave();
|
||||
|
||||
signals:
|
||||
void periodicUpdate();
|
||||
|
||||
|
||||
@@ -61,11 +61,12 @@ const Keys BaseKey = Key_A;
|
||||
const Octaves BaseOctave = DefaultOctave;
|
||||
|
||||
|
||||
class MixerWorkerThread;
|
||||
|
||||
#include "ThreadableJob.h"
|
||||
#include "play_handle.h"
|
||||
|
||||
|
||||
class MixerWorkerThread;
|
||||
|
||||
/*! \brief The Mixer class is responsible for processing and rendering audio chunks. */
|
||||
class EXPORT Mixer : public QObject
|
||||
{
|
||||
@@ -321,9 +322,7 @@ private:
|
||||
|
||||
|
||||
friend class engine;
|
||||
friend class MixerWorkerThread;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
117
include/MixerWorkerThread.h
Normal file
117
include/MixerWorkerThread.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* MixerWorkerThread.h - declaration of class MixerWorkerThread
|
||||
*
|
||||
* Copyright (c) 2009 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 "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
|
||||
32
include/SendButtonIndicator.h
Normal file
32
include/SendButtonIndicator.h
Normal 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
84
include/ThreadableJob.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* ThreadableJob.h - declaration of class ThreadableJob
|
||||
*
|
||||
* Copyright (c) 2009 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 "Mixer.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 * _working_buffer )
|
||||
{
|
||||
if( m_state.testAndSetOrdered( Queued, InProgress ) )
|
||||
{
|
||||
doProcessing( _working_buffer );
|
||||
m_state = Done;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool requiresProcessing() const = 0;
|
||||
|
||||
|
||||
protected:
|
||||
virtual void doProcessing( sampleFrame * _working_buffer ) = 0;
|
||||
|
||||
QAtomicInt m_state;
|
||||
|
||||
} ;
|
||||
|
||||
#endif
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
// LMMS Stuff
|
||||
|
||||
virtual void drawFxLine(QPainter * _painter, const QWidget *_fxLine,
|
||||
const QString & _name, bool _active);
|
||||
const QString & _name, bool _active, bool _sendToThis);
|
||||
|
||||
virtual void drawTrackContentBackground(QPainter * _painter,
|
||||
const QSize & _size, const int _pixelsPerTact);
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
virtual void unpolish( QWidget * widget );
|
||||
|
||||
virtual void drawFxLine( QPainter * _painter, const QWidget *_fxLine,
|
||||
const QString & _name, bool _active );
|
||||
const QString & _name, bool _active, bool _sendToThis );
|
||||
|
||||
virtual void drawTrackContentBackground( QPainter * _painter,
|
||||
const QSize & _size, const int _pixelsPerTact );
|
||||
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
|
||||
|
||||
virtual void drawFxLine(QPainter * _painter, const QWidget *_fxLine,
|
||||
const QString & _name, bool _active) = 0;
|
||||
const QString & _name, bool _active, bool _sendToThis) = 0;
|
||||
|
||||
virtual void drawTrackContentBackground(QPainter * _painter,
|
||||
const QSize & _size, const int _pixelsPerTact) = 0;
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
#define _PLAY_HANDLE_H
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#include "lmms_basics.h"
|
||||
#include "ThreadableJob.h"
|
||||
#include "Mixer.h"
|
||||
|
||||
class track;
|
||||
|
||||
|
||||
class playHandle
|
||||
class playHandle : public ThreadableJob
|
||||
{
|
||||
public:
|
||||
enum Types
|
||||
@@ -71,6 +71,17 @@ public:
|
||||
return m_type;
|
||||
}
|
||||
|
||||
// required for ThreadableJob
|
||||
virtual void doProcessing( sampleFrame * _working_buffer )
|
||||
{
|
||||
play( _working_buffer );
|
||||
}
|
||||
|
||||
virtual bool requiresProcessing() const
|
||||
{
|
||||
return !done();
|
||||
}
|
||||
|
||||
virtual void play( sampleFrame * _working_buffer ) = 0;
|
||||
virtual bool done() const = 0;
|
||||
|
||||
|
||||
@@ -151,9 +151,10 @@ public:
|
||||
// file management
|
||||
void createNewProject();
|
||||
void createNewProjectFromTemplate( const QString & _template );
|
||||
void loadProject( const QString & _file_name );
|
||||
bool saveProject();
|
||||
bool saveProjectAs( const QString & _file_name );
|
||||
void loadProject( const QString & _filename );
|
||||
bool guiSaveProject();
|
||||
bool guiSaveProjectAs( const QString & _filename );
|
||||
bool saveProjectFile( const QString & _filename );
|
||||
inline const QString & projectFileName() const
|
||||
{
|
||||
return m_fileName;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <QtGui/QProgressDialog>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#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"
|
||||
@@ -104,7 +106,7 @@ extern QString outstring;
|
||||
|
||||
}
|
||||
|
||||
|
||||
const int NumFLFxChannels = 64;
|
||||
|
||||
static void dump_mem( const void * buffer, uint n_bytes )
|
||||
{
|
||||
@@ -542,7 +544,7 @@ struct FL_Project
|
||||
int currentPattern;
|
||||
int activeEditPattern;
|
||||
|
||||
FL_EffectChannel effectChannels[NumFxChannels+1];
|
||||
FL_EffectChannel effectChannels[NumFLFxChannels+1];
|
||||
int currentEffectChannel;
|
||||
|
||||
QString projectNotes;
|
||||
@@ -903,7 +905,6 @@ bool FlpImport::tryFLPImport( trackContainer * _tc )
|
||||
const bool is_journ = engine::projectJournal()->isJournalling();
|
||||
engine::projectJournal()->setJournalling( false );
|
||||
|
||||
|
||||
while( file().atEnd() == false )
|
||||
{
|
||||
FLP_Events ev = static_cast<FLP_Events>( readByte() );
|
||||
@@ -1022,7 +1023,7 @@ bool FlpImport::tryFLPImport( trackContainer * _tc )
|
||||
break;
|
||||
|
||||
case FLP_EffectChannelMuted:
|
||||
if( p.currentEffectChannel <= NumFxChannels )
|
||||
if( p.currentEffectChannel <= NumFLFxChannels )
|
||||
{
|
||||
p.effectChannels[p.currentEffectChannel].isMuted =
|
||||
( data & 0x08 ) > 0 ? false : true;
|
||||
@@ -1274,7 +1275,7 @@ if( p.currentEffectChannel <= NumFxChannels )
|
||||
|
||||
case FLP_Text_EffectChanName:
|
||||
++p.currentEffectChannel;
|
||||
if( p.currentEffectChannel <= NumFxChannels )
|
||||
if( p.currentEffectChannel <= NumFLFxChannels )
|
||||
{
|
||||
p.effectChannels[p.currentEffectChannel].name = text;
|
||||
}
|
||||
@@ -1497,7 +1498,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;
|
||||
}
|
||||
@@ -1558,9 +1559,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 );
|
||||
@@ -1797,7 +1804,7 @@ p->putValue( jt->pos, value, false );
|
||||
}
|
||||
}
|
||||
|
||||
for( int fx_ch = 0; fx_ch <= NumFxChannels ; ++fx_ch )
|
||||
for( int fx_ch = 0; fx_ch <= NumFLFxChannels ; ++fx_ch )
|
||||
{
|
||||
FxChannel * ch = engine::fxMixer()->effectChannel( fx_ch );
|
||||
if( !ch )
|
||||
@@ -1857,7 +1864,7 @@ p->putValue( jt->pos, value, false );
|
||||
break;
|
||||
}
|
||||
if( effName.isEmpty() || it->fxChannel < 0 ||
|
||||
it->fxChannel > NumFxChannels )
|
||||
it->fxChannel > NumFLFxChannels )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -44,11 +44,11 @@ AutomatableModel::AutomatableModel( DataType _type,
|
||||
const QString & _display_name,
|
||||
bool _default_constructed ) :
|
||||
Model( _parent, _display_name, _default_constructed ),
|
||||
m_minValue( _min ),
|
||||
m_maxValue( _max ),
|
||||
m_dataType( _type ),
|
||||
m_value( _val ),
|
||||
m_initValue( _val ),
|
||||
m_minValue( _min ),
|
||||
m_maxValue( _max ),
|
||||
m_step( _step ),
|
||||
m_range( _max - _min ),
|
||||
m_journalEntryReady( false ),
|
||||
|
||||
@@ -179,6 +179,25 @@ void bufMixNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src,
|
||||
|
||||
|
||||
|
||||
void bufMixCoeffNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src,
|
||||
float _coeff, int _frames )
|
||||
{
|
||||
for( int i = 0; i < _frames; )
|
||||
{
|
||||
_dst[i+0][0] += _src[i+0][0]*_coeff;
|
||||
_dst[i+0][1] += _src[i+0][1]*_coeff;
|
||||
_dst[i+1][0] += _src[i+1][0]*_coeff;
|
||||
_dst[i+1][1] += _src[i+1][1]*_coeff;
|
||||
_dst[i+2][0] += _src[i+2][0]*_coeff;
|
||||
_dst[i+2][1] += _src[i+2][1]*_coeff;
|
||||
_dst[i+3][0] += _src[i+3][0]*_coeff;
|
||||
_dst[i+3][1] += _src[i+3][1]*_coeff;
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void bufMixLRCoeffNoOpt( sampleFrameA * RP _dst,
|
||||
const sampleFrameA * RP _src,
|
||||
float _left, float _right, int _frames )
|
||||
@@ -306,6 +325,7 @@ MemCpyFunc memCpy = memCpyNoOpt;
|
||||
MemClearFunc memClear = memClearNoOpt;
|
||||
BufApplyGainFunc bufApplyGain = bufApplyGainNoOpt;
|
||||
BufMixFunc bufMix = bufMixNoOpt;
|
||||
BufMixCoeffFunc bufMixCoeff = bufMixCoeffNoOpt;
|
||||
BufMixLRCoeffFunc bufMixLRCoeff = bufMixLRCoeffNoOpt;
|
||||
UnalignedBufMixLRCoeffFunc unalignedBufMixLRCoeff = unalignedBufMixLRCoeffNoOpt;
|
||||
BufWetDryMixFunc bufWetDryMix = bufWetDryMixNoOpt;
|
||||
@@ -337,6 +357,7 @@ void memCpySSE( void * RP _dst, const void * RP _src, int _size );
|
||||
void memClearSSE( void * RP _dst, int _size );
|
||||
void bufApplyGainSSE( sampleFrameA * RP _dst, float _gain, int _frames );
|
||||
void bufMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, int _frames );
|
||||
void bufMixCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _coeff, int _frames );
|
||||
void bufMixLRCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames );
|
||||
void unalignedBufMixLRCoeffSSE( sampleFrame * RP _dst, const sampleFrame * RP _src, const float _left, const float _right, int _frames );
|
||||
void bufWetDryMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _wet, float _dry, int _frames );
|
||||
@@ -447,6 +468,7 @@ void init()
|
||||
memClear = memClearSSE;
|
||||
bufApplyGain = bufApplyGainSSE;
|
||||
bufMix = bufMixSSE;
|
||||
bufMixCoeff = bufMixCoeffSSE;
|
||||
bufMixLRCoeff = bufMixLRCoeffSSE;
|
||||
unalignedBufMixLRCoeff = unalignedBufMixLRCoeffSSE;
|
||||
bufWetDryMix = bufWetDryMixSSE;
|
||||
|
||||
@@ -229,6 +229,29 @@ void bufMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src,
|
||||
}
|
||||
|
||||
|
||||
void bufMixCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src,
|
||||
float _coeff, int _frames )
|
||||
{
|
||||
int i;
|
||||
|
||||
PREFETCH_READ(_src);
|
||||
PREFETCH_WRITE(_dst);
|
||||
|
||||
for( i = 0; i < _frames; )
|
||||
{
|
||||
_dst[i+0][0] += _src[i+0][0]*_coeff;
|
||||
_dst[i+0][1] += _src[i+0][1]*_coeff;
|
||||
_dst[i+1][0] += _src[i+1][0]*_coeff;
|
||||
_dst[i+1][1] += _src[i+1][1]*_coeff;
|
||||
_dst[i+2][0] += _src[i+2][0]*_coeff;
|
||||
_dst[i+2][1] += _src[i+2][1]*_coeff;
|
||||
_dst[i+3][0] += _src[i+3][0]*_coeff;
|
||||
_dst[i+3][1] += _src[i+3][1]*_coeff;
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bufMixLRCoeffSSE( sampleFrameA * RP _dst,
|
||||
const sampleFrameA * RP _src,
|
||||
float _left, float _right, int _frames )
|
||||
|
||||
@@ -22,18 +22,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <QtXml/QDomElement>
|
||||
|
||||
#include "FxMixer.h"
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "Cpu.h"
|
||||
#include "Effect.h"
|
||||
#include "song.h"
|
||||
|
||||
#include "InstrumentTrack.h"
|
||||
#include "bb_track_container.h"
|
||||
|
||||
|
||||
FxChannel::FxChannel( Model * _parent ) :
|
||||
m_fxChain( NULL ),
|
||||
m_used( false ),
|
||||
m_stillRunning( false ),
|
||||
m_peakLeft( 0.0f ),
|
||||
m_peakRight( 0.0f ),
|
||||
@@ -41,7 +43,8 @@ FxChannel::FxChannel( Model * _parent ) :
|
||||
m_muteModel( false, _parent ),
|
||||
m_volumeModel( 1.0, 0.0, 2.0, 0.01, _parent ),
|
||||
m_name(),
|
||||
m_lock()
|
||||
m_lock(),
|
||||
m_queued( false )
|
||||
{
|
||||
engine::getMixer()->clearAudioBuffer( m_buffer,
|
||||
engine::getMixer()->framesPerPeriod() );
|
||||
@@ -57,34 +60,361 @@ FxChannel::~FxChannel()
|
||||
|
||||
|
||||
|
||||
void FxChannel::doProcessing( sampleFrame * _buf )
|
||||
{
|
||||
FxMixer * fxm = engine::fxMixer();
|
||||
const fpp_t fpp = engine::getMixer()->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;
|
||||
|
||||
if( ! m_muteModel.value() )
|
||||
{
|
||||
// do mixer sends. loop through the channels that send to this one
|
||||
for( int i = 0; i < m_receives.size(); ++i)
|
||||
{
|
||||
fx_ch_t senderIndex = m_receives[i];
|
||||
FxChannel * sender = fxm->effectChannel(senderIndex);
|
||||
|
||||
// mix it with this one
|
||||
float amt = fxm->channelSendModel(senderIndex,
|
||||
m_channelIndex)->value();
|
||||
CPU::bufMixCoeff( _buf, sender->m_buffer,
|
||||
sender->m_volumeModel.value() * amt, fpp );
|
||||
}
|
||||
|
||||
const float v = m_volumeModel.value();
|
||||
|
||||
m_fxChain.startRunning();
|
||||
m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp );
|
||||
m_peakLeft = engine::getMixer()->peakValueLeft( _buf, fpp ) * v;
|
||||
m_peakRight = engine::getMixer()->peakValueRight( _buf, fpp ) * v;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_peakLeft = m_peakRight = 0.0f;
|
||||
}
|
||||
|
||||
m_state = ThreadableJob::Done;
|
||||
|
||||
// check if any of its parents are now able to be processed
|
||||
for(int i=0; i<m_sends.size(); ++i)
|
||||
{
|
||||
// if parent.unstarted and every parent.leaf.done:
|
||||
FxChannel * parent = fxm->effectChannel(m_sends[i]);
|
||||
if( parent->state() == ThreadableJob::Unstarted )
|
||||
{
|
||||
bool everyLeafDone = true;
|
||||
for( int j=0; j<parent->m_receives.size(); ++j )
|
||||
{
|
||||
if( fxm->effectChannel( parent->m_receives[j] )->state() !=
|
||||
ThreadableJob::Done )
|
||||
{
|
||||
everyLeafDone = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( everyLeafDone )
|
||||
{
|
||||
MixerWorkerThread::addJob(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int FxMixer::createChannel()
|
||||
{
|
||||
// create new channel
|
||||
m_fxChannels.push_back(new FxChannel( this ));
|
||||
|
||||
// reset channel state
|
||||
int index = m_fxChannels.size() - 1;
|
||||
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<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];
|
||||
int val = inst->effectChannelModel()->value(0);
|
||||
if( val == index )
|
||||
{
|
||||
// we are deleting this track's fx send
|
||||
// send to master
|
||||
inst->effectChannelModel()->setValue(0);
|
||||
}
|
||||
else if( val > index )
|
||||
{
|
||||
// subtract 1 to make up for the missing channel
|
||||
inst->effectChannelModel()->setValue(val-1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete all of this channel's sends and receives
|
||||
for(int i=0; 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::moveChannelLeft(int index)
|
||||
{
|
||||
// can't move master or first channel
|
||||
if( index <= 1 || index >= m_fxChannels.size() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// channels to swap
|
||||
int a = index - 1, b = index;
|
||||
|
||||
// go through every instrument and adjust for the channel index change
|
||||
QVector<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];
|
||||
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 )
|
||||
{
|
||||
@@ -93,7 +423,6 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch )
|
||||
m_fxChannels[_ch]->m_lock.lock();
|
||||
CPU::bufMix( m_fxChannels[_ch]->m_buffer, _buf,
|
||||
engine::getMixer()->framesPerPeriod() );
|
||||
m_fxChannels[_ch]->m_used = true;
|
||||
m_fxChannels[_ch]->m_lock.unlock();
|
||||
}
|
||||
}
|
||||
@@ -101,40 +430,6 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch )
|
||||
|
||||
|
||||
|
||||
void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf )
|
||||
{
|
||||
if( m_fxChannels[_ch]->m_muteModel.value() == false &&
|
||||
( m_fxChannels[_ch]->m_used ||
|
||||
m_fxChannels[_ch]->m_stillRunning ||
|
||||
_ch == 0 ) )
|
||||
{
|
||||
if( _buf == NULL )
|
||||
{
|
||||
_buf = m_fxChannels[_ch]->m_buffer;
|
||||
}
|
||||
const fpp_t f = engine::getMixer()->framesPerPeriod();
|
||||
m_fxChannels[_ch]->m_fxChain.startRunning();
|
||||
m_fxChannels[_ch]->m_stillRunning =
|
||||
m_fxChannels[_ch]->m_fxChain.processAudioBuffer(
|
||||
_buf, f );
|
||||
m_fxChannels[_ch]->m_peakLeft =
|
||||
engine::getMixer()->peakValueLeft( _buf, f ) *
|
||||
m_fxChannels[_ch]->m_volumeModel.value();
|
||||
m_fxChannels[_ch]->m_peakRight =
|
||||
engine::getMixer()->peakValueRight( _buf, f ) *
|
||||
m_fxChannels[_ch]->m_volumeModel.value();
|
||||
m_fxChannels[_ch]->m_used = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fxChannels[_ch]->m_peakLeft =
|
||||
m_fxChannels[_ch]->m_peakRight = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FxMixer::prepareMasterMix()
|
||||
{
|
||||
engine::getMixer()->clearAudioBuffer( m_fxChannels[0]->m_buffer,
|
||||
@@ -143,38 +438,53 @@ void FxMixer::prepareMasterMix()
|
||||
|
||||
|
||||
|
||||
void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf )
|
||||
{
|
||||
FxChannel * thisCh = m_fxChannels[_ch];
|
||||
|
||||
// remember what channel number we are, 'cause we need it later
|
||||
thisCh->m_channelIndex = _ch;
|
||||
|
||||
// if we're muted or this channel is seen already, discount it
|
||||
if( thisCh->m_muteModel.value() || thisCh->m_queued )
|
||||
return;
|
||||
|
||||
int numDeps = thisCh->m_receives.size();
|
||||
if( numDeps > 0 )
|
||||
{
|
||||
for(int i=0; i<numDeps; ++i)
|
||||
{
|
||||
addChannelLeaf( thisCh->m_receives[i], _buf );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// add this channel to job list
|
||||
thisCh->m_queued = true;
|
||||
MixerWorkerThread::addJob( thisCh );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::masterMix( sampleFrame * _buf )
|
||||
{
|
||||
const int fpp = engine::getMixer()->framesPerPeriod();
|
||||
|
||||
// recursively loop through channel dependency chain
|
||||
// and add all channels to job list that have no dependencies
|
||||
// when the channel completes it will check its parent to see if it needs
|
||||
// to be processed.
|
||||
MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic );
|
||||
addChannelLeaf( 0, _buf );
|
||||
while( m_fxChannels[0]->state() != ThreadableJob::Done )
|
||||
{
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
}
|
||||
|
||||
memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp );
|
||||
|
||||
for( int i = 1; i < NumFxChannels+1; ++i )
|
||||
{
|
||||
if( m_fxChannels[i]->m_used )
|
||||
{
|
||||
sampleFrame * ch_buf = m_fxChannels[i]->m_buffer;
|
||||
const float v = m_fxChannels[i]->m_volumeModel.value();
|
||||
for( f_cnt_t f = 0; f < fpp; ++f )
|
||||
{
|
||||
_buf[f][0] += ch_buf[f][0] * v;
|
||||
_buf[f][1] += ch_buf[f][1] * v;
|
||||
}
|
||||
engine::getMixer()->clearAudioBuffer( ch_buf,
|
||||
engine::getMixer()->framesPerPeriod() );
|
||||
m_fxChannels[i]->m_used = false;
|
||||
}
|
||||
}
|
||||
|
||||
processChannel( 0, _buf );
|
||||
|
||||
if( m_fxChannels[0]->m_muteModel.value() )
|
||||
{
|
||||
engine::getMixer()->clearAudioBuffer( _buf,
|
||||
engine::getMixer()->framesPerPeriod() );
|
||||
return;
|
||||
}
|
||||
|
||||
const float v = m_fxChannels[0]->m_volumeModel.value();
|
||||
for( f_cnt_t f = 0; f < engine::getMixer()->framesPerPeriod(); ++f )
|
||||
{
|
||||
@@ -184,6 +494,16 @@ void FxMixer::masterMix( sampleFrame * _buf )
|
||||
|
||||
m_fxChannels[0]->m_peakLeft *= engine::getMixer()->masterGain();
|
||||
m_fxChannels[0]->m_peakRight *= engine::getMixer()->masterGain();
|
||||
|
||||
// clear all channel buffers and
|
||||
// reset channel process state
|
||||
for( int i = 0; i < numChannels(); ++i)
|
||||
{
|
||||
engine::getMixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer,
|
||||
engine::getMixer()->framesPerPeriod() );
|
||||
m_fxChannels[i]->reset();
|
||||
m_fxChannels[i]->m_queued = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,58 +511,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "AudioOutputContext.h"
|
||||
#include "Mixer.h"
|
||||
#include "FxMixer.h"
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "play_handle.h"
|
||||
#include "song.h"
|
||||
#include "templates.h"
|
||||
@@ -62,214 +63,6 @@
|
||||
#endif
|
||||
|
||||
|
||||
static QVector<fx_ch_t> __fx_channel_jobs( NumFxChannels );
|
||||
|
||||
|
||||
|
||||
class MixerWorkerThread : public QThread
|
||||
{
|
||||
public:
|
||||
enum JobTypes
|
||||
{
|
||||
InvalidJob,
|
||||
PlayHandle,
|
||||
AudioPortEffects,
|
||||
EffectChannel,
|
||||
NumJobTypes
|
||||
} ;
|
||||
|
||||
struct JobQueueItem
|
||||
{
|
||||
JobQueueItem() :
|
||||
type( InvalidJob ),
|
||||
job( NULL ),
|
||||
param( 0 ),
|
||||
done( false )
|
||||
{
|
||||
}
|
||||
JobQueueItem( JobTypes _type, void * _job, int _param = 0 ) :
|
||||
type( _type ),
|
||||
job( _job ),
|
||||
param( _param ),
|
||||
done( false )
|
||||
{
|
||||
}
|
||||
|
||||
JobTypes type;
|
||||
void * job;
|
||||
int param;
|
||||
|
||||
QAtomicInt done;
|
||||
|
||||
} ;
|
||||
|
||||
struct JobQueue
|
||||
{
|
||||
#define JOB_QUEUE_SIZE 1024
|
||||
JobQueue() :
|
||||
queueSize( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
JobQueueItem items[JOB_QUEUE_SIZE];
|
||||
int queueSize;
|
||||
QAtomicInt itemsDone;
|
||||
} ;
|
||||
|
||||
static JobQueue s_jobQueue;
|
||||
|
||||
MixerWorkerThread( int _worker_num, Mixer * _mixer ) :
|
||||
QThread( _mixer ),
|
||||
m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ),
|
||||
m_workerNum( _worker_num ),
|
||||
m_quit( false ),
|
||||
m_mixer( _mixer ),
|
||||
m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond )
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~MixerWorkerThread()
|
||||
{
|
||||
CPU::freeFrames( m_workingBuf );
|
||||
}
|
||||
|
||||
virtual void quit()
|
||||
{
|
||||
m_quit = true;
|
||||
}
|
||||
|
||||
void processJobQueue();
|
||||
|
||||
|
||||
private:
|
||||
virtual void run()
|
||||
{
|
||||
#if 0
|
||||
#ifdef LMMS_BUILD_LINUX
|
||||
#ifdef LMMS_HAVE_PTHREAD_H
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO( &mask );
|
||||
CPU_SET( m_workerNum, &mask );
|
||||
pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask );
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
QMutex m;
|
||||
while( m_quit == false )
|
||||
{
|
||||
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( qBound<int>( 32,
|
||||
configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt(),
|
||||
@@ -297,11 +90,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;
|
||||
}
|
||||
|
||||
m_workingBuf = CPU::allocFrames( m_framesPerPeriod );
|
||||
for( Uint8 i = 0; i < 3; i++ )
|
||||
{
|
||||
@@ -312,7 +100,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 );
|
||||
@@ -335,14 +123,11 @@ Mixer::Mixer() :
|
||||
|
||||
Mixer::~Mixer()
|
||||
{
|
||||
// distribute an empty job-queue so that worker-threads
|
||||
// get out of their processing-loop
|
||||
MixerWorkerThread::s_jobQueue.queueSize = 0;
|
||||
for( int w = 0; w < m_numWorkers; ++w )
|
||||
{
|
||||
m_workers[w]->quit();
|
||||
}
|
||||
START_JOBS();
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
for( int w = 0; w < m_numWorkers; ++w )
|
||||
{
|
||||
m_workers[w]->wait( 500 );
|
||||
@@ -495,6 +280,8 @@ sampleFrameA * Mixer::renderNextBuffer()
|
||||
MicroTimer timer;
|
||||
static song::playPos last_metro_pos = -1;
|
||||
|
||||
FxMixer * fxm = engine::fxMixer();
|
||||
|
||||
song::playPos p = engine::getSong()->getPlayPos( song::Mode_PlayPattern );
|
||||
if( engine::getSong()->playMode() == song::Mode_PlayPattern &&
|
||||
engine::getPianoRoll()->isRecording() == true &&
|
||||
@@ -547,18 +334,15 @@ sampleFrameA * Mixer::renderNextBuffer()
|
||||
clearAudioBuffer( m_writeBuf, m_framesPerPeriod );
|
||||
|
||||
// prepare master mix (clear internal buffers etc.)
|
||||
engine::fxMixer()->prepareMasterMix();
|
||||
fxm->prepareMasterMix();
|
||||
|
||||
// create play-handles for new notes, samples etc.
|
||||
engine::getSong()->processNextBuffer();
|
||||
|
||||
|
||||
// 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<PlayHandleList>( m_playHandles );
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
|
||||
// removed all play handles which are done
|
||||
for( PlayHandleList::Iterator it = m_playHandles.begin();
|
||||
@@ -583,21 +367,11 @@ sampleFrameA * 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
|
||||
engine::fxMixer()->masterMix( m_writeBuf );
|
||||
// STAGE 3: do master mix in FX mixer
|
||||
fxm->masterMix( m_writeBuf );
|
||||
|
||||
unlock();
|
||||
|
||||
|
||||
166
src/core/MixerWorkerThread.cpp
Normal file
166
src/core/MixerWorkerThread.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* MixerWorkerThread.cpp - implementation of MixerWorkerThread
|
||||
*
|
||||
* Copyright (c) 2009 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 "Cpu.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( CPU::allocFrames( _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()
|
||||
{
|
||||
CPU::freeFrames( 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AudioPort.h"
|
||||
#include "AudioBackend.h"
|
||||
#include "AudioOutputContext.h"
|
||||
#include "EffectChain.h"
|
||||
#include "engine.h"
|
||||
#include "AudioPort.h"
|
||||
#include "Cpu.h"
|
||||
#include "EffectChain.h"
|
||||
#include "FxMixer.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) :
|
||||
@@ -126,3 +127,15 @@ bool AudioPort::processEffects()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioPort::doProcessing( sampleFrame * )
|
||||
{
|
||||
const bool me = processEffects();
|
||||
if( me || m_bufferUsage != NoUsage )
|
||||
{
|
||||
engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() );
|
||||
nextPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QLocale>
|
||||
#include <QtCore/QProcess>
|
||||
@@ -457,6 +458,13 @@ int main( int argc, char * * argv )
|
||||
// srandom() calls in their init procedure
|
||||
srand( getpid() + time( 0 ) );
|
||||
|
||||
// recover a file?
|
||||
QString recoveryFile = QDir(configManager::inst()->workingDir()).absoluteFilePath("recover.mmp");
|
||||
if( QFileInfo(recoveryFile).exists() )
|
||||
{
|
||||
file_to_load = recoveryFile;
|
||||
}
|
||||
|
||||
// we try to load given file
|
||||
if( !file_to_load.isEmpty() )
|
||||
{
|
||||
@@ -531,7 +539,7 @@ int main( int argc, char * * argv )
|
||||
}
|
||||
else
|
||||
{
|
||||
engine::getSong()->saveProjectAs( file_to_save );
|
||||
engine::getSong()->saveProjectFile( file_to_save );
|
||||
return( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,6 +904,26 @@ void song::loadProject( const QString & _file_name )
|
||||
firstChildElement( "track" ) );
|
||||
}
|
||||
QDomNode node = mmp.content().firstChild();
|
||||
|
||||
// walk through and fix up the mixer
|
||||
while( !node.isNull() )
|
||||
{
|
||||
if( node.nodeName() == engine::fxMixer()->nodeName() )
|
||||
{
|
||||
engine::fxMixer()->restoreState( node.toElement() );
|
||||
|
||||
if( engine::hasGUI() )
|
||||
{
|
||||
// refresh FxMixerView
|
||||
engine::fxMixerView()->refreshDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
node = node.nextSibling();
|
||||
}
|
||||
|
||||
node = mmp.content().firstChild();
|
||||
|
||||
while( !node.isNull() )
|
||||
{
|
||||
if( node.isElement() )
|
||||
@@ -917,10 +937,6 @@ void song::loadProject( const QString & _file_name )
|
||||
{
|
||||
restoreControllerStates( node.toElement() );
|
||||
}
|
||||
else if( node.nodeName() == engine::fxMixer()->nodeName() )
|
||||
{
|
||||
engine::fxMixer()->restoreState( node.toElement() );
|
||||
}
|
||||
else if( engine::hasGUI() )
|
||||
{
|
||||
if( node.nodeName() ==
|
||||
@@ -973,7 +989,6 @@ void song::loadProject( const QString & _file_name )
|
||||
// resolve all IDs so that autoModels are automated
|
||||
automationPattern::resolveAllIDs();
|
||||
|
||||
|
||||
engine::getMixer()->unlock();
|
||||
|
||||
configManager::inst()->addRecentlyOpenedProject( _file_name );
|
||||
@@ -994,10 +1009,8 @@ void song::loadProject( const QString & _file_name )
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// save current song
|
||||
bool song::saveProject()
|
||||
// only save current song as _filename and do nothing else
|
||||
bool song::saveProjectFile( const QString & _filename )
|
||||
{
|
||||
multimediaProject mmp( multimediaProject::SongProject );
|
||||
|
||||
@@ -1006,7 +1019,6 @@ bool song::saveProject()
|
||||
m_masterVolumeModel.saveSettings( mmp, mmp.head(), "mastervol" );
|
||||
m_masterPitchModel.saveSettings( mmp, mmp.head(), "masterpitch" );
|
||||
|
||||
|
||||
saveState( mmp, mmp.content() );
|
||||
|
||||
m_globalAutomationTrack->saveState( mmp, mmp.content() );
|
||||
@@ -1024,8 +1036,17 @@ bool song::saveProject()
|
||||
|
||||
saveControllerStates( mmp, mmp.content() );
|
||||
|
||||
return mmp.writeFile( _filename );
|
||||
}
|
||||
|
||||
|
||||
|
||||
// save current song and update the gui
|
||||
bool song::guiSaveProject()
|
||||
{
|
||||
multimediaProject mmp( multimediaProject::SongProject );
|
||||
m_fileName = mmp.nameWithExtension( m_fileName );
|
||||
if( mmp.writeFile( m_fileName ) == true && engine::hasGUI() )
|
||||
if( saveProjectFile( m_fileName ) && engine::hasGUI() )
|
||||
{
|
||||
textFloat::displayMessage( tr( "Project saved" ),
|
||||
tr( "The project %1 is now saved."
|
||||
@@ -1052,12 +1073,12 @@ bool song::saveProject()
|
||||
|
||||
|
||||
// save current song in given filename
|
||||
bool song::saveProjectAs( const QString & _file_name )
|
||||
bool song::guiSaveProjectAs( const QString & _file_name )
|
||||
{
|
||||
QString o = m_oldFileName;
|
||||
m_oldFileName = m_fileName;
|
||||
m_fileName = _file_name;
|
||||
if( saveProject() == false )
|
||||
if( guiSaveProject() == false )
|
||||
{
|
||||
m_fileName = m_oldFileName;
|
||||
m_oldFileName = o;
|
||||
|
||||
92
src/gui/FxLine.cpp
Normal file
92
src/gui/FxLine.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#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"
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
void FxLine::paintEvent( QPaintEvent * )
|
||||
{
|
||||
FxMixer * mix = engine::fxMixer();
|
||||
bool sendToThis = mix->channelSendModel(
|
||||
m_mv->currentFxLine()->m_channelIndex, m_channelIndex) != NULL;
|
||||
QPainter painter;
|
||||
painter.begin( this );
|
||||
engine::getLmmsStyle()->drawFxLine( &painter, this,
|
||||
mix->effectChannel(m_channelIndex)->m_name,
|
||||
m_mv->currentFxLine() == this, sendToThis );
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void FxLine::mousePressEvent( QMouseEvent * )
|
||||
{
|
||||
m_mv->setCurrentFxLine( this );
|
||||
}
|
||||
|
||||
void FxLine::mouseDoubleClickEvent( QMouseEvent * )
|
||||
{
|
||||
bool ok;
|
||||
FxMixer * mix = engine::fxMixer();
|
||||
QString new_name = QInputDialog::getText( this,
|
||||
FxMixerView::tr( "Rename FX channel" ),
|
||||
FxMixerView::tr( "Enter the new name for this "
|
||||
"FX channel" ),
|
||||
QLineEdit::Normal, mix->effectChannel(m_channelIndex)->m_name, &ok );
|
||||
if( ok && !new_name.isEmpty() )
|
||||
{
|
||||
mix->effectChannel(m_channelIndex)->m_name = new_name;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_FxLine.cxx"
|
||||
@@ -22,6 +22,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDebug>
|
||||
|
||||
#include <QtGui/QButtonGroup>
|
||||
#include <QtGui/QInputDialog>
|
||||
#include <QtGui/QLayout>
|
||||
@@ -31,71 +34,20 @@
|
||||
#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 "lcd_spinbox.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 painter;
|
||||
painter.begin( this );
|
||||
engine::getLmmsStyle()->drawFxLine( &painter,
|
||||
this, m_name, m_mv->currentFxLine() == this );
|
||||
painter.end();
|
||||
}
|
||||
|
||||
virtual void mousePressEvent( QMouseEvent * )
|
||||
{
|
||||
m_mv->setCurrentFxLine( this );
|
||||
}
|
||||
|
||||
virtual void mouseDoubleClickEvent( QMouseEvent * )
|
||||
{
|
||||
bool ok;
|
||||
QString new_name = QInputDialog::getText( this,
|
||||
FxMixerView::tr( "Rename FX channel" ),
|
||||
FxMixerView::tr( "Enter the new name for this "
|
||||
"FX channel" ),
|
||||
QLineEdit::Normal, m_name, &ok );
|
||||
if( ok && !new_name.isEmpty() )
|
||||
{
|
||||
m_name = new_name;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
FxMixerView * m_mv;
|
||||
QString & m_name;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
#include "InstrumentTrack.h"
|
||||
#include "song.h"
|
||||
#include "bb_track_container.h"
|
||||
|
||||
FxMixerView::FxMixerView() :
|
||||
QWidget(),
|
||||
@@ -105,118 +57,81 @@ FxMixerView::FxMixerView() :
|
||||
FxMixer * m = engine::fxMixer();
|
||||
m->setHook( this );
|
||||
|
||||
QPalette pal = palette();
|
||||
//QPalette pal = palette();
|
||||
//pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) );
|
||||
//setPalette( pal );
|
||||
setAutoFillBackground( true );
|
||||
setSizePolicy( QSizePolicy::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 );
|
||||
}
|
||||
lcdSpinBox * l = new lcdSpinBox( 2, cv->m_fxLine );
|
||||
l->model()->setRange( i, i );
|
||||
l->model()->setValue( i );
|
||||
l->move( 2, 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() ) );
|
||||
@@ -226,10 +141,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 );
|
||||
@@ -238,15 +153,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 )
|
||||
{
|
||||
@@ -262,18 +237,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,23 +427,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -306,28 +447,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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ MainWindow::MainWindow() :
|
||||
m_workspace( NULL ),
|
||||
m_templatesMenu( NULL ),
|
||||
m_recentlyOpenedProjectsMenu( NULL ),
|
||||
m_toolsMenu( NULL )
|
||||
m_toolsMenu( NULL ),
|
||||
m_autoSaveTimer( this )
|
||||
{
|
||||
setAttribute( Qt::WA_DeleteOnClose );
|
||||
|
||||
@@ -154,6 +155,10 @@ MainWindow::MainWindow() :
|
||||
|
||||
m_updateTimer.start( 1000 / 20, this ); // 20 fps
|
||||
|
||||
// connect auto save
|
||||
connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave()));
|
||||
m_autoSaveTimer.start(1000 * 60); // 1 minute
|
||||
|
||||
m_welcomeScreen = new WelcomeScreen( this );
|
||||
m_welcomeScreen->setVisible( false );
|
||||
}
|
||||
@@ -1007,7 +1012,7 @@ bool MainWindow::saveProject()
|
||||
}
|
||||
else
|
||||
{
|
||||
engine::getSong()->saveProject();
|
||||
engine::getSong()->guiSaveProject();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1036,7 +1041,7 @@ bool MainWindow::saveProjectAs()
|
||||
if( sfd.exec () == QFileDialog::Accepted &&
|
||||
!sfd.selectedFiles().isEmpty() && sfd.selectedFiles()[0] != "" )
|
||||
{
|
||||
engine::getSong()->saveProjectAs(
|
||||
engine::getSong()->guiSaveProjectAs(
|
||||
sfd.selectedFiles()[0] );
|
||||
return true;
|
||||
}
|
||||
@@ -1195,6 +1200,9 @@ void MainWindow::closeEvent( QCloseEvent * _ce )
|
||||
{
|
||||
if( mayChangeProject() )
|
||||
{
|
||||
// delete recovery file
|
||||
QDir working(configManager::inst()->workingDir());
|
||||
working.remove("recover.mmp");
|
||||
_ce->accept();
|
||||
}
|
||||
else
|
||||
@@ -1400,7 +1408,7 @@ void MainWindow::keyReleaseEvent( QKeyEvent * _ke )
|
||||
|
||||
|
||||
|
||||
void MainWindow::timerEvent( QTimerEvent * )
|
||||
void MainWindow::timerEvent( QTimerEvent * _te)
|
||||
{
|
||||
emit periodicUpdate();
|
||||
}
|
||||
@@ -1570,6 +1578,13 @@ void MainWindow::toggleRecordAutomation( bool _recording )
|
||||
|
||||
|
||||
|
||||
void MainWindow::autoSave()
|
||||
{
|
||||
QDir work(configManager::inst()->workingDir());
|
||||
engine::getSong()->saveProjectFile(work.absoluteFilePath("recover.mmp"));
|
||||
}
|
||||
|
||||
|
||||
#include "moc_MainWindow.cxx"
|
||||
|
||||
/* vim: set tw=0 noexpandtab: */
|
||||
|
||||
53
src/gui/SendButtonIndicator.cpp
Normal file
53
src/gui/SendButtonIndicator.cpp
Normal 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 );
|
||||
}
|
||||
@@ -279,7 +279,7 @@ int ClassicStyle::pixelMetric( PixelMetric _metric,
|
||||
|
||||
|
||||
void ClassicStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine,
|
||||
const QString & _name, bool _active )
|
||||
const QString & _name, bool _active, bool _sendToThis )
|
||||
{
|
||||
int width = _fxLine->rect().width();
|
||||
int height = _fxLine->rect().height();
|
||||
@@ -293,10 +293,18 @@ void ClassicStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine,
|
||||
p->setPen( QColor( 20, 24, 32 ) );
|
||||
p->drawRect( 0, 0, width-1, height-1 );
|
||||
|
||||
// draw the mixer send background
|
||||
if( _sendToThis )
|
||||
{
|
||||
p->drawPixmap(2, 0, 28, 56,
|
||||
embed::getIconPixmap("send_bg_arrow", 28, 56));
|
||||
}
|
||||
|
||||
// draw the channel name
|
||||
p->rotate( -90 );
|
||||
p->setPen( _active ? QColor( 0, 255, 0 ) : Qt::white );
|
||||
p->setFont( pointSizeF( _fxLine->font(), 7.5f ) );
|
||||
p->drawText( -90, 20, _name );
|
||||
p->drawText( -145, 20, _name );
|
||||
}
|
||||
|
||||
void ClassicStyle::drawTrackContentBackground(QPainter * _painter,
|
||||
|
||||
@@ -881,7 +881,7 @@ int CusisStyle::pixelMetric( PixelMetric _metric, const QStyleOption * _option,
|
||||
|
||||
|
||||
void CusisStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine,
|
||||
const QString & _name, bool _active )
|
||||
const QString & _name, bool _active, bool _sendToThis )
|
||||
{
|
||||
int width = _fxLine->rect().width();
|
||||
int height = _fxLine->rect().height();
|
||||
|
||||
@@ -43,7 +43,7 @@ EffectRackView::EffectRackView( EffectChain * _model, QWidget * _parent ) :
|
||||
|
||||
m_mainLayout = new QVBoxLayout( this );
|
||||
m_mainLayout->setSpacing( 0 );
|
||||
m_mainLayout->setMargin( 5 );
|
||||
m_mainLayout->setMargin( 0 );
|
||||
|
||||
m_effectsGroupBox = new groupBox( tr( "EFFECTS CHAIN" ) );
|
||||
m_mainLayout->addWidget( m_effectsGroupBox );
|
||||
|
||||
@@ -492,7 +492,11 @@ void knob::mouseMoveEvent( QMouseEvent * _me )
|
||||
|
||||
void knob::mouseReleaseEvent( QMouseEvent * /* _me*/ )
|
||||
{
|
||||
model()->addJournalEntryFromOldToCurVal();
|
||||
AutomatableModel * thisModel = model();
|
||||
if( thisModel )
|
||||
{
|
||||
thisModel->addJournalEntryFromOldToCurVal();
|
||||
}
|
||||
|
||||
m_buttonPressed = false;
|
||||
|
||||
|
||||
@@ -103,13 +103,14 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) :
|
||||
this, tr( "Panning" ) ),
|
||||
m_pitchModel( 0, -100, 100, 1, this, tr( "Pitch" ) ),
|
||||
m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ),
|
||||
m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ),
|
||||
m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), // change this so it's a combo box, all the channels and then new.
|
||||
m_instrument( NULL ),
|
||||
m_soundShaping( this ),
|
||||
m_arpeggiator( this ),
|
||||
m_chordCreator( this ),
|
||||
m_piano( this )
|
||||
{
|
||||
m_effectChannelModel.setRange( 0, engine::fxMixer()->numChannels()-1, 1);
|
||||
connect( baseNoteModel(), SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateBaseNote() ) );
|
||||
connect( &m_pitchModel, SIGNAL( dataChanged() ),
|
||||
|
||||
Reference in New Issue
Block a user