FxMixer, Mixer, FxMixerView: backported FX send support from master branch
There once have been huge efforts to implement FX send support in the master branch. In order to make it available on a stable base here's a backport which is non-trivial as there have been major rewrites of the mixer's worker thread architecture. There still seem to be bugs which we have to fix before merging into stable branch. Thanks to Andrew Kelley for the original work.
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
#ifndef SINGLE_SOURCE_COMPILE
|
||||
|
||||
/*
|
||||
* FxMixer.cpp - effect mixer for LMMS
|
||||
*
|
||||
* Copyright (c) 2008-2011 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
|
||||
@@ -24,17 +22,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <QtXml/QDomElement>
|
||||
|
||||
#include "FxMixer.h"
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "MixHelpers.h"
|
||||
#include "Effect.h"
|
||||
#include "song.h"
|
||||
|
||||
#include "InstrumentTrack.h"
|
||||
#include "bb_track_container.h"
|
||||
|
||||
FxChannel::FxChannel( Model * _parent ) :
|
||||
|
||||
FxChannel::FxChannel( int idx, Model * _parent ) :
|
||||
m_fxChain( NULL ),
|
||||
m_used( false ),
|
||||
m_stillRunning( false ),
|
||||
m_peakLeft( 0.0f ),
|
||||
m_peakRight( 0.0f ),
|
||||
@@ -42,7 +43,9 @@ FxChannel::FxChannel( Model * _parent ) :
|
||||
m_muteModel( false, _parent ),
|
||||
m_volumeModel( 1.0, 0.0, 2.0, 0.01, _parent ),
|
||||
m_name(),
|
||||
m_lock()
|
||||
m_lock(),
|
||||
m_channelIndex( idx ),
|
||||
m_queued( false )
|
||||
{
|
||||
engine::mixer()->clearAudioBuffer( m_buffer,
|
||||
engine::mixer()->framesPerPeriod() );
|
||||
@@ -59,81 +62,373 @@ FxChannel::~FxChannel()
|
||||
|
||||
|
||||
|
||||
void FxChannel::doProcessing( sampleFrame * _buf )
|
||||
{
|
||||
FxMixer * fxm = engine::fxMixer();
|
||||
const fpp_t fpp = engine::mixer()->framesPerPeriod();
|
||||
|
||||
// <tobydox> ignore the passed _buf
|
||||
// <tobydox> always use m_buffer
|
||||
// <tobydox> this is just an auxilliary buffer if doProcessing()
|
||||
// needs one for processing while running
|
||||
// <tobydox> particularly important for playHandles, so Instruments
|
||||
// can operate on this buffer the whole time
|
||||
// <tobydox> this improves cache hit rate
|
||||
_buf = m_buffer;
|
||||
|
||||
// SMF: OK, due to the fact, that the data from the audio-tracks has been
|
||||
// written into our buffer already, all which needs to be done at this
|
||||
// stage is to process inter-channel sends. I really don't like the idea
|
||||
// of using threads for this -- it just doesn't make any sense and wastes
|
||||
// cpu-cylces... so I just go through every child of this channel and
|
||||
// call the acc. doProcessing() directly.
|
||||
|
||||
if( m_muteModel.value() == false )
|
||||
{
|
||||
// OK, we are not muted, so we go recursively through all the channels
|
||||
// which send to us (our children)...
|
||||
foreach( fx_ch_t senderIndex, m_receives )
|
||||
{
|
||||
FxChannel * sender = fxm->effectChannel( senderIndex );
|
||||
|
||||
// wait for the sender job - either it's just been queued yet,
|
||||
// then ThreadableJob::process() will process it now within this
|
||||
// thread - otherwise it has been is is being processed by another
|
||||
// thread and we just have to wait for it to finish
|
||||
while( sender->state() != ThreadableJob::Done )
|
||||
{
|
||||
sender->process();
|
||||
}
|
||||
|
||||
// get the send level...
|
||||
const float amt =
|
||||
fxm->channelSendModel( senderIndex, m_channelIndex )->value();
|
||||
|
||||
// mix it's output with this one's output
|
||||
sampleFrame * ch_buf = sender->m_buffer;
|
||||
const float v = sender->m_volumeModel.value() * amt;
|
||||
for( f_cnt_t f = 0; f < fpp; ++f )
|
||||
{
|
||||
_buf[f][0] += ch_buf[f][0] * v;
|
||||
_buf[f][1] += ch_buf[f][1] * v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float v = m_volumeModel.value();
|
||||
|
||||
if( !engine::getSong()->isFreezingPattern() )
|
||||
{
|
||||
m_fxChain.startRunning();
|
||||
m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp );
|
||||
m_peakLeft = engine::mixer()->peakValueLeft( _buf, fpp ) * v;
|
||||
m_peakRight = engine::mixer()->peakValueRight( _buf, fpp ) * v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
FxMixer::FxMixer() :
|
||||
JournallingObject(),
|
||||
Model( NULL )
|
||||
Model( NULL ),
|
||||
m_fxChannels()
|
||||
{
|
||||
for( int i = 0; i < NumFxChannels+1; ++i )
|
||||
{
|
||||
m_fxChannels[i] = new FxChannel( this );
|
||||
}
|
||||
// reset name etc.
|
||||
clear();
|
||||
// create master channel
|
||||
createChannel();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
FxMixer::~FxMixer()
|
||||
{
|
||||
for( int i = 0; i < NumFxChannels+1; ++i )
|
||||
for( int i = 0; i < m_fxChannels.size(); ++i )
|
||||
{
|
||||
for( int j = 0; j < m_fxChannels[i]->m_sendAmount.size(); ++j)
|
||||
{
|
||||
delete m_fxChannels[i]->m_sendAmount[j];
|
||||
}
|
||||
delete m_fxChannels[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch )
|
||||
int FxMixer::createChannel()
|
||||
{
|
||||
if( m_fxChannels[_ch]->m_muteModel.value() == false )
|
||||
const int index = m_fxChannels.size();
|
||||
// create new channel
|
||||
m_fxChannels.push_back( new FxChannel( index, this ) );
|
||||
|
||||
// reset channel state
|
||||
clearChannel( index );
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
void FxMixer::deleteChannel(int index)
|
||||
{
|
||||
m_fxChannels[index]->m_lock.lock();
|
||||
|
||||
// go through every instrument and adjust for the channel index change
|
||||
QVector<track *> songTrackList = engine::getSong()->tracks();
|
||||
QVector<track *> bbTrackList = engine::getBBTrackContainer()->tracks();
|
||||
|
||||
QVector<track *> trackLists[] = {songTrackList, bbTrackList};
|
||||
for(int tl=0; tl<2; ++tl)
|
||||
{
|
||||
m_fxChannels[_ch]->m_lock.lock();
|
||||
sampleFrame * buf = m_fxChannels[_ch]->m_buffer;
|
||||
for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod();
|
||||
++f )
|
||||
QVector<track *> trackList = trackLists[tl];
|
||||
for(int i=0; i<trackList.size(); ++i)
|
||||
{
|
||||
buf[f][0] += _buf[f][0];
|
||||
buf[f][1] += _buf[f][1];
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
m_fxChannels[_ch]->m_used = true;
|
||||
m_fxChannels[_ch]->m_lock.unlock();
|
||||
}
|
||||
|
||||
// delete all of this channel's sends and receives
|
||||
for(int i=0; i<m_fxChannels[index]->m_sends.size(); ++i)
|
||||
{
|
||||
deleteChannelSend(index, m_fxChannels[index]->m_sends[i]);
|
||||
}
|
||||
for(int i=0; i<m_fxChannels[index]->m_receives.size(); ++i)
|
||||
{
|
||||
deleteChannelSend(m_fxChannels[index]->m_receives[i], index);
|
||||
}
|
||||
|
||||
for(int i=0; i<m_fxChannels.size(); ++i)
|
||||
{
|
||||
// for every send/receive, adjust for the channel index change
|
||||
for(int j=0; j<m_fxChannels[i]->m_sends.size(); ++j)
|
||||
{
|
||||
if( m_fxChannels[i]->m_sends[j] > index )
|
||||
{
|
||||
// subtract 1 to make up for the missing channel
|
||||
--m_fxChannels[i]->m_sends[j];
|
||||
}
|
||||
}
|
||||
for(int j=0; j<m_fxChannels[i]->m_receives.size(); ++j)
|
||||
{
|
||||
if( m_fxChannels[i]->m_receives[j] > index )
|
||||
{
|
||||
// subtract 1 to make up for the missing channel
|
||||
--m_fxChannels[i]->m_receives[j];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// actually delete the channel
|
||||
delete m_fxChannels[index];
|
||||
m_fxChannels.remove(index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf )
|
||||
void FxMixer::moveChannelLeft(int index)
|
||||
{
|
||||
if( m_fxChannels[_ch]->m_muteModel.value() == false &&
|
||||
( m_fxChannels[_ch]->m_used ||
|
||||
m_fxChannels[_ch]->m_stillRunning ||
|
||||
_ch == 0 ) )
|
||||
// can't move master or first channel
|
||||
if( index <= 1 || index >= m_fxChannels.size() )
|
||||
{
|
||||
if( _buf == NULL )
|
||||
{
|
||||
_buf = m_fxChannels[_ch]->m_buffer;
|
||||
}
|
||||
const fpp_t f = engine::mixer()->framesPerPeriod();
|
||||
if( !engine::getSong()->isFreezingPattern() )
|
||||
{
|
||||
m_fxChannels[_ch]->m_fxChain.startRunning();
|
||||
m_fxChannels[_ch]->m_stillRunning = m_fxChannels[_ch]->m_fxChain.processAudioBuffer( _buf, f );
|
||||
m_fxChannels[_ch]->m_peakLeft = engine::mixer()->peakValueLeft( _buf, f ) *
|
||||
m_fxChannels[_ch]->m_volumeModel.value();
|
||||
m_fxChannels[_ch]->m_peakRight = engine::mixer()->peakValueRight( _buf, f ) *
|
||||
m_fxChannels[_ch]->m_volumeModel.value();
|
||||
}
|
||||
m_fxChannels[_ch]->m_used = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// channels to swap
|
||||
int a = index - 1, b = index;
|
||||
|
||||
// go through every instrument and adjust for the channel index change
|
||||
QVector<track *> songTrackList = engine::getSong()->tracks();
|
||||
QVector<track *> bbTrackList = engine::getBBTrackContainer()->tracks();
|
||||
|
||||
QVector<track *> trackLists[] = {songTrackList, bbTrackList};
|
||||
for(int tl=0; tl<2; ++tl)
|
||||
{
|
||||
m_fxChannels[_ch]->m_peakLeft =
|
||||
m_fxChannels[_ch]->m_peakRight = 0.0f;
|
||||
QVector<track *> trackList = trackLists[tl];
|
||||
for(int i=0; i<trackList.size(); ++i)
|
||||
{
|
||||
if( trackList[i]->type() == track::InstrumentTrack )
|
||||
{
|
||||
InstrumentTrack * inst = (InstrumentTrack *) trackList[i];
|
||||
int val = inst->effectChannelModel()->value(0);
|
||||
if( val == a )
|
||||
{
|
||||
inst->effectChannelModel()->setValue(b);
|
||||
}
|
||||
else if( val == b )
|
||||
{
|
||||
inst->effectChannelModel()->setValue(a);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0; i<m_fxChannels.size(); ++i)
|
||||
{
|
||||
// for every send/receive, adjust for the channel index change
|
||||
for(int j=0; j<m_fxChannels[i]->m_sends.size(); ++j)
|
||||
{
|
||||
if( m_fxChannels[i]->m_sends[j] == a )
|
||||
{
|
||||
m_fxChannels[i]->m_sends[j] = b;
|
||||
}
|
||||
else if( m_fxChannels[i]->m_sends[j] == b )
|
||||
{
|
||||
m_fxChannels[i]->m_sends[j] = a;
|
||||
}
|
||||
}
|
||||
for(int j=0; j<m_fxChannels[i]->m_receives.size(); ++j)
|
||||
{
|
||||
if( m_fxChannels[i]->m_receives[j] == a )
|
||||
{
|
||||
m_fxChannels[i]->m_receives[j] = b;
|
||||
}
|
||||
else if( m_fxChannels[i]->m_receives[j] == b )
|
||||
{
|
||||
m_fxChannels[i]->m_receives[j] = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// actually do the swap
|
||||
FxChannel * tmpChannel = m_fxChannels[a];
|
||||
m_fxChannels[a] = m_fxChannels[b];
|
||||
m_fxChannels[b] = tmpChannel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::moveChannelRight(int index)
|
||||
{
|
||||
moveChannelLeft(index+1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel,
|
||||
float amount)
|
||||
{
|
||||
// find the existing connection
|
||||
FxChannel * from = m_fxChannels[fromChannel];
|
||||
for(int i=0; i<from->m_sends.size(); ++i){
|
||||
if( from->m_sends[i] == toChannel )
|
||||
{
|
||||
// simply adjust the amount
|
||||
from->m_sendAmount[i]->setValue(amount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// connection does not exist. create a new one
|
||||
|
||||
// add to from's sends
|
||||
from->m_sends.push_back(toChannel);
|
||||
from->m_sendAmount.push_back(new FloatModel(amount, 0, 1, 0.001, NULL,
|
||||
tr("Amount to send")));
|
||||
|
||||
// add to to's receives
|
||||
m_fxChannels[toChannel]->m_receives.push_back(fromChannel);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// delete the connection made by createChannelSend
|
||||
void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel)
|
||||
{
|
||||
// delete the send
|
||||
FxChannel * from = m_fxChannels[fromChannel];
|
||||
FxChannel * to = m_fxChannels[toChannel];
|
||||
|
||||
// find and delete the send entry
|
||||
for(int i=0; i<from->m_sends.size(); ++i) {
|
||||
if( from->m_sends[i] == toChannel )
|
||||
{
|
||||
// delete this index
|
||||
delete from->m_sendAmount[i];
|
||||
from->m_sendAmount.remove(i);
|
||||
from->m_sends.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// find and delete the receive entry
|
||||
for(int i=0; i<to->m_receives.size(); ++i)
|
||||
{
|
||||
if( to->m_receives[i] == fromChannel )
|
||||
{
|
||||
// delete this index
|
||||
to->m_receives.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FxMixer::isInfiniteLoop(fx_ch_t sendFrom, fx_ch_t sendTo) {
|
||||
// can't send master to anything
|
||||
if( sendFrom == 0 ) return true;
|
||||
|
||||
// can't send channel to itself
|
||||
if( sendFrom == sendTo ) return true;
|
||||
|
||||
// follow sendTo's outputs recursively looking for something that sends
|
||||
// to sendFrom
|
||||
for(int i=0; i<m_fxChannels[sendTo]->m_sends.size(); ++i)
|
||||
{
|
||||
if( isInfiniteLoop( sendFrom, m_fxChannels[sendTo]->m_sends[i] ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// how much does fromChannel send its output to the input of toChannel?
|
||||
FloatModel * FxMixer::channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel)
|
||||
{
|
||||
FxChannel * from = m_fxChannels[fromChannel];
|
||||
for(int i=0; i<from->m_sends.size(); ++i){
|
||||
if( from->m_sends[i] == toChannel )
|
||||
return from->m_sendAmount[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch )
|
||||
{
|
||||
// SMF: it seems like here the track-channels are mixed in... but from where
|
||||
// is this called and when and why...?!?
|
||||
//
|
||||
// OK, found it (git grep is your friend...): This is the next part,
|
||||
// where there is a mix between push and pull model inside the core, as
|
||||
// the audio-tracks *push* their data into the fx-channels hopefully just
|
||||
// before the Mixer-Channels are processed... Sorry to say this: but this
|
||||
// took me senseless hours to find out and is silly, too...
|
||||
|
||||
if( m_fxChannels[_ch]->m_muteModel.value() == false )
|
||||
{
|
||||
m_fxChannels[_ch]->m_lock.lock();
|
||||
MixHelpers::add( m_fxChannels[_ch]->m_buffer, _buf, engine::mixer()->framesPerPeriod() );
|
||||
m_fxChannels[_ch]->m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,47 +443,58 @@ void FxMixer::prepareMasterMix()
|
||||
|
||||
|
||||
|
||||
void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf )
|
||||
{
|
||||
FxChannel * thisCh = m_fxChannels[_ch];
|
||||
|
||||
// if we're muted or this channel is seen already, discount it
|
||||
if( thisCh->m_muteModel.value() || thisCh->m_queued )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( const int senderIndex, thisCh->m_receives )
|
||||
{
|
||||
addChannelLeaf( senderIndex, _buf );
|
||||
}
|
||||
|
||||
// add this channel to job list
|
||||
thisCh->m_queued = true;
|
||||
MixerWorkerThread::addJob( thisCh );
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::masterMix( sampleFrame * _buf )
|
||||
{
|
||||
const int fpp = engine::mixer()->framesPerPeriod();
|
||||
memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp );
|
||||
|
||||
for( int i = 1; i < NumFxChannels+1; ++i )
|
||||
// recursively loop through channel dependency chain
|
||||
// and add all channels to job list that have no dependencies
|
||||
// when the channel completes it will check its parent to see if it needs
|
||||
// to be processed.
|
||||
MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic );
|
||||
addChannelLeaf( 0, _buf );
|
||||
while( m_fxChannels[0]->state() != ThreadableJob::Done )
|
||||
{
|
||||
if( m_fxChannels[i]->m_used )
|
||||
{
|
||||
sampleFrame * ch_buf = m_fxChannels[i]->m_buffer;
|
||||
const float v = m_fxChannels[i]->m_volumeModel.value();
|
||||
for( f_cnt_t f = 0; f < fpp; ++f )
|
||||
{
|
||||
_buf[f][0] += ch_buf[f][0] * v;
|
||||
_buf[f][1] += ch_buf[f][1] * v;
|
||||
}
|
||||
engine::mixer()->clearAudioBuffer( ch_buf,
|
||||
engine::mixer()->framesPerPeriod() );
|
||||
m_fxChannels[i]->m_used = false;
|
||||
}
|
||||
}
|
||||
|
||||
processChannel( 0, _buf );
|
||||
|
||||
if( m_fxChannels[0]->m_muteModel.value() )
|
||||
{
|
||||
engine::mixer()->clearAudioBuffer( _buf,
|
||||
engine::mixer()->framesPerPeriod() );
|
||||
return;
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
}
|
||||
//m_fxChannels[0]->doProcessing( NULL );
|
||||
|
||||
const float v = m_fxChannels[0]->m_volumeModel.value();
|
||||
for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); ++f )
|
||||
{
|
||||
_buf[f][0] *= v;
|
||||
_buf[f][1] *= v;
|
||||
}
|
||||
MixHelpers::addMultiplied( _buf, m_fxChannels[0]->m_buffer, v, fpp );
|
||||
|
||||
m_fxChannels[0]->m_peakLeft *= engine::mixer()->masterGain();
|
||||
m_fxChannels[0]->m_peakRight *= engine::mixer()->masterGain();
|
||||
|
||||
// clear all channel buffers and
|
||||
// reset channel process state
|
||||
for( int i = 0; i < numChannels(); ++i)
|
||||
{
|
||||
engine::mixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer, engine::mixer()->framesPerPeriod() );
|
||||
m_fxChannels[i]->reset();
|
||||
m_fxChannels[i]->m_queued = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,60 +502,139 @@ void FxMixer::masterMix( sampleFrame * _buf )
|
||||
|
||||
void FxMixer::clear()
|
||||
{
|
||||
for( int i = 0; i <= NumFxChannels; ++i )
|
||||
while( m_fxChannels.size() > 1 )
|
||||
{
|
||||
m_fxChannels[i]->m_fxChain.clear();
|
||||
m_fxChannels[i]->m_volumeModel.setValue( 1.0f );
|
||||
m_fxChannels[i]->m_muteModel.setValue( false );
|
||||
m_fxChannels[i]->m_name = ( i == 0 ) ?
|
||||
tr( "Master" ) : tr( "FX %1" ).arg( i );
|
||||
m_fxChannels[i]->m_volumeModel.setDisplayName(
|
||||
m_fxChannels[i]->m_name );
|
||||
|
||||
deleteChannel(1);
|
||||
}
|
||||
|
||||
clearChannel(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::clearChannel(fx_ch_t index)
|
||||
{
|
||||
FxChannel * ch = m_fxChannels[index];
|
||||
ch->m_fxChain.clear();
|
||||
ch->m_volumeModel.setValue( 1.0f );
|
||||
ch->m_muteModel.setValue( false );
|
||||
ch->m_name = ( index == 0 ) ? tr( "Master" ) : tr( "FX %1" ).arg( index );
|
||||
ch->m_volumeModel.setDisplayName(ch->m_name );
|
||||
|
||||
// send only to master
|
||||
if( index > 0)
|
||||
{
|
||||
// delete existing sends
|
||||
for( int i=0; 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();
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Mixer.h"
|
||||
#include "FxMixer.h"
|
||||
#include "MixHelpers.h"
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "play_handle.h"
|
||||
#include "song.h"
|
||||
#include "templates.h"
|
||||
@@ -58,8 +59,6 @@
|
||||
#include "MidiDummy.h"
|
||||
|
||||
|
||||
static QVector<fx_ch_t> __fx_channel_jobs( NumFxChannels );
|
||||
|
||||
|
||||
|
||||
static void aligned_free( void * _buf )
|
||||
@@ -91,210 +90,6 @@ static void * aligned_malloc( int _bytes )
|
||||
|
||||
|
||||
|
||||
class MixerWorkerThread : public QThread
|
||||
{
|
||||
public:
|
||||
enum JobTypes
|
||||
{
|
||||
InvalidJob,
|
||||
PlayHandle,
|
||||
AudioPortEffects,
|
||||
EffectChannel,
|
||||
NumJobTypes
|
||||
} ;
|
||||
|
||||
struct JobQueueItem
|
||||
{
|
||||
JobQueueItem() :
|
||||
type( InvalidJob ),
|
||||
job( NULL ),
|
||||
param( 0 ),
|
||||
done( false )
|
||||
{
|
||||
}
|
||||
JobQueueItem( JobTypes _type, void * _job, int _param = 0 ) :
|
||||
type( _type ),
|
||||
job( _job ),
|
||||
param( _param ),
|
||||
done( false )
|
||||
{
|
||||
}
|
||||
|
||||
JobTypes type;
|
||||
void * job;
|
||||
int param;
|
||||
|
||||
AtomicInt done;
|
||||
} ;
|
||||
|
||||
|
||||
struct JobQueue
|
||||
{
|
||||
#define JOB_QUEUE_SIZE 1024
|
||||
JobQueue() :
|
||||
queueSize( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
JobQueueItem items[JOB_QUEUE_SIZE];
|
||||
int queueSize;
|
||||
AtomicInt itemsDone;
|
||||
} ;
|
||||
|
||||
static JobQueue s_jobQueue;
|
||||
|
||||
MixerWorkerThread( int _worker_num, Mixer* mixer ) :
|
||||
QThread( mixer ),
|
||||
m_workingBuf( (sampleFrame *) aligned_malloc(
|
||||
mixer->framesPerPeriod() *
|
||||
sizeof( sampleFrame ) ) ),
|
||||
m_workerNum( _worker_num ),
|
||||
m_quit( false ),
|
||||
m_mixer( mixer ),
|
||||
m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond )
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~MixerWorkerThread()
|
||||
{
|
||||
aligned_free( m_workingBuf );
|
||||
}
|
||||
|
||||
virtual void quit()
|
||||
{
|
||||
m_quit = true;
|
||||
}
|
||||
|
||||
void processJobQueue();
|
||||
|
||||
|
||||
private:
|
||||
virtual void run()
|
||||
{
|
||||
#if 0
|
||||
#ifdef LMMS_BUILD_LINUX
|
||||
#ifdef LMMS_HAVE_SCHED_H
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO( &mask );
|
||||
CPU_SET( m_workerNum, &mask );
|
||||
sched_setaffinity( 0, sizeof( mask ), &mask );
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
QMutex m;
|
||||
while( m_quit == false )
|
||||
{
|
||||
m.lock();
|
||||
m_queueReadyWaitCond->wait( &m );
|
||||
processJobQueue();
|
||||
m.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
sampleFrame * m_workingBuf;
|
||||
int m_workerNum;
|
||||
volatile bool m_quit;
|
||||
Mixer* m_mixer;
|
||||
QWaitCondition * m_queueReadyWaitCond;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue;
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::processJobQueue()
|
||||
{
|
||||
for( int i = 0; i < s_jobQueue.queueSize; ++i )
|
||||
{
|
||||
JobQueueItem * it = &s_jobQueue.items[i];
|
||||
if( it->done.fetchAndStoreOrdered( 1 ) == 0 )
|
||||
{
|
||||
switch( it->type )
|
||||
{
|
||||
case PlayHandle:
|
||||
( (playHandle *) it->job )->
|
||||
play( m_workingBuf );
|
||||
break;
|
||||
case AudioPortEffects:
|
||||
{
|
||||
AudioPort * a = (AudioPort *) it->job;
|
||||
const bool me = a->processEffects();
|
||||
if( me || a->m_bufferUsage != AudioPort::NoUsage )
|
||||
{
|
||||
engine::fxMixer()->mixToChannel( a->firstBuffer(),
|
||||
a->nextFxChannel() );
|
||||
a->nextPeriod();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EffectChannel:
|
||||
engine::fxMixer()->processChannel( (fx_ch_t) it->param );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
s_jobQueue.itemsDone.fetchAndAddOrdered( 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \
|
||||
MixerWorkerThread::s_jobQueue.queueSize = 0; \
|
||||
MixerWorkerThread::s_jobQueue.itemsDone = 0; \
|
||||
for( _vec_type::Iterator it = _vec.begin(); \
|
||||
it != _vec.end(); ++it ) \
|
||||
{ \
|
||||
if( _condition ) \
|
||||
{
|
||||
|
||||
#define FILL_JOB_QUEUE_END() \
|
||||
++MixerWorkerThread::s_jobQueue.queueSize; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define FILL_JOB_QUEUE(_vec_type,_vec,_job_type,_condition) \
|
||||
FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \
|
||||
MixerWorkerThread::s_jobQueue.items \
|
||||
[MixerWorkerThread::s_jobQueue.queueSize] = \
|
||||
MixerWorkerThread::JobQueueItem( _job_type, \
|
||||
(void *) *it ); \
|
||||
FILL_JOB_QUEUE_END()
|
||||
|
||||
#define FILL_JOB_QUEUE_PARAM(_vec_type,_vec,_job_type,_condition) \
|
||||
FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \
|
||||
MixerWorkerThread::s_jobQueue.items \
|
||||
[MixerWorkerThread::s_jobQueue.queueSize] = \
|
||||
MixerWorkerThread::JobQueueItem( _job_type, \
|
||||
NULL, *it ); \
|
||||
FILL_JOB_QUEUE_END()
|
||||
|
||||
#define START_JOBS() \
|
||||
m_queueReadyWaitCond.wakeAll();
|
||||
|
||||
// define a pause instruction for spinlock-loop - merely useful on
|
||||
// HyperThreading systems with just one physical core (e.g. Intel Atom)
|
||||
#ifdef LMMS_HOST_X86
|
||||
#define SPINLOCK_PAUSE() asm( "pause" )
|
||||
#else
|
||||
#ifdef LMMS_HOST_X86_64
|
||||
#define SPINLOCK_PAUSE() asm( "pause" )
|
||||
#else
|
||||
#define SPINLOCK_PAUSE()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define WAIT_FOR_JOBS() \
|
||||
m_workers[m_numWorkers]->processJobQueue(); \
|
||||
while( MixerWorkerThread::s_jobQueue.itemsDone < \
|
||||
MixerWorkerThread::s_jobQueue.queueSize ) \
|
||||
{ \
|
||||
SPINLOCK_PAUSE(); \
|
||||
} \
|
||||
|
||||
|
||||
|
||||
|
||||
Mixer::Mixer() :
|
||||
m_framesPerPeriod( DEFAULT_BUFFER_SIZE ),
|
||||
@@ -321,11 +116,6 @@ Mixer::Mixer() :
|
||||
clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] );
|
||||
}
|
||||
|
||||
for( int i = 1; i < NumFxChannels+1; ++i )
|
||||
{
|
||||
__fx_channel_jobs[i-1] = (fx_ch_t) i;
|
||||
}
|
||||
|
||||
// just rendering?
|
||||
if( !engine::hasGUI() )
|
||||
{
|
||||
@@ -372,7 +162,7 @@ Mixer::Mixer() :
|
||||
|
||||
for( int i = 0; i < m_numWorkers+1; ++i )
|
||||
{
|
||||
MixerWorkerThread * wt = new MixerWorkerThread( i, this );
|
||||
MixerWorkerThread * wt = new MixerWorkerThread( this );
|
||||
if( i < m_numWorkers )
|
||||
{
|
||||
wt->start( QThread::TimeCriticalPriority );
|
||||
@@ -390,14 +180,13 @@ Mixer::Mixer() :
|
||||
|
||||
Mixer::~Mixer()
|
||||
{
|
||||
// distribute an empty job-queue so that worker-threads
|
||||
// get out of their processing-loop
|
||||
MixerWorkerThread::s_jobQueue.queueSize = 0;
|
||||
for( int w = 0; w < m_numWorkers; ++w )
|
||||
{
|
||||
m_workers[w]->quit();
|
||||
}
|
||||
START_JOBS();
|
||||
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
|
||||
for( int w = 0; w < m_numWorkers; ++w )
|
||||
{
|
||||
m_workers[w]->wait( 500 );
|
||||
@@ -618,11 +407,8 @@ const surroundSampleFrame * Mixer::renderNextBuffer()
|
||||
|
||||
|
||||
// STAGE 1: run and render all play handles
|
||||
FILL_JOB_QUEUE(PlayHandleList,m_playHandles,
|
||||
MixerWorkerThread::PlayHandle,
|
||||
!( *it )->done());
|
||||
START_JOBS();
|
||||
WAIT_FOR_JOBS();
|
||||
MixerWorkerThread::fillJobQueue<PlayHandleList>( m_playHandles );
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
|
||||
// removed all play handles which are done
|
||||
for( PlayHandleList::Iterator it = m_playHandles.begin();
|
||||
@@ -647,20 +433,11 @@ const surroundSampleFrame * Mixer::renderNextBuffer()
|
||||
|
||||
|
||||
// STAGE 2: process effects of all instrument- and sampletracks
|
||||
FILL_JOB_QUEUE(QVector<AudioPort*>,m_audioPorts,
|
||||
MixerWorkerThread::AudioPortEffects,1);
|
||||
START_JOBS();
|
||||
WAIT_FOR_JOBS();
|
||||
MixerWorkerThread::fillJobQueue<QVector<AudioPort *> >( m_audioPorts );
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
|
||||
|
||||
// STAGE 3: process effects in FX mixer
|
||||
FILL_JOB_QUEUE_PARAM(QVector<fx_ch_t>,__fx_channel_jobs,
|
||||
MixerWorkerThread::EffectChannel,1);
|
||||
START_JOBS();
|
||||
WAIT_FOR_JOBS();
|
||||
|
||||
|
||||
// STAGE 4: do master mix in FX mixer
|
||||
// STAGE 3: do master mix in FX mixer
|
||||
engine::fxMixer()->masterMix( m_writeBuf );
|
||||
|
||||
unlock();
|
||||
|
||||
165
src/core/MixerWorkerThread.cpp
Normal file
165
src/core/MixerWorkerThread.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* MixerWorkerThread.cpp - implementation of MixerWorkerThread
|
||||
*
|
||||
* Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue;
|
||||
QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL;
|
||||
QList<MixerWorkerThread *> MixerWorkerThread::workerThreads;
|
||||
|
||||
|
||||
|
||||
// implementation of internal JobQueue
|
||||
void MixerWorkerThread::JobQueue::reset( OperationMode _opMode )
|
||||
{
|
||||
m_queueSize = 0;
|
||||
m_itemsDone = 0;
|
||||
m_opMode = _opMode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
{
|
||||
if( _job->requiresProcessing() )
|
||||
{
|
||||
// update job state
|
||||
_job->queue();
|
||||
// actually queue the job via atomic operations
|
||||
m_items[m_queueSize.fetchAndAddOrdered(1)] = _job;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::run( sampleFrame * _buffer )
|
||||
{
|
||||
bool processedJob = true;
|
||||
while( processedJob && (int) m_itemsDone < (int) m_queueSize )
|
||||
{
|
||||
processedJob = false;
|
||||
for( int i = 0; i < m_queueSize; ++i )
|
||||
{
|
||||
ThreadableJob * job = m_items[i].fetchAndStoreOrdered( NULL );
|
||||
if( job )
|
||||
{
|
||||
job->process( _buffer );
|
||||
processedJob = true;
|
||||
m_itemsDone.fetchAndAddOrdered( 1 );
|
||||
}
|
||||
}
|
||||
// always exit loop if we're not in dynamic mode
|
||||
processedJob = processedJob && ( m_opMode == Dynamic );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::wait()
|
||||
{
|
||||
while( (int) m_itemsDone < (int) m_queueSize )
|
||||
{
|
||||
#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64)
|
||||
asm( "pause" );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// implementation of worker threads
|
||||
|
||||
MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) :
|
||||
QThread( mixer ),
|
||||
m_workingBuf( new sampleFrame[mixer->framesPerPeriod()] ),
|
||||
m_quit( false )
|
||||
{
|
||||
// initialize global static data
|
||||
if( queueReadyWaitCond == NULL )
|
||||
{
|
||||
queueReadyWaitCond = new QWaitCondition;
|
||||
}
|
||||
|
||||
// keep track of all instantiated worker threads - this is used for
|
||||
// processing the last worker thread "inline", see comments in
|
||||
// MixerWorkerThread::startAndWaitForJobs() for details
|
||||
workerThreads << this;
|
||||
|
||||
resetJobQueue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MixerWorkerThread::~MixerWorkerThread()
|
||||
{
|
||||
delete[] m_workingBuf;
|
||||
|
||||
workerThreads.removeAll( this );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::quit()
|
||||
{
|
||||
m_quit = true;
|
||||
resetJobQueue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::startAndWaitForJobs()
|
||||
{
|
||||
queueReadyWaitCond->wakeAll();
|
||||
// The last worker-thread is never started. Instead it's processed "inline"
|
||||
// i.e. within the global Mixer thread. This way we can reduce latencies
|
||||
// that otherwise would be caused by synchronizing with another thread.
|
||||
globalJobQueue.run( workerThreads.last()->m_workingBuf );
|
||||
globalJobQueue.wait();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::run()
|
||||
{
|
||||
QMutex m;
|
||||
while( m_quit == false )
|
||||
{
|
||||
m.lock();
|
||||
queueReadyWaitCond->wait( &m );
|
||||
globalJobQueue.run( m_workingBuf );
|
||||
m.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* AudioPort.cpp - base-class for objects providing sound at a port
|
||||
*
|
||||
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
||||
*
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "AudioPort.h"
|
||||
#include "AudioDevice.h"
|
||||
#include "EffectChain.h"
|
||||
#include "FxMixer.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
@@ -121,3 +122,13 @@ bool AudioPort::processEffects()
|
||||
}
|
||||
|
||||
|
||||
void AudioPort::doProcessing( sampleFrame * )
|
||||
{
|
||||
const bool me = processEffects();
|
||||
if( me || m_bufferUsage != NoUsage )
|
||||
{
|
||||
engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() );
|
||||
nextPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDebug>
|
||||
|
||||
#include <QtGui/QButtonGroup>
|
||||
#include <QtGui/QInputDialog>
|
||||
#include <QtGui/QLayout>
|
||||
@@ -31,80 +34,19 @@
|
||||
#include <QtGui/QPushButton>
|
||||
#include <QtGui/QToolButton>
|
||||
#include <QtGui/QStackedLayout>
|
||||
#include <QtGui/QScrollArea>
|
||||
#include <QtGui/QStyle>
|
||||
#include <QtGui/QKeyEvent>
|
||||
|
||||
#include "FxMixerView.h"
|
||||
#include "fader.h"
|
||||
#include "EffectRackView.h"
|
||||
#include "knob.h"
|
||||
#include "engine.h"
|
||||
#include "embed.h"
|
||||
#include "MainWindow.h"
|
||||
#include "LcdWidget.h"
|
||||
#include "gui_templates.h"
|
||||
#include "tooltip.h"
|
||||
#include "pixmap_button.h"
|
||||
|
||||
|
||||
|
||||
class FxLine : public QWidget
|
||||
{
|
||||
public:
|
||||
FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name ) :
|
||||
QWidget( _parent ),
|
||||
m_mv( _mv ),
|
||||
m_name( _name )
|
||||
{
|
||||
setFixedSize( 32, 232 );
|
||||
setAttribute( Qt::WA_OpaquePaintEvent, true );
|
||||
setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) );
|
||||
}
|
||||
|
||||
virtual void paintEvent( QPaintEvent * )
|
||||
{
|
||||
QPainter p( this );
|
||||
p.fillRect( rect(), QColor( 72, 76, 88 ) );
|
||||
p.setPen( QColor( 40, 42, 48 ) );
|
||||
p.drawRect( 0, 0, width()-2, height()-2 );
|
||||
p.setPen( QColor( 108, 114, 132 ) );
|
||||
p.drawRect( 1, 1, width()-2, height()-2 );
|
||||
p.setPen( QColor( 20, 24, 32 ) );
|
||||
p.drawRect( 0, 0, width()-1, height()-1 );
|
||||
|
||||
p.rotate( -90 );
|
||||
p.setPen( m_mv->currentFxLine() == this ?
|
||||
QColor( 0, 255, 0 ) : Qt::white );
|
||||
p.setFont( pointSizeF( font(), 7.5f ) );
|
||||
p.drawText( -90, 20, m_name );
|
||||
}
|
||||
|
||||
virtual void mousePressEvent( QMouseEvent * )
|
||||
{
|
||||
m_mv->setCurrentFxLine( this );
|
||||
}
|
||||
|
||||
virtual void mouseDoubleClickEvent( QMouseEvent * )
|
||||
{
|
||||
bool ok;
|
||||
QString new_name = QInputDialog::getText( this,
|
||||
FxMixerView::tr( "Rename FX channel" ),
|
||||
FxMixerView::tr( "Enter the new name for this "
|
||||
"FX channel" ),
|
||||
QLineEdit::Normal, m_name, &ok );
|
||||
if( ok && !new_name.isEmpty() )
|
||||
{
|
||||
m_name = new_name;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
FxMixerView * m_mv;
|
||||
QString & m_name;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
#include "InstrumentTrack.h"
|
||||
#include "song.h"
|
||||
#include "bb_track_container.h"
|
||||
|
||||
FxMixerView::FxMixerView() :
|
||||
QWidget(),
|
||||
@@ -114,117 +56,81 @@ FxMixerView::FxMixerView() :
|
||||
FxMixer * m = engine::fxMixer();
|
||||
m->setHook( this );
|
||||
|
||||
QPalette pal = palette();
|
||||
pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) );
|
||||
setPalette( pal );
|
||||
//QPalette pal = palette();
|
||||
//pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) );
|
||||
//setPalette( pal );
|
||||
setAutoFillBackground( true );
|
||||
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum );
|
||||
|
||||
setWindowTitle( tr( "FX-Mixer" ) );
|
||||
setWindowIcon( embed::getIconPixmap( "fx_mixer" ) );
|
||||
|
||||
m_fxLineBanks = new QStackedLayout;
|
||||
m_fxLineBanks->setSpacing( 0 );
|
||||
m_fxLineBanks->setMargin( 1 );
|
||||
|
||||
m_fxRacksLayout = new QStackedLayout;
|
||||
m_fxRacksLayout->setSpacing( 0 );
|
||||
m_fxRacksLayout->setMargin( 0 );
|
||||
|
||||
// main-layout
|
||||
QHBoxLayout * ml = new QHBoxLayout;
|
||||
ml->setMargin( 0 );
|
||||
ml->setSpacing( 0 );
|
||||
ml->addSpacing( 6 );
|
||||
|
||||
// Channel area
|
||||
m_channelAreaWidget = new QWidget;
|
||||
chLayout = new QHBoxLayout(m_channelAreaWidget);
|
||||
chLayout->setSizeConstraint(QLayout::SetMinimumSize);
|
||||
chLayout->setSpacing( 0 );
|
||||
chLayout->setMargin( 0 );
|
||||
m_channelAreaWidget->setLayout(chLayout);
|
||||
|
||||
QHBoxLayout * banks[NumFxChannels/16];
|
||||
for( int i = 0; i < NumFxChannels/16; ++i )
|
||||
// add master channel
|
||||
m_fxChannelViews.resize(m->numChannels());
|
||||
m_fxChannelViews[0] = new FxChannelView(this, this, 0);
|
||||
|
||||
FxChannelView * masterView = m_fxChannelViews[0];
|
||||
ml->addWidget( masterView->m_fxLine, 0, Qt::AlignTop );
|
||||
|
||||
QSize fxLineSize = masterView->m_fxLine->size();
|
||||
|
||||
// add mixer channels
|
||||
for( int i = 1; i < m_fxChannelViews.size(); ++i )
|
||||
{
|
||||
QWidget * w = new QWidget( this );
|
||||
banks[i] = new QHBoxLayout( w );
|
||||
banks[i]->setMargin( 5 );
|
||||
banks[i]->setSpacing( 1 );
|
||||
m_fxLineBanks->addWidget( w );
|
||||
m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i);
|
||||
chLayout->addWidget(m_fxChannelViews[i]->m_fxLine);
|
||||
}
|
||||
|
||||
for( int i = 0; i < NumFxChannels+1; ++i )
|
||||
// add the scrolling section to the main layout
|
||||
// class solely for scroll area to pass key presses down
|
||||
class ChannelArea : public QScrollArea
|
||||
{
|
||||
FxChannelView * cv = &m_fxChannelViews[i];
|
||||
if( i == 0 )
|
||||
{
|
||||
cv->m_fxLine = new FxLine( NULL, this,
|
||||
m->m_fxChannels[i]->m_name );
|
||||
ml->addWidget( cv->m_fxLine );
|
||||
ml->addSpacing( 10 );
|
||||
}
|
||||
else
|
||||
{
|
||||
const int bank = (i-1) / 16;
|
||||
cv->m_fxLine = new FxLine( NULL, this,
|
||||
m->m_fxChannels[i]->m_name );
|
||||
banks[bank]->addWidget( cv->m_fxLine );
|
||||
}
|
||||
LcdWidget* l = new LcdWidget( 2, cv->m_fxLine );
|
||||
l->setValue( i );
|
||||
l->move( 3, 4 );
|
||||
l->setMarginWidth( 1 );
|
||||
|
||||
|
||||
cv->m_fader = new fader( &m->m_fxChannels[i]->m_volumeModel,
|
||||
tr( "FX Fader %1" ).arg( i ),
|
||||
cv->m_fxLine );
|
||||
cv->m_fader->move( 15-cv->m_fader->width()/2,
|
||||
cv->m_fxLine->height()-
|
||||
cv->m_fader->height()-5 );
|
||||
|
||||
cv->m_muteBtn = new pixmapButton( cv->m_fxLine, tr( "Mute" ) );
|
||||
cv->m_muteBtn->setModel( &m->m_fxChannels[i]->m_muteModel );
|
||||
cv->m_muteBtn->setActiveGraphic(
|
||||
embed::getIconPixmap( "led_off" ) );
|
||||
cv->m_muteBtn->setInactiveGraphic(
|
||||
embed::getIconPixmap( "led_green" ) );
|
||||
cv->m_muteBtn->setCheckable( true );
|
||||
cv->m_muteBtn->move( 9, cv->m_fader->y()-16);
|
||||
toolTip::add( cv->m_muteBtn, tr( "Mute this FX channel" ) );
|
||||
|
||||
cv->m_rackView = new EffectRackView(
|
||||
&m->m_fxChannels[i]->m_fxChain, this );
|
||||
m_fxRacksLayout->addWidget( cv->m_rackView );
|
||||
if( i == 0 )
|
||||
{
|
||||
QVBoxLayout * l = new QVBoxLayout;
|
||||
l->addSpacing( 10 );
|
||||
QButtonGroup * g = new QButtonGroup( this );
|
||||
m_bankButtons = g;
|
||||
g->setExclusive( true );
|
||||
for( int j = 0; j < 4; ++j )
|
||||
public:
|
||||
ChannelArea(QWidget * parent, FxMixerView * mv) :
|
||||
QScrollArea(parent), m_mv(mv) {}
|
||||
~ChannelArea() {}
|
||||
virtual void keyPressEvent(QKeyEvent * e)
|
||||
{
|
||||
QToolButton * btn = new QToolButton;
|
||||
btn->setText( QString( 'A'+j ) );
|
||||
btn->setCheckable( true );
|
||||
btn->setSizePolicy( QSizePolicy::Preferred,
|
||||
QSizePolicy::Expanding );
|
||||
l->addWidget( btn );
|
||||
g->addButton( btn, j );
|
||||
btn->setChecked( j == 0);
|
||||
m_mv->keyPressEvent(e);
|
||||
}
|
||||
l->addSpacing( 10 );
|
||||
ml->addLayout( l );
|
||||
connect( g, SIGNAL( buttonClicked( int ) ),
|
||||
m_fxLineBanks, SLOT( setCurrentIndex( int ) ) );
|
||||
}
|
||||
}
|
||||
private:
|
||||
FxMixerView * m_mv;
|
||||
};
|
||||
channelArea = new ChannelArea(this, this);
|
||||
channelArea->setWidget(m_channelAreaWidget);
|
||||
channelArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
||||
channelArea->setFrameStyle( QFrame::NoFrame );
|
||||
channelArea->setMinimumWidth( fxLineSize.width() * 6 );
|
||||
channelArea->setFixedHeight( fxLineSize.height() +
|
||||
style()->pixelMetric( QStyle::PM_ScrollBarExtent ) );
|
||||
ml->addWidget(channelArea);
|
||||
|
||||
ml->addLayout( m_fxLineBanks );
|
||||
ml->addLayout( m_fxRacksLayout );
|
||||
// show the add new effect channel button
|
||||
QPushButton * newChannelBtn = new QPushButton("new", this );
|
||||
newChannelBtn->setFont(QFont("sans-serif", 10, 1, false));
|
||||
newChannelBtn->setFixedSize(fxLineSize);
|
||||
connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel()));
|
||||
ml->addWidget( newChannelBtn, 0, Qt::AlignTop );
|
||||
|
||||
|
||||
// Create EffectRack and set initial index to master channel
|
||||
m_rackView = new EffectRackView( &m->m_fxChannels[0]->m_fxChain, this );
|
||||
ml->addWidget( m_rackView, 0, Qt::AlignTop );
|
||||
setCurrentFxLine( m_fxChannelViews[0]->m_fxLine );
|
||||
|
||||
setLayout( ml );
|
||||
updateGeometry();
|
||||
|
||||
m_fxLineBanks->setCurrentIndex( 0 );
|
||||
setCurrentFxLine( m_fxChannelViews[0].m_fxLine );
|
||||
|
||||
// timer for updating faders
|
||||
connect( engine::mainWindow(), SIGNAL( periodicUpdate() ),
|
||||
this, SLOT( updateFaders() ) );
|
||||
@@ -234,10 +140,10 @@ FxMixerView::FxMixerView() :
|
||||
QMdiSubWindow * subWin =
|
||||
engine::mainWindow()->workspace()->addSubWindow( this );
|
||||
Qt::WindowFlags flags = subWin->windowFlags();
|
||||
flags |= Qt::MSWindowsFixedSizeDialogHint;
|
||||
flags &= ~Qt::WindowMaximizeButtonHint;
|
||||
subWin->setWindowFlags( flags );
|
||||
//subWin->layout()->setSizeConstraint(QLayout::SetFixedSize);
|
||||
layout()->setSizeConstraint( QLayout::SetMinAndMaxSize );
|
||||
subWin->layout()->setSizeConstraint( QLayout::SetMinAndMaxSize );
|
||||
|
||||
parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false );
|
||||
parentWidget()->move( 5, 310 );
|
||||
@@ -246,15 +152,75 @@ FxMixerView::FxMixerView() :
|
||||
setModel( m );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
FxMixerView::~FxMixerView()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixerView::addNewChannel()
|
||||
{
|
||||
// add new fx mixer channel and redraw the form.
|
||||
FxMixer * mix = engine::fxMixer();
|
||||
|
||||
int newChannelIndex = mix->createChannel();
|
||||
m_fxChannelViews.push_back(new FxChannelView(m_channelAreaWidget, this,
|
||||
newChannelIndex));
|
||||
chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine);
|
||||
|
||||
updateFxLine(newChannelIndex);
|
||||
|
||||
updateMaxChannelSelector();
|
||||
}
|
||||
|
||||
|
||||
void FxMixerView::refreshDisplay()
|
||||
{
|
||||
// delete all views and re-add them
|
||||
for( int i = 1; 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 )
|
||||
{
|
||||
@@ -270,18 +236,189 @@ void FxMixerView::loadSettings( const QDomElement & _this )
|
||||
}
|
||||
|
||||
|
||||
FxMixerView::FxChannelView::FxChannelView(QWidget * _parent, FxMixerView * _mv,
|
||||
int _chIndex )
|
||||
{
|
||||
m_fxLine = new FxLine(_parent, _mv, _chIndex);
|
||||
|
||||
FxMixer * m = engine::fxMixer();
|
||||
m_fader = new fader( &m->effectChannel(_chIndex)->m_volumeModel,
|
||||
tr( "FX Fader %1" ).arg( _chIndex ), m_fxLine );
|
||||
m_fader->move( 15-m_fader->width()/2,
|
||||
m_fxLine->height()-
|
||||
m_fader->height()-5 );
|
||||
|
||||
m_muteBtn = new pixmapButton( m_fxLine, tr( "Mute" ) );
|
||||
m_muteBtn->setModel( &m->effectChannel(_chIndex)->m_muteModel );
|
||||
m_muteBtn->setActiveGraphic(
|
||||
embed::getIconPixmap( "led_off" ) );
|
||||
m_muteBtn->setInactiveGraphic(
|
||||
embed::getIconPixmap( "led_green" ) );
|
||||
m_muteBtn->setCheckable( true );
|
||||
m_muteBtn->move( 9, m_fader->y()-16);
|
||||
toolTip::add( m_muteBtn, tr( "Mute this FX channel" ) );
|
||||
}
|
||||
|
||||
|
||||
void FxMixerView::setCurrentFxLine( FxLine * _line )
|
||||
{
|
||||
// select
|
||||
m_currentFxLine = _line;
|
||||
for( int i = 0; i < NumFxChannels+1; ++i )
|
||||
m_rackView->setModel( &engine::fxMixer()->m_fxChannels[_line->channelIndex()]->m_fxChain );
|
||||
|
||||
// set up send knob
|
||||
for(int i = 0; i < m_fxChannelViews.size(); ++i)
|
||||
{
|
||||
if( m_fxChannelViews[i].m_fxLine == _line )
|
||||
updateFxLine(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FxMixerView::updateFxLine(int index)
|
||||
{
|
||||
FxMixer * mix = engine::fxMixer();
|
||||
|
||||
// does current channel send to this channel?
|
||||
int selIndex = m_currentFxLine->channelIndex();
|
||||
FxLine * thisLine = m_fxChannelViews[index]->m_fxLine;
|
||||
FloatModel * sendModel = mix->channelSendModel(selIndex, index);
|
||||
if( sendModel == NULL )
|
||||
{
|
||||
// does not send, hide send knob
|
||||
thisLine->m_sendKnob->setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// it does send, show knob and connect
|
||||
thisLine->m_sendKnob->setVisible(true);
|
||||
thisLine->m_sendKnob->setModel(sendModel);
|
||||
}
|
||||
|
||||
// disable the send button if it would cause an infinite loop
|
||||
thisLine->m_sendBtn->setVisible(! mix->isInfiniteLoop(selIndex, index));
|
||||
thisLine->m_sendBtn->updateLightStatus();
|
||||
thisLine->update();
|
||||
}
|
||||
|
||||
|
||||
void FxMixerView::deleteChannel(int index)
|
||||
{
|
||||
// can't delete master
|
||||
if( index == 0 ) return;
|
||||
|
||||
// remember selected line
|
||||
int selLine = m_currentFxLine->channelIndex();
|
||||
|
||||
// delete the real channel
|
||||
engine::fxMixer()->deleteChannel(index);
|
||||
|
||||
// delete the view
|
||||
chLayout->removeWidget(m_fxChannelViews[index]->m_fxLine);
|
||||
delete m_fxChannelViews[index]->m_fader;
|
||||
delete m_fxChannelViews[index]->m_muteBtn;
|
||||
delete m_fxChannelViews[index]->m_fxLine;
|
||||
delete m_fxChannelViews[index];
|
||||
m_channelAreaWidget->adjustSize();
|
||||
|
||||
// make sure every channel knows what index it is
|
||||
for(int i=0; i<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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,23 +426,18 @@ void FxMixerView::setCurrentFxLine( FxLine * _line )
|
||||
|
||||
void FxMixerView::setCurrentFxLine( int _line )
|
||||
{
|
||||
if ( _line >= 0 && _line < NumFxChannels+1 )
|
||||
if( _line >= 0 && _line < m_fxChannelViews.size() )
|
||||
{
|
||||
setCurrentFxLine( m_fxChannelViews[_line].m_fxLine );
|
||||
|
||||
m_bankButtons->button( (_line-1) / 16 )->click();
|
||||
setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FxMixerView::clear()
|
||||
{
|
||||
for( int i = 0; i <= NumFxChannels; ++i )
|
||||
{
|
||||
m_fxChannelViews[i].m_rackView->clearViews();
|
||||
}
|
||||
m_rackView->clearViews();
|
||||
refreshDisplay();
|
||||
}
|
||||
|
||||
|
||||
@@ -314,28 +446,28 @@ void FxMixerView::clear()
|
||||
void FxMixerView::updateFaders()
|
||||
{
|
||||
FxMixer * m = engine::fxMixer();
|
||||
for( int i = 0; i < NumFxChannels+1; ++i )
|
||||
for( int i = 0; i < m_fxChannelViews.size(); ++i )
|
||||
{
|
||||
const float opl = m_fxChannelViews[i].m_fader->getPeak_L();
|
||||
const float opr = m_fxChannelViews[i].m_fader->getPeak_R();
|
||||
const float opl = m_fxChannelViews[i]->m_fader->getPeak_L();
|
||||
const float opr = m_fxChannelViews[i]->m_fader->getPeak_R();
|
||||
const float fall_off = 1.2;
|
||||
if( m->m_fxChannels[i]->m_peakLeft > opl )
|
||||
{
|
||||
m_fxChannelViews[i].m_fader->setPeak_L(
|
||||
m_fxChannelViews[i]->m_fader->setPeak_L(
|
||||
m->m_fxChannels[i]->m_peakLeft );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fxChannelViews[i].m_fader->setPeak_L( opl/fall_off );
|
||||
m_fxChannelViews[i]->m_fader->setPeak_L( opl/fall_off );
|
||||
}
|
||||
if( m->m_fxChannels[i]->m_peakRight > opr )
|
||||
{
|
||||
m_fxChannelViews[i].m_fader->setPeak_R(
|
||||
m_fxChannelViews[i]->m_fader->setPeak_R(
|
||||
m->m_fxChannels[i]->m_peakRight );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fxChannelViews[i].m_fader->setPeak_R( opr/fall_off );
|
||||
m_fxChannelViews[i]->m_fader->setPeak_R( opr/fall_off );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
147
src/gui/widgets/FxLine.cpp
Normal file
147
src/gui/widgets/FxLine.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* FxLine.cpp - FX line widget
|
||||
*
|
||||
* Copyright (c) 2009 Andrew Kelley <superjoe30/at/gmail/dot/com>
|
||||
* Copyright (c) 2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FxLine.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtGui/QInputDialog>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QLineEdit>
|
||||
|
||||
#include "FxMixer.h"
|
||||
#include "FxMixerView.h"
|
||||
#include "embed.h"
|
||||
#include "engine.h"
|
||||
#include "SendButtonIndicator.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
FxLine::FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex) :
|
||||
QWidget( _parent ),
|
||||
m_mv( _mv ),
|
||||
m_channelIndex( _channelIndex )
|
||||
{
|
||||
setFixedSize( 32, 287 );
|
||||
setAttribute( Qt::WA_OpaquePaintEvent, true );
|
||||
setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) );
|
||||
|
||||
// mixer sends knob
|
||||
m_sendKnob = new knob(0, this, tr("Channel send amount"));
|
||||
m_sendKnob->move(0, 22);
|
||||
m_sendKnob->setVisible(false);
|
||||
|
||||
// send button indicator
|
||||
m_sendBtn = new SendButtonIndicator(this, this, m_mv);
|
||||
m_sendBtn->setPixmap(embed::getIconPixmap("mixer_send_off", 23, 16));
|
||||
m_sendBtn->move(4,4);
|
||||
|
||||
// channel number
|
||||
m_lcd = new lcdSpinBox( 2, this );
|
||||
m_lcd->model()->setRange( m_channelIndex, m_channelIndex );
|
||||
m_lcd->model()->setValue( m_channelIndex );
|
||||
m_lcd->move( 2, 58 );
|
||||
m_lcd->setMarginWidth( 1 );
|
||||
}
|
||||
|
||||
FxLine::~FxLine()
|
||||
{
|
||||
delete m_sendKnob;
|
||||
delete m_sendBtn;
|
||||
delete m_lcd;
|
||||
}
|
||||
|
||||
|
||||
void FxLine::setChannelIndex(int index) {
|
||||
m_channelIndex = index;
|
||||
|
||||
m_lcd->model()->setRange( m_channelIndex, m_channelIndex );
|
||||
m_lcd->model()->setValue( m_channelIndex );
|
||||
m_lcd->update();
|
||||
}
|
||||
|
||||
|
||||
static void drawFxLine( QPainter* p, const QWidget *fxLine, const QString& name, bool isActive, bool sendToThis )
|
||||
{
|
||||
int width = fxLine->rect().width();
|
||||
int height = fxLine->rect().height();
|
||||
|
||||
p->fillRect( fxLine->rect(), QColor( 72, 76, 88 ) );
|
||||
p->setPen( QColor( 40, 42, 48 ) );
|
||||
p->drawRect( 0, 0, width-2, height-2 );
|
||||
p->setPen( QColor( 108, 114, 132 ) );
|
||||
p->drawRect( 1, 1, width-2, height-2 );
|
||||
p->setPen( QColor( 20, 24, 32 ) );
|
||||
p->drawRect( 0, 0, width-1, height-1 );
|
||||
|
||||
// draw the mixer send background
|
||||
if( sendToThis )
|
||||
{
|
||||
p->drawPixmap(2, 0, 28, 56,
|
||||
embed::getIconPixmap("send_bg_arrow", 28, 56));
|
||||
}
|
||||
|
||||
// draw the channel name
|
||||
p->rotate( -90 );
|
||||
p->setPen( isActive ? QColor( 0, 255, 0 ) : Qt::white );
|
||||
p->setFont( pointSizeF( fxLine->font(), 7.5f ) );
|
||||
p->drawText( -145, 20, name );
|
||||
|
||||
}
|
||||
|
||||
void FxLine::paintEvent( QPaintEvent * )
|
||||
{
|
||||
FxMixer * mix = engine::fxMixer();
|
||||
bool sendToThis = mix->channelSendModel(
|
||||
m_mv->currentFxLine()->m_channelIndex, m_channelIndex) != NULL;
|
||||
QPainter painter;
|
||||
painter.begin( this );
|
||||
drawFxLine( &painter, this,
|
||||
mix->effectChannel(m_channelIndex)->m_name,
|
||||
m_mv->currentFxLine() == this, sendToThis );
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void FxLine::mousePressEvent( QMouseEvent * )
|
||||
{
|
||||
m_mv->setCurrentFxLine( this );
|
||||
}
|
||||
|
||||
void FxLine::mouseDoubleClickEvent( QMouseEvent * )
|
||||
{
|
||||
bool ok;
|
||||
FxMixer * mix = engine::fxMixer();
|
||||
QString new_name = QInputDialog::getText( this,
|
||||
FxMixerView::tr( "Rename FX channel" ),
|
||||
FxMixerView::tr( "Enter the new name for this "
|
||||
"FX channel" ),
|
||||
QLineEdit::Normal, mix->effectChannel(m_channelIndex)->m_name, &ok );
|
||||
if( ok && !new_name.isEmpty() )
|
||||
{
|
||||
mix->effectChannel(m_channelIndex)->m_name = new_name;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_FxLine.cxx"
|
||||
|
||||
53
src/gui/widgets/SendButtonIndicator.cpp
Normal file
53
src/gui/widgets/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 );
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* InstrumentTrack.cpp - implementation of instrument-track-class
|
||||
* (window + data-structures)
|
||||
*
|
||||
* Copyright (c) 2004-2013 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
||||
*
|
||||
@@ -106,13 +106,14 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) :
|
||||
m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f,
|
||||
this, tr( "Panning" ) ),
|
||||
m_pitchModel( 0, -100, 100, 1, this, tr( "Pitch" ) ),
|
||||
m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ),
|
||||
m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ),
|
||||
m_instrument( NULL ),
|
||||
m_soundShaping( this ),
|
||||
m_arpeggiator( this ),
|
||||
m_chordCreator( this ),
|
||||
m_piano( this )
|
||||
{
|
||||
m_effectChannelModel.setRange( 0, engine::fxMixer()->numChannels()-1, 1);
|
||||
m_baseNoteModel.setInitValue( DefaultKey );
|
||||
connect( &m_baseNoteModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateBaseNote() ) );
|
||||
|
||||
Reference in New Issue
Block a user