From 68920fda535ce250186fa2a6305f7de1c136703f Mon Sep 17 00:00:00 2001 From: Spechtstatt <93736385+spechtstatt@users.noreply.github.com> Date: Sun, 15 May 2022 21:39:02 +0200 Subject: [PATCH] Automatic FX-mixer strip management #1215 rebasing/resolved conflicts --- include/Mixer.h | 12 + include/MixerLine.h | 3 + include/MixerView.h | 13 +- src/core/Mixer.cpp | 229 +++++++++------ src/core/Song.cpp | 4 +- src/gui/MixerLine.cpp | 73 ++++- src/gui/MixerView.cpp | 245 ++++++++++++---- src/gui/editors/BBEditor.cpp | 343 +++++++++++++++++++++++ src/gui/editors/PatternEditor.cpp | 1 + src/gui/editors/TrackContainerView.cpp | 7 + src/gui/tracks/InstrumentTrackView.cpp | 7 +- src/gui/tracks/SampleTrackView.cpp | 8 +- src/gui/tracks/TrackLabelButton.cpp | 10 +- src/gui/tracks/TrackOperationsWidget.cpp | 5 + 14 files changed, 787 insertions(+), 173 deletions(-) create mode 100644 src/gui/editors/BBEditor.cpp diff --git a/include/Mixer.h b/include/Mixer.h index e6fdb2b06bb..16199429b3a 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -26,6 +26,7 @@ #define MIXER_H #include "Model.h" +#include "Track.h" #include "EffectChain.h" #include "JournallingObject.h" #include "ThreadableJob.h" @@ -66,6 +67,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; @@ -195,6 +197,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 process); + IntModel * getFxChannelModelByTrack(Track * track); + std::vector getUsedChannelCounts(); + bool isChannelUsed(int index); + bool isAutoTrackLinkToggleAllowed(int index); // reset a channel's name, fx, sends, etc void clearChannel(mix_ch_t channelIndex); diff --git a/include/MixerLine.h b/include/MixerLine.h index decd5f3aa0c..239fb5042ed 100644 --- a/include/MixerLine.h +++ b/include/MixerLine.h @@ -89,6 +89,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; @@ -112,6 +113,8 @@ private slots: void removeUnusedChannels(); void moveChannelLeft(); void moveChannelRight(); + void toogleAutoTrackLink(); + void autoTrackLinkChanged(); }; diff --git a/include/MixerView.h b/include/MixerView.h index a7b1b51919d..063acbfc030 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -36,6 +36,7 @@ #include "PixmapButton.h" #include "embed.h" #include "EffectRackView.h" +#include "Track.h" class QButtonGroup; @@ -101,7 +102,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); @@ -110,6 +110,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(); @@ -132,6 +141,8 @@ private slots: QWidget * m_racksWidget; void updateMaxChannelSelector(); + void swapChannels(int indexA, int indexB); + void updateAutoTrackSortOrder(); friend class MixerChannelView; } ; diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 688190ee2dc..b1d95055f34 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -77,6 +77,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) { @@ -289,46 +290,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( 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( 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]; @@ -372,74 +352,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 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 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 FxMixer::getUsedChannelCounts() +{ + std::vector 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; ivalue(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 ); } @@ -740,13 +789,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 ) @@ -788,11 +838,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" ) ); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index d26eab45052..3750e805744 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -818,12 +818,12 @@ void Song::addPatternTrack() void Song::addSampleTrack() { - ( void )Track::create( Track::SampleTrack, this ); + Track* track = Track::create( Track::SampleTrack, this ); + getGUI()->fxMixerView()->processAfterTrackAdd(track); } - void Song::addAutomationTrack() { ( void )Track::create( Track::AutomationTrack, this ); diff --git a/src/gui/MixerLine.cpp b/src/gui/MixerLine.cpp index bf63a903559..e504742a956 100644 --- a/src/gui/MixerLine.cpp +++ b/src/gui/MixerLine.cpp @@ -82,6 +82,7 @@ MixerLine::MixerLine( QWidget * _parent, MixerView * _mv, int _channelIndex ) : m_strokeInnerInactive( 0, 0, 0 ), m_inRename( false ) { + if( !s_sendBgArrow ) { s_sendBgArrow = new QPixmap( embed::getIconPixmap( "send_bg_arrow", 29, 56 ) ); @@ -134,8 +135,12 @@ MixerLine::MixerLine( QWidget * _parent, MixerView * _mv, int _channelIndex ) : proxyWidget->setRotation( -90 ); proxyWidget->setPos( 8, 145 ); + autoTrackLinkChanged(); + connect( m_renameLineEdit, SIGNAL( editingFinished() ), this, SLOT( renameFinished() ) ); - connect( &Engine::mixer()->mixerChannel( m_channelIndex )->m_muteModel, SIGNAL( dataChanged() ), this, SLOT( update() ) ); + connect( &Engine::fxMixer()->effectChannel( m_channelIndex )->m_muteModel, SIGNAL( dataChanged() ), this, SLOT( update() ) ); + connect( &Engine::fxMixer()->effectChannel( m_channelIndex )->m_autoTrackLinkModel, SIGNAL(dataChanged()),this, SLOT(autoTrackLinkChanged())); + } @@ -170,9 +175,8 @@ void MixerLine::drawMixerLine( QPainter* p, const MixerLine *mixerLine, bool isA { m_renameLineEdit->setText( elidedName ); } - - int width = mixerLine->rect().width(); - int height = mixerLine->rect().height(); + int width = fxLine->rect().width(); + int height = fxLine->rect().height(); if( channel->m_hasColor && !muted ) { @@ -183,6 +187,7 @@ void MixerLine::drawMixerLine( QPainter* p, const MixerLine *mixerLine, bool isA p->fillRect( mixerLine->rect(), isActive ? mixerLine->backgroundActive().color() : p->background().color() ); } + // inner border p->setPen( isActive ? mixerLine->strokeInnerActive() : mixerLine->strokeInnerInactive() ); @@ -248,14 +253,31 @@ void MixerLine::mouseDoubleClickEvent( QMouseEvent * ) void MixerLine::contextMenuEvent( QContextMenuEvent * ) { - QPointer contextMenu = new CaptionMenu( Engine::mixer()->mixerChannel( m_channelIndex )->m_name, this ); + FxMixer * mix = Engine::fxMixer(); + QPointer contextMenu = new CaptionMenu( mix->effectChannel( m_channelIndex )->m_name, this ); + bool autoTrackLink = mix->effectChannel( m_channelIndex )->m_autoTrackLinkModel.value(); if( m_channelIndex != 0 ) // no move-options in master { - contextMenu->addAction( tr( "Move &left" ), this, SLOT( moveChannelLeft() ) ); - contextMenu->addAction( tr( "Move &right" ), this, SLOT( moveChannelRight() ) ); + if (!autoTrackLink) + { + contextMenu->addAction( tr( "Move &left" ), this, SLOT( moveChannelLeft() ) ); + bool autoTrackLinkRight = (m_channelIndex +1 < mix->numChannels()) ? mix->effectChannel( m_channelIndex +1 )->m_autoTrackLinkModel.value() : false; + if (!autoTrackLinkRight) + { + contextMenu->addAction( tr( "Move &right" ), this, SLOT( moveChannelRight() ) ); + } + } + if (mix->isAutoTrackLinkToggleAllowed(m_channelIndex)) + { + QString marker = (autoTrackLink ? " *" : ""); + contextMenu->addAction( tr("Auto track link") + marker, this, SLOT( toogleAutoTrackLink() ) ); + } + } + if (!autoTrackLink) + { + contextMenu->addAction( tr( "Rename &channel" ), this, SLOT( renameChannel() ) ); + contextMenu->addSeparator(); } - contextMenu->addAction( tr( "Rename &channel" ), this, SLOT( renameChannel() ) ); - contextMenu->addSeparator(); if( m_channelIndex != 0 ) // no remove-option in master { @@ -266,21 +288,44 @@ void MixerLine::contextMenuEvent( QContextMenuEvent * ) contextMenu->addSeparator(); QMenu colorMenu(tr("Color"), this); - colorMenu.setIcon(embed::getIconPixmap("colorize")); - colorMenu.addAction(tr("Change"), this, SLOT(selectColor())); - colorMenu.addAction(tr("Reset"), this, SLOT(resetColor())); - colorMenu.addAction(tr("Pick random"), this, SLOT(randomizeColor())); - contextMenu->addMenu(&colorMenu); + if (!autoTrackLink) + { + colorMenu.setIcon(embed::getIconPixmap("colorize")); + colorMenu.addAction(tr("Change"), this, SLOT(selectColor())); + colorMenu.addAction(tr("Reset"), this, SLOT(resetColor())); + colorMenu.addAction(tr("Pick random"), this, SLOT(randomizeColor())); + contextMenu->addMenu(&colorMenu); + } contextMenu->exec( QCursor::pos() ); delete contextMenu; } +void FxLine::toogleAutoTrackLink() +{ + FxMixerView * mix = getGUI()->fxMixerView(); + mix->toggleAutoTrackLink(m_channelIndex); +} +void FxLine::autoTrackLinkChanged() +{ + auto channel = Engine::fxMixer()->effectChannel( m_channelIndex ); + if (channel->m_autoTrackLinkModel.value()) + { + m_renameEditPalette.setColor(QPalette::Text,Qt::green); + } + else + { + m_renameEditPalette.setColor(QPalette::Text,Qt::white); + } + m_renameLineEdit->setPalette(m_renameEditPalette); +} void MixerLine::renameChannel() { + if (Engine::fxMixer()->effectChannel( m_channelIndex )->m_autoTrackLinkModel.value()) return; + m_inRename = true; setToolTip( "" ); m_renameLineEdit->setReadOnly( false ); diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 5368fcf7d7e..1ac0afa1e1e 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -174,6 +174,93 @@ MixerView::~MixerView() } } +void FxMixerView::processAfterTrackAdd(Track * track) +{ + // TODO: check if an autotrack mode is enabled (still missing) + FxMixer * mix = Engine::fxMixer(); + IntModel * model = mix->getFxChannelModelByTrack(track); + if ( model != NULL) + { + int channelIndex = addNewChannel(); + model->setValue( channelIndex ); + mix->effectChannel(channelIndex)->m_autoTrackLinkModel.setValue(true); + + processAfterTrackStyleModify(track); + + setCurrentFxLine( channelIndex ); + } +} + +void FxMixerView::processAfterTrackStyleModify(Track * track) +{ + FxMixer * mix = Engine::fxMixer(); + IntModel * model = Engine::fxMixer()->getFxChannelModelByTrack(track); + if (model != NULL) + { + int index = model->value(); + FxChannel * channel = mix->effectChannel(index); + if (channel->m_autoTrackLinkModel.value()) + { + channel->m_name = track->name(); + if (track->useColor()) { channel->setColor (track->color()); } + updateFxLine(index); + } + } +} + +void FxMixerView::processAfterTrackFxMixerModify(Track * track) +{ + FxMixer * mix = Engine::fxMixer(); + IntModel * model = Engine::fxMixer()->getFxChannelModelByTrack(track); + if (model != NULL) + { + // check if there are more than one track is pointing to the same mixer channel + // if yes disable the autotracklink + std::vector used(m_fxChannelViews.size(), false); + bool needUpdate = false; + std::vector usedChannelCounts = mix->getUsedChannelCounts(); + for(unsigned long i = 0; i< usedChannelCounts.size();i++) + { + if (usedChannelCounts[i] == 0 || usedChannelCounts[i] > 1) + { + mix->effectChannel(i)->m_autoTrackLinkModel.setValue(false); + needUpdate = true; + } + } + if (needUpdate) updateAutoTrackSortOrder(); + } +} + +void FxMixerView::processAfterTrackMove(Track * track) +{ + FxMixer * mix = Engine::fxMixer(); + IntModel * model = Engine::fxMixer()->getFxChannelModelByTrack(track); + if (model != NULL) + { + FxChannel * channel = mix->effectChannel(model->value()); + if (channel->m_autoTrackLinkModel.value()) + { + updateAutoTrackSortOrder(); + } + } +} + +void FxMixerView::processAfterTrackDelete(Track * track) +{ + FxMixer * mix = Engine::fxMixer(); + IntModel * model = mix->getFxChannelModelByTrack(track); + if ( model != NULL) + { + int channelIndex = mix->getFxChannelModelByTrack(track)->value(); + FxChannel * channel = mix->effectChannel(channelIndex); + if (channel->m_autoTrackLinkModel.value()) + { + deleteChannel(channelIndex); + updateAutoTrackSortOrder(); + } + } +} + int MixerView::addNewChannel() @@ -191,9 +278,60 @@ int MixerView::addNewChannel() updateMaxChannelSelector(); + updateAutoTrackSortOrder(); + return newChannelIndex; } +void FxMixerView::updateAutoTrackSortOrder() +{ + return; + + FxMixer * mix = Engine::fxMixer(); + QList *list = new QList(); + + // add all non auto track first + for( int i = 1; ieffectChannel(i)->m_autoTrackLinkModel.value()) + { + list->append(i); + } + } + + // add auto tracks in the order of the song tracks + mix->processFxTracks([&, list](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel) + mutable { + (void) track; + if (fxChannel == NULL) return; + if (fxChannel->m_autoTrackLinkModel.value()) + { + list->append(fxChannelModel->value()); + } + }); + + + // bubblesort here because the list is normally almost ordered + int n = list->length(); + bool swapped = false; + do + { + for (int i=0; ivalue(i) > list->value(i+1)) + { + // a (+1) because we didn't include master track in our list + swapChannels(list->value(i+1), list->value(i+2)); + list->swapItemsAt(i,i+1); + } + } + n = n-1; + } while (swapped); + + // TODO: think about focus + // setCurrentFxLine( index - 1 ); +} + void MixerView::refreshDisplay() { @@ -236,29 +374,13 @@ void MixerView::refreshDisplay() // update the and max. channel number for every instrument void MixerView::updateMaxChannelSelector() { - TrackContainer::TrackList songTracks = Engine::getSong()->tracks(); - TrackContainer::TrackList patternStoreTracks = Engine::patternStore()->tracks(); - - TrackContainer::TrackList trackLists[] = {songTracks, patternStoreTracks}; - for(int tl=0; tl<2; ++tl) + FxMixer * mix = Engine::fxMixer(); + mix->processFxTracks([this](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel) { - TrackContainer::TrackList trackList = trackLists[tl]; - for(int i=0; itype() == Track::InstrumentTrack ) - { - InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; - inst->mixerChannelModel()->setRange(0, - m_mixerChannelViews.size()-1,1); - } - else if( trackList[i]->type() == Track::SampleTrack ) - { - SampleTrack * strk = (SampleTrack *) trackList[i]; - strk->mixerChannelModel()->setRange(0, - m_mixerChannelViews.size()-1,1); - } - } - } + (void) track; + (void) fxChannel; + fxChannelModel->setRange(0,m_fxChannelViews.size()-1,1); + }); } @@ -431,71 +553,76 @@ void MixerView::deleteChannel(int index) void MixerView::deleteUnusedChannels() { - TrackContainer::TrackList tracks; - tracks += Engine::getSong()->tracks(); - tracks += Engine::patternStore()->tracks(); - - std::vector inUse(m_mixerChannelViews.size(), false); + FxMixer * mix = Engine::fxMixer(); + std::vector inUse = mix->getUsedChannelCounts(); - //Populate inUse by checking the destination channel for every track - for (Track* t: tracks) + //Check all channels except master, delete those with no incoming sends + for(int i = m_fxChannelViews.size()-1; i > 0; --i) { - //The channel that this track sends to. Since master channel is always in use, - //setting this to 0 is a safe default (for tracks that don't sent to the mixer). - int channel = 0; - if (t->type() == Track::InstrumentTrack) + if ((inUse[i]==0) && Engine::fxMixer()->effectChannel(i)->m_receives.isEmpty()) { - InstrumentTrack* inst = dynamic_cast(t); - channel = inst->mixerChannelModel()->value(); + deleteChannel(i); } - else if (t->type() == Track::SampleTrack) + } +} + + +void FxMixerView::toggleAutoTrackLink(int index) +{ + FxMixer * mix = Engine::fxMixer(); + mix->toggleAutoTrackLink(index); + FxChannel * channel = mix->effectChannel(index); + if (!channel->m_autoTrackLinkModel.value()) return; + + Track * trackFound = NULL; + mix->processFxTracks([&trackFound, index](Track * track, IntModel * fxChannelModel, FxChannel * fxChannel) + mutable { + (void) fxChannel; + if (fxChannelModel->value() == index) { - SampleTrack *strack = dynamic_cast(t); - channel = strack->mixerChannelModel()->value(); + trackFound = track; } - inUse[channel] = true; - } + }); - //Check all channels except master, delete those with no incoming sends - for(int i = m_mixerChannelViews.size()-1; i > 0; --i) + if (trackFound != NULL) { - if (!inUse[i] && Engine::mixer()->mixerChannel(i)->m_receives.isEmpty()) - { deleteChannel(i); } + updateAutoTrackSortOrder(); + processAfterTrackStyleModify(trackFound); } } - -void MixerView::moveChannelLeft(int index, int focusIndex) +void FxMixerView::swapChannels(int indexA, int indexB) { - // can't move master or first channel left or last channel right - if( index <= 1 || index >= m_mixerChannelViews.size() ) return; + if (( indexA == indexB ) || + ( indexA <= 1 || indexA >= m_fxChannelViews.size() ) || + ( indexB <= 1 || indexB >= m_fxChannelViews.size() )) + { + return; + } Mixer *m = Engine::mixer(); - // Move instruments channels - m->moveChannelLeft( index ); + m->swapChannels(indexA,indexB); // Update widgets models - m_mixerChannelViews[index]->setChannelIndex( index ); - m_mixerChannelViews[index - 1]->setChannelIndex( index - 1 ); - - // Focus on new position - setCurrentMixerLine( focusIndex ); + m_fxChannelViews[indexA]->setChannelIndex(indexA); + m_fxChannelViews[indexB]->setChannelIndex(indexB ); } - -void MixerView::moveChannelLeft(int index) +void FxMixerView::moveChannelLeft(int index) { - moveChannelLeft( index, index - 1 ); + swapChannels( index, index - 1); + setCurrentFxLine( index - 1 ); } void MixerView::moveChannelRight(int index) { - moveChannelLeft( index + 1, index + 1 ); + swapChannels( index , index + 1 ); + setCurrentFxLine( index + 1 ); } diff --git a/src/gui/editors/BBEditor.cpp b/src/gui/editors/BBEditor.cpp new file mode 100644 index 00000000000..e5a51699e54 --- /dev/null +++ b/src/gui/editors/BBEditor.cpp @@ -0,0 +1,343 @@ +/* + * BBEditor.cpp - basic main-window for editing of beats and basslines + * + * Copyright (c) 2004-2008 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * 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 "BBEditor.h" + +#include +#include +#include + +#include "ComboBox.h" +#include "BBTrack.h" +#include "BBTrackContainer.h" +#include "DataFile.h" +#include "embed.h" +#include "MainWindow.h" +#include "Song.h" +#include "StringPairDrag.h" + +#include "Pattern.h" +#include "GuiApplication.h" +#include "FxMixerView.h" + + + +BBEditor::BBEditor( BBTrackContainer* tc ) : + Editor(false), + m_trackContainerView( new BBTrackContainerView(tc) ) +{ + setWindowIcon( embed::getIconPixmap( "bb_track_btn" ) ); + setWindowTitle( tr( "Beat+Bassline Editor" ) ); + setCentralWidget(m_trackContainerView); + + setAcceptDrops(true); + m_toolBar->setAcceptDrops(true); + connect(m_toolBar, SIGNAL(dragEntered(QDragEnterEvent*)), m_trackContainerView, SLOT(dragEnterEvent(QDragEnterEvent*))); + connect(m_toolBar, SIGNAL(dropped(QDropEvent*)), m_trackContainerView, SLOT(dropEvent(QDropEvent*))); + + // TODO: Use style sheet + if( ConfigManager::inst()->value( "ui", + "compacttrackbuttons" ).toInt() ) + { + setMinimumWidth( TRACK_OP_WIDTH_COMPACT + DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + + 2 * TCO_BORDER_WIDTH + 384 ); + } + else + { + setMinimumWidth( TRACK_OP_WIDTH + DEFAULT_SETTINGS_WIDGET_WIDTH + + 2 * TCO_BORDER_WIDTH + 384 ); + } + + + m_playAction->setToolTip(tr( "Play/pause current beat/bassline (Space)" )); + m_stopAction->setToolTip(tr( "Stop playback of current beat/bassline (Space)" )); + + + // Beat selector + DropToolBar *beatSelectionToolBar = addDropToolBarToTop(tr("Beat selector")); + + m_bbComboBox = new ComboBox( m_toolBar ); + m_bbComboBox->setFixedSize( 200, ComboBox::DEFAULT_HEIGHT ); + m_bbComboBox->setModel( &tc->m_bbComboBoxModel ); + + beatSelectionToolBar->addWidget( m_bbComboBox ); + + + // Track actions + DropToolBar *trackAndStepActionsToolBar = addDropToolBarToTop(tr("Track and step actions")); + + + trackAndStepActionsToolBar->addAction(embed::getIconPixmap("add_bb_track"), tr("Add beat/bassline"), + Engine::getSong(), SLOT(addBBTrack())); + trackAndStepActionsToolBar->addAction(embed::getIconPixmap("clone_bb_track_pattern"), tr("Clone beat/bassline pattern"), + m_trackContainerView, SLOT(clonePattern())); + trackAndStepActionsToolBar->addAction( + embed::getIconPixmap("add_sample_track"), + tr("Add sample-track"), m_trackContainerView, + SLOT(addSampleTrack())); + trackAndStepActionsToolBar->addAction(embed::getIconPixmap("add_automation"), tr("Add automation-track"), + m_trackContainerView, SLOT(addAutomationTrack())); + + QWidget* stretch = new QWidget(m_toolBar); + stretch->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + trackAndStepActionsToolBar->addWidget(stretch); + + + // Step actions + trackAndStepActionsToolBar->addAction(embed::getIconPixmap("step_btn_remove"), tr("Remove steps"), + m_trackContainerView, SLOT(removeSteps())); + trackAndStepActionsToolBar->addAction(embed::getIconPixmap("step_btn_add"), tr("Add steps"), + m_trackContainerView, SLOT( addSteps())); + trackAndStepActionsToolBar->addAction( embed::getIconPixmap( "step_btn_duplicate" ), tr( "Clone Steps" ), + m_trackContainerView, SLOT( cloneSteps() ) ); + + connect( &tc->m_bbComboBoxModel, SIGNAL( dataChanged() ), + m_trackContainerView, SLOT( updatePosition() ) ); + + + QAction* viewNext = new QAction(this); + connect(viewNext, SIGNAL(triggered()), m_bbComboBox, SLOT(selectNext())); + viewNext->setShortcut(Qt::Key_Plus); + addAction(viewNext); + + QAction* viewPrevious = new QAction(this); + connect(viewPrevious, SIGNAL(triggered()), m_bbComboBox, SLOT(selectPrevious())); + viewPrevious->setShortcut(Qt::Key_Minus); + addAction(viewPrevious); +} + + +BBEditor::~BBEditor() +{ +} + + +QSize BBEditor::sizeHint() const +{ + return {minimumWidth()+10, 300}; +} + + +void BBEditor::removeBBView( int bb ) +{ + m_trackContainerView->removeBBView(bb); +} + + +void BBEditor::play() +{ + if( Engine::getSong()->playMode() != Song::Mode_PlayBB ) + { + Engine::getSong()->playBB(); + } + else + { + Engine::getSong()->togglePause(); + } +} + + +void BBEditor::stop() +{ + Engine::getSong()->stop(); +} + + + + +BBTrackContainerView::BBTrackContainerView(BBTrackContainer* tc) : + TrackContainerView(tc), + m_bbtc(tc) +{ + setModel( tc ); +} + + + + +void BBTrackContainerView::addSteps() +{ + makeSteps( false ); +} + +void BBTrackContainerView::cloneSteps() +{ + makeSteps( true ); +} + + + + +void BBTrackContainerView::removeSteps() +{ + TrackContainer::TrackList tl = model()->tracks(); + + for( TrackContainer::TrackList::iterator it = tl.begin(); + it != tl.end(); ++it ) + { + if( ( *it )->type() == Track::InstrumentTrack ) + { + Pattern* p = static_cast( ( *it )->getTCO( m_bbtc->currentBB() ) ); + p->removeSteps(); + } + } +} + + + + +void BBTrackContainerView::addSampleTrack() +{ + Track* track = Track::create( Track::SampleTrack, model() ); + getGUI()->fxMixerView()->processAfterTrackAdd(track); +} + + + + +void BBTrackContainerView::addAutomationTrack() +{ + (void) Track::create( Track::AutomationTrack, model() ); +} + + + + +void BBTrackContainerView::removeBBView(int bb) +{ + for( TrackView* view : trackViews() ) + { + view->getTrackContentWidget()->removeTCOView( bb ); + } +} + + + +void BBTrackContainerView::saveSettings(QDomDocument& doc, QDomElement& element) +{ + MainWindow::saveWidgetState( parentWidget(), element ); +} + +void BBTrackContainerView::loadSettings(const QDomElement& element) +{ + MainWindow::restoreWidgetState(parentWidget(), element); +} + + + + +void BBTrackContainerView::dropEvent(QDropEvent* de) +{ + QString type = StringPairDrag::decodeKey( de ); + QString value = StringPairDrag::decodeValue( de ); + + if( type.left( 6 ) == "track_" ) + { + DataFile dataFile( value.toUtf8() ); + Track * t = Track::create( dataFile.content().firstChild().toElement(), model() ); + + // Ensure BB TCOs exist + bool hasValidBBTCOs = false; + if (t->getTCOs().size() == m_bbtc->numOfBBs()) + { + hasValidBBTCOs = true; + for (int i = 0; i < t->getTCOs().size(); ++i) + { + if (t->getTCOs()[i]->startPosition() != TimePos(i, 0)) + { + hasValidBBTCOs = false; + break; + } + } + } + if (!hasValidBBTCOs) + { + t->deleteTCOs(); + t->createTCOsForBB(m_bbtc->numOfBBs() - 1); + } + m_bbtc->updateAfterTrackAdd(); + + de->accept(); + } + else + { + TrackContainerView::dropEvent( de ); + } +} + + + + +void BBTrackContainerView::updatePosition() +{ + //realignTracks(); + emit positionChanged( m_currentPosition ); +} + + + + +void BBTrackContainerView::makeSteps( bool clone ) +{ + TrackContainer::TrackList tl = model()->tracks(); + + for( TrackContainer::TrackList::iterator it = tl.begin(); + it != tl.end(); ++it ) + { + if( ( *it )->type() == Track::InstrumentTrack ) + { + Pattern* p = static_cast( ( *it )->getTCO( m_bbtc->currentBB() ) ); + if( clone ) + { + p->cloneSteps(); + } else + { + p->addSteps(); + } + } + } +} + +// Creates a clone of the current BB track with the same pattern, but no TCOs in the song editor +// TODO: Avoid repeated code from cloneTrack and clearTrack in TrackOperationsWidget somehow +void BBTrackContainerView::clonePattern() +{ + // Get the current BBTrack id + BBTrackContainer *bbtc = static_cast(model()); + const int cur_bb = bbtc->currentBB(); + + BBTrack *bbt = BBTrack::findBBTrack(cur_bb); + + if( bbt ) + { + // Clone the track + Track *newTrack = bbt->clone(); + bbtc->setCurrentBB( static_cast( newTrack )->index() ); + + // Track still have the TCOs which is undesirable in this case, clear the track + newTrack->lock(); + newTrack->deleteTCOs(); + newTrack->unlock(); + } +} diff --git a/src/gui/editors/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index 86a116dd298..72b415b5fe4 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -88,6 +88,7 @@ void PatternEditor::removeSteps() void PatternEditor::addSampleTrack() { (void) Track::create( Track::SampleTrack, model() ); + getGUI()->fxMixerView()->processAfterTrackAdd(track); } diff --git a/src/gui/editors/TrackContainerView.cpp b/src/gui/editors/TrackContainerView.cpp index 6c85cf1a3b2..2fdb0d183e2 100644 --- a/src/gui/editors/TrackContainerView.cpp +++ b/src/gui/editors/TrackContainerView.cpp @@ -43,6 +43,7 @@ #include "TrackView.h" #include "GuiApplication.h" #include "PluginFactory.h" +#include "FxMixerView.h" namespace lmms { @@ -166,6 +167,8 @@ void TrackContainerView::removeTrackView( TrackView * _tv ) int index = m_trackViews.indexOf( _tv ); if( index != -1 ) { + Track * t = _tv->getTrack(); + getGUI()->fxMixerView()->processAfterTrackDelete(t); m_trackViews.removeAt( index ); disconnect( _tv ); @@ -203,6 +206,7 @@ void TrackContainerView::moveTrackView( TrackView * trackView, int indexTo ) m_tc->m_tracks.insert( indexTo, track ); m_trackViews.move( indexFrom, indexTo ); + getGUI()->fxMixerView()->processAfterTrackMove(track); realignTracks(); } @@ -402,6 +406,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) InstrumentTrack * it = dynamic_cast( Track::create( Track::InstrumentTrack, m_tc ) ); + getGUI()->fxMixerView()->processAfterTrackAdd(it); InstrumentLoaderThread *ilt = new InstrumentLoaderThread( this, it, value ); ilt->start(); @@ -415,6 +420,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) InstrumentTrack * it = dynamic_cast( Track::create( Track::InstrumentTrack, m_tc ) ); + getGUI()->fxMixerView()->processAfterTrackAdd(it); PluginFactory::PluginInfoAndKey piakn = getPluginFactory()->pluginSupportingExtension(FileItem::extension(value)); Instrument * i = it->loadInstrument(piakn.info.name(), &piakn.key); @@ -428,6 +434,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) InstrumentTrack * it = dynamic_cast( Track::create( Track::InstrumentTrack, m_tc ) ); + getGUI()->fxMixerView()->processAfterTrackAdd(it); it->setSimpleSerializing(); it->loadSettings( dataFile.content().toElement() ); //it->toggledInstrumentTrackButton( true ); diff --git a/src/gui/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index b7a2fda614d..815a0d82d39 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -241,9 +241,10 @@ void InstrumentTrackView::createMixerLine() /*! \brief Assign a specific mixer Channel for this track */ void InstrumentTrackView::assignMixerLine(int channelIndex) { - model()->mixerChannelModel()->setValue( channelIndex ); - - getGUI()->mixerView()->setCurrentMixerLine( channelIndex ); + model()->effectChannelModel()->setValue( channelIndex ); + FxMixerView* fxMixerView = getGUI()->fxMixerView(); + fxMixerView->processAfterTrackFxMixerModify(getTrack()); + fxMixerView->setCurrentFxLine( channelIndex ); } diff --git a/src/gui/tracks/SampleTrackView.cpp b/src/gui/tracks/SampleTrackView.cpp index 9897b3309e6..d10656b5c12 100644 --- a/src/gui/tracks/SampleTrackView.cpp +++ b/src/gui/tracks/SampleTrackView.cpp @@ -232,10 +232,10 @@ void SampleTrackView::createMixerLine() /*! \brief Assign a specific mixer Channel for this track */ void SampleTrackView::assignMixerLine(int channelIndex) { - model()->mixerChannelModel()->setValue(channelIndex); - - getGUI()->mixerView()->setCurrentMixerLine(channelIndex); + model()->effectChannelModel()->setValue(channelIndex); + FxMixerView* fxMixerView = getGUI()->fxMixerView(); + fxMixerView->processAfterTrackFxMixerModify(getTrack()); + fxMixerView->setCurrentFxLine( channelIndex ); } - } // namespace lmms::gui diff --git a/src/gui/tracks/TrackLabelButton.cpp b/src/gui/tracks/TrackLabelButton.cpp index 6f3d1372741..ab4355da52b 100644 --- a/src/gui/tracks/TrackLabelButton.cpp +++ b/src/gui/tracks/TrackLabelButton.cpp @@ -37,6 +37,8 @@ #include "Song.h" #include "TrackRenameLineEdit.h" #include "TrackView.h" +#include "GuiApplication.h" +#include "MixerView.h" namespace lmms::gui { @@ -89,7 +91,9 @@ void TrackLabelButton::rename() renameDlg.exec(); if( txt != text() ) { - m_trackView->getTrack()->setName( txt ); + Track * track = m_trackView->getTrack(); + track->setName( txt ); + getGUI()->fxMixerView()->processAfterTrackStyleModify(track); Engine::getSong()->setModified(); } } @@ -116,7 +120,9 @@ void TrackLabelButton::renameFinished() if( m_renameLineEdit->text() != m_trackView->getTrack()->name() ) { setText( elideName( m_renameLineEdit->text() ) ); - m_trackView->getTrack()->setName( m_renameLineEdit->text() ); + Track * track = m_trackView->getTrack(); + track->setName( m_renameLineEdit->text() ); + getGUI()->fxMixerView()->processAfterTrackStyleModify(track); Engine::getSong()->setModified(); } } diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index c529b26e8e2..d10789aeb1d 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -46,6 +46,8 @@ #include "Track.h" #include "TrackContainerView.h" #include "TrackView.h" +#include "GuiApplication.h" +#include "FxMixerView.h" namespace lmms::gui { @@ -282,6 +284,7 @@ void TrackOperationsWidget::selectTrackColor() auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); track->setColor(new_color); + getGUI()->fxMixerView()->processAfterTrackStyleModify(track); Engine::getSong()->setModified(); } @@ -290,6 +293,7 @@ void TrackOperationsWidget::resetTrackColor() auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); track->resetColor(); + getGUI()->fxMixerView()->processAfterTrackStyleModify(track); Engine::getSong()->setModified(); } @@ -299,6 +303,7 @@ void TrackOperationsWidget::randomizeTrackColor() auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); track->setColor(buffer); + getGUI()->fxMixerView()->processAfterTrackStyleModify(track); Engine::getSong()->setModified(); }