Skip to content

Commit

Permalink
Automatic FX-mixer strip management LMMS#1215
Browse files Browse the repository at this point in the history
rebasing/resolved conflicts
  • Loading branch information
spechtstatt committed May 20, 2022
1 parent 3964c53 commit 199deb3
Show file tree
Hide file tree
Showing 14 changed files with 788 additions and 174 deletions.
12 changes: 12 additions & 0 deletions include/Mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#define MIXER_H

#include "Model.h"
#include "Track.h"
#include "EffectChain.h"
#include "JournallingObject.h"
#include "ThreadableJob.h"
Expand Down Expand Up @@ -62,6 +63,7 @@ class MixerChannel : public ThreadableJob
int m_channelIndex; // what channel index are we
bool m_queued; // are we queued up for rendering yet?
bool m_muted; // are we muted? updated per period so we don't have to call m_muteModel.value() twice
BoolModel m_autoTrackLinkModel;

// pointers to other channels that this one sends to
MixerRouteVector m_sends;
Expand Down Expand Up @@ -191,6 +193,16 @@ class LMMS_EXPORT Mixer : public Model, public JournallingObject
// re-arrange channels
void moveChannelLeft(int index);
void moveChannelRight(int index);
void swapChannels(int indexA, int indexB);

void toggleAutoTrackLink(int index);

// process tracks which have a fx send
void processFxTracks(std::function<void(Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)> process);
IntModel * getFxChannelModelByTrack(Track * track);
std::vector<int> getUsedChannelCounts();
bool isChannelUsed(int index);
bool isAutoTrackLinkToggleAllowed(int index);

// reset a channel's name, fx, sends, etc
void clearChannel(mix_ch_t channelIndex);
Expand Down
3 changes: 3 additions & 0 deletions include/MixerLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class MixerLine : public QWidget
MixerView * m_mv;
LcdWidget* m_lcd;
int m_channelIndex;
QPalette m_renameEditPalette;
QBrush m_backgroundActive;
QColor m_strokeOuterActive;
QColor m_strokeOuterInactive;
Expand All @@ -110,6 +111,8 @@ private slots:
void removeUnusedChannels();
void moveChannelLeft();
void moveChannelRight();
void toogleAutoTrackLink();
void autoTrackLinkChanged();
};


Expand Down
13 changes: 12 additions & 1 deletion include/MixerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "PixmapButton.h"
#include "embed.h"
#include "EffectRackView.h"
#include "Track.h"

class QButtonGroup;
class MixerLine;
Expand Down Expand Up @@ -96,7 +97,6 @@ class LMMS_EXPORT MixerView : public QWidget, public ModelView,

// move the channel to the left or right
void moveChannelLeft(int index);
void moveChannelLeft(int index, int focusIndex);
void moveChannelRight(int index);

void renameChannel(int index);
Expand All @@ -105,6 +105,15 @@ class LMMS_EXPORT MixerView : public QWidget, public ModelView,
// useful for loading projects
void refreshDisplay();

// Auto track link support
void processAfterTrackAdd(Track * track);
void processAfterTrackStyleModify(Track * track);
void processAfterTrackFxMixerModify(Track * track);
void processAfterTrackMove(Track * track);
void processAfterTrackDelete(Track * track);
void toggleAutoTrackLink(int index);


public slots:
int addNewChannel();

Expand All @@ -127,6 +136,8 @@ private slots:
QWidget * m_racksWidget;

void updateMaxChannelSelector();
void swapChannels(int indexA, int indexB);
void updateAutoTrackSortOrder();

friend class MixerChannelView;
} ;
Expand Down
229 changes: 141 additions & 88 deletions src/core/Mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ MixerChannel::MixerChannel( int idx, Model * _parent ) :
m_lock(),
m_channelIndex( idx ),
m_queued( false ),
m_autoTrackLinkModel (false, _parent),
m_hasColor( false ),
m_dependenciesMet(0)
{
Expand Down Expand Up @@ -285,46 +286,25 @@ void Mixer::deleteChannel( int index )
// channel deletion is performed between mixer rounds
Engine::audioEngine()->requestChangeInModel();

// go through every instrument and adjust for the channel index change
TrackContainer::TrackList tracks;
tracks += Engine::getSong()->tracks();
tracks += Engine::patternStore()->tracks();

for( Track* t : tracks )
processFxTracks( [index](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)
{
if( t->type() == Track::InstrumentTrack )
(void) track;
(void) fxChannel;
int curFxIndex = fxChannelModel->value(0);
if( curFxIndex == index )
{
InstrumentTrack* inst = dynamic_cast<InstrumentTrack *>( t );
int val = inst->mixerChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's channel send
// send to master
inst->mixerChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
inst->mixerChannelModel()->setValue(val-1);
}
// we are deleting this track's fx send
// send to master
fxChannelModel->setValue(0);
}
else if( t->type() == Track::SampleTrack )
else if ( curFxIndex > index )
{
SampleTrack* strk = dynamic_cast<SampleTrack *>( t );
int val = strk->mixerChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's channel send
// send to master
strk->mixerChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
strk->mixerChannelModel()->setValue(val-1);
}
// subtract 1 to make up for the missing channel
fxChannelModel->setValue(-1);
}
}
});


MixerChannel * ch = m_mixerChannels[index];

Expand Down Expand Up @@ -368,74 +348,143 @@ void Mixer::deleteChannel( int index )
Engine::audioEngine()->doneChangeInModel();
}

void FxMixer::toggleAutoTrackLink(int index)
{
m_fxChannels[index]->m_autoTrackLinkModel.setValue(! m_fxChannels[index]->m_autoTrackLinkModel.value());
}

IntModel * FxMixer::getFxChannelModelByTrack(Track * track)
{
if( track->type() == Track::InstrumentTrack )
{
InstrumentTrack * inst = (InstrumentTrack *) track;
return inst->effectChannelModel();
}
else if( track->type() == Track::SampleTrack )
{
SampleTrack * strk = (SampleTrack *) track;
return strk->effectChannelModel();
}
return NULL;
}

bool FxMixer::isAutoTrackLinkToggleAllowed(int index)
{
if (effectChannel( index )->m_autoTrackLinkModel.value()) return true;

std::vector<int> used(m_fxChannels.size(), 0);
processFxTracks([&used](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)
mutable {
(void) track;
(void) fxChannel;
++used[fxChannelModel->value()];
});

void Mixer::moveChannelLeft( int index )
return used[index] == 1;
}


void FxMixer::processFxTracks(std::function<void(Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)> process)
{
// can't move master or first channel
if( index <= 1 || index >= m_mixerChannels.size() )
TrackContainer::TrackList trackList;
trackList += Engine::getSong()->tracks();
trackList += Engine::getBBTrackContainer()->tracks();

for (Track* track: trackList)
{
IntModel * fxChannelModel = getFxChannelModelByTrack(track);
FxChannel * fxChannel = NULL;
if (fxChannelModel != NULL)
{
int channelIndex = fxChannelModel->value();
if (channelIndex > 0 && channelIndex < m_fxChannels.size() )
{
fxChannel = effectChannel(channelIndex);
}
process(track,fxChannelModel, fxChannel);
}
}
}

std::vector<int> FxMixer::getUsedChannelCounts()
{
std::vector<int> used(m_fxChannels.size(), 0);
processFxTracks([&used](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)
mutable {
(void) track;
(void) fxChannel;
++used[fxChannelModel->value()];
});

return used;
}

bool FxMixer::isChannelUsed(int index)
{
bool result = false;

processFxTracks([&result, index](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)
mutable {
(void) track;
(void) fxChannel;
if (fxChannelModel->value() == index) result = true;
});
return result;
}



void FxMixer::swapChannels(int indexA, int indexB)
{
// range check - can't move master or first channel
if (( indexA == indexB ) ||
( indexA <= 1 || indexA >= m_fxChannels.size() ) ||
( indexB <= 1 || indexB >= m_fxChannels.size() ))
{
return;
}

// channels to swap
int a = index - 1, b = index;
int a = indexA, b = indexB;

// check if m_lastSoloed is one of our swaps
if (m_lastSoloed == a) { m_lastSoloed = b; }
else if (m_lastSoloed == b) { m_lastSoloed = a; }

// go through every instrument and adjust for the channel index change
TrackContainer::TrackList songTrackList = Engine::getSong()->tracks();
TrackContainer::TrackList patternTrackList = Engine::patternStore()->tracks();

TrackContainer::TrackList trackLists[] = {songTrackList, patternTrackList};
for(int tl=0; tl<2; ++tl)
processFxTracks( [a,b](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel)
{
TrackContainer::TrackList trackList = trackLists[tl];
for(int i=0; i<trackList.size(); ++i)
(void) track;
(void) fxChannel;
int curFxIndex = fxChannelModel->value(0);
if( curFxIndex == a )
{
if( trackList[i]->type() == Track::InstrumentTrack )
{
InstrumentTrack * inst = (InstrumentTrack *) trackList[i];
int val = inst->mixerChannelModel()->value(0);
if( val == a )
{
inst->mixerChannelModel()->setValue(b);
}
else if( val == b )
{
inst->mixerChannelModel()->setValue(a);
}
}
else if( trackList[i]->type() == Track::SampleTrack )
{
SampleTrack * strk = (SampleTrack *) trackList[i];
int val = strk->mixerChannelModel()->value(0);
if( val == a )
{
strk->mixerChannelModel()->setValue(b);
}
else if( val == b )
{
strk->mixerChannelModel()->setValue(a);
}
}
fxChannelModel->setValue(b);
}
}
else if ( curFxIndex == b )
{
fxChannelModel->setValue(a);
}
});

// Swap positions in array
qSwap(m_mixerChannels[index], m_mixerChannels[index - 1]);
qSwap(m_fxChannels[a], m_fxChannels[b]);

// Update m_channelIndex of both channels
m_mixerChannels[index]->m_channelIndex = index;
m_mixerChannels[index - 1]->m_channelIndex = index -1;
m_fxChannels[a]->m_channelIndex = a;
m_fxChannels[b]->m_channelIndex = b;
}


void FxMixer::moveChannelLeft( int index )
{
swapChannels(index - 1, index);
}


void Mixer::moveChannelRight( int index )
{
moveChannelLeft( index + 1 );
swapChannels(index, index + 1 );
}


Expand Down Expand Up @@ -736,13 +785,14 @@ void Mixer::saveSettings( QDomDocument & _doc, QDomElement & _this )
QDomElement mixch = _doc.createElement( QString( "mixerchannel" ) );
_this.appendChild( mixch );

ch->m_fxChain.saveState( _doc, mixch );
ch->m_volumeModel.saveSettings( _doc, mixch, "volume" );
ch->m_muteModel.saveSettings( _doc, mixch, "muted" );
ch->m_soloModel.saveSettings( _doc, mixch, "soloed" );
mixch.setAttribute( "num", i );
mixch.setAttribute( "name", ch->m_name );
if( ch->m_hasColor ) mixch.setAttribute( "color", ch->m_color.name() );
ch->m_fxChain.saveState( _doc, fxch );
ch->m_volumeModel.saveSettings( _doc, fxch, "volume" );
ch->m_muteModel.saveSettings( _doc, fxch, "muted" );
ch->m_soloModel.saveSettings( _doc, fxch, "soloed" );
ch->m_autoTrackLinkModel.saveSettings( _doc, fxch, "autoTrackLink" );
fxch.setAttribute( "num", i );
fxch.setAttribute( "name", ch->m_name );
if( ch->m_hasColor ) fxch.setAttribute( "color", ch->m_color.name() );

// add the channel sends
for( int si = 0; si < ch->m_sends.size(); ++si )
Expand Down Expand Up @@ -784,11 +834,14 @@ void Mixer::loadSettings( const QDomElement & _this )
// allocate enough channels
allocateChannelsTo( num );

m_mixerChannels[num]->m_volumeModel.loadSettings( mixch, "volume" );
m_mixerChannels[num]->m_muteModel.loadSettings( mixch, "muted" );
m_mixerChannels[num]->m_soloModel.loadSettings( mixch, "soloed" );
m_mixerChannels[num]->m_name = mixch.attribute( "name" );
if( mixch.hasAttribute( "color" ) )
m_fxChannels[num]->m_volumeModel.loadSettings( fxch, "volume" );

m_fxChannels[num]->m_muteModel.loadSettings( fxch, "muted" );
m_fxChannels[num]->m_soloModel.loadSettings( fxch, "soloed" );
m_fxChannels[num]->m_autoTrackLinkModel.loadSettings( fxch, "autoTrackLink" );

m_fxChannels[num]->m_name = fxch.attribute( "name" );
if( fxch.hasAttribute( "color" ) )
{
m_mixerChannels[num]->m_hasColor = true;
m_mixerChannels[num]->m_color.setNamedColor( mixch.attribute( "color" ) );
Expand Down
Loading

0 comments on commit 199deb3

Please sign in to comment.