diff --git a/data/themes/default/auto_link_active.png b/data/themes/default/auto_link_active.png new file mode 100644 index 00000000000..fd765509dac Binary files /dev/null and b/data/themes/default/auto_link_active.png differ diff --git a/data/themes/default/auto_link_inactive.png b/data/themes/default/auto_link_inactive.png new file mode 100644 index 00000000000..83208784996 Binary files /dev/null and b/data/themes/default/auto_link_inactive.png differ diff --git a/include/Mixer.h b/include/Mixer.h index ecd864ae749..d157763f7ee 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -25,7 +25,9 @@ #ifndef MIXER_H #define MIXER_H +#include "ConfigManager.h" #include "Model.h" +#include "Track.h" #include "EffectChain.h" #include "JournallingObject.h" #include "ThreadableJob.h" @@ -66,6 +68,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; @@ -142,6 +145,131 @@ class LMMS_EXPORT Mixer : public Model, public JournallingObject { Q_OBJECT public: + struct autoTrackLinkSettings + { + // Remark: take care of the enum order as it is used for range checking during reading the values from config + enum class LinkStyle + { + Disabled, /* don't link any styles */ + LinkNameAndColor, /* link namd and color */ + LinkColorOnly, /* link only color */ + }; + + enum class AutoAdd + { + Disabled, /* do not link tracks after add */ + CreateNew, /* one channel for each track after add*/ + UseFirstTrackOnly, /* use always the first channel in the editor for new tracks*/ + }; + + enum class LinkMode + { + OneToOne, /* do not link song editor tracks */ + OneToMany, /* one channel for each song editor track */ + }; + + enum class AutoSort + { + Disabled, /* automatic sorting is disabled */ + LinkedPattern, /* linked tracks first, pattern editor afterwards */ + PatternLinked /* pattern editor first, linked tracks afterwards */ + }; + + struct editorSettings + { + LinkStyle linkStyle; + AutoAdd autoAdd; + + editorSettings() + { + linkStyle = LinkStyle::LinkNameAndColor; + autoAdd = AutoAdd::CreateNew; + } + }; + + bool enabled; + bool autoDelete; + LinkMode linkMode; + AutoSort autoSort; + editorSettings songEditor; + editorSettings patternEditor; + + bool getAsBoolOrDefault(const QString & text, bool defaultValue) + { + return (bool)getIntInRangeOrDefault(text, 0,1,defaultValue); + } + + + AutoAdd getAsAutoAddOrDefault(const QString & text, AutoAdd defaultValue) + { + return (AutoAdd) getIntInRangeOrDefault(text,(int)AutoAdd::Disabled, (int)AutoAdd::UseFirstTrackOnly,(int)defaultValue); + } + + LinkStyle getAsLinkStyleOrDefault(const QString & text, LinkStyle defaultValue) + { + return (LinkStyle) getIntInRangeOrDefault(text,(int)LinkStyle::Disabled, (int)LinkStyle::LinkColorOnly,(int)defaultValue); + } + + AutoSort getAsAutoSortOrDefault(const QString & text, AutoSort defaultValue) + { + return (AutoSort) getIntInRangeOrDefault(text,(int)AutoSort::Disabled, (int)AutoSort::PatternLinked,(int)defaultValue); + } + + + LinkMode getAsLinkModeOrDefault(const QString & text, LinkMode defaultValue) + { + return (LinkMode) getIntInRangeOrDefault(text,(int)LinkMode::OneToOne, (int)LinkMode::OneToMany,(int)defaultValue); + } + + bool autoAdd() + { + return songEditor.autoAdd != AutoAdd::Disabled || patternEditor.autoAdd != AutoAdd::Disabled; + } + + + bool linkName(bool isPatternEditor) + { + return isPatternEditor ? + patternEditor.linkStyle == LinkStyle::LinkNameAndColor : + songEditor.linkStyle == LinkStyle::LinkNameAndColor; + } + + bool linkColor(bool isPatternEditor) + { + return isPatternEditor ? + (patternEditor.linkStyle == LinkStyle::LinkNameAndColor) || (patternEditor.linkStyle == LinkStyle::LinkColorOnly) : + (songEditor.linkStyle == LinkStyle::LinkNameAndColor) || (songEditor.linkStyle == LinkStyle::LinkColorOnly); + } + + bool linkAnyStyles() + { + return patternEditor.linkStyle != LinkStyle::Disabled || songEditor.linkStyle != LinkStyle::Disabled; + } + + autoTrackLinkSettings() + { + enabled = false; + autoDelete = true; + linkMode = LinkMode::OneToOne; + autoSort = AutoSort::Disabled; + } + + private: + int getIntInRangeOrDefault(const QString & text, int lower, int upper, int defaultValue) + { + if (text == nullptr || text.isEmpty()) return defaultValue; + auto asInt = text.toInt(); + if (asInt < lower || asInt >upper) return defaultValue; + return asInt; + } + }; + + enum class ProcessSortOrder + { + SongPattern, + PatternSong + }; + Mixer(); ~Mixer() override; @@ -195,6 +323,22 @@ 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 mixer channel assigned + void processAssignedTracks(std::function process, ProcessSortOrder sortOrder = ProcessSortOrder::SongPattern); + // process tracks assigned to a specific channel + void processChannelTracks(MixerChannel * channel, std::function process); + IntModel * getChannelModelByTrack(Track * track); + MixerChannel * getCustomChannelByTrack(Track * track); + std::vector getUsedChannelCounts(); + bool isAutoTrackLinkToggleAllowed(int index); + //bool autoLinkTrackConfigEnabled(); + + autoTrackLinkSettings getAutoLinkTrackSettings(); + void saveAutoLinkTrackSettings(autoTrackLinkSettings settings); // reset a channel's name, fx, sends, etc void clearChannel(mix_ch_t channelIndex); @@ -214,6 +358,16 @@ class LMMS_EXPORT Mixer : public Model, public JournallingObject MixerRouteVector m_mixerRoutes; private: + inline const QString & getAutoTrackCfg(ConfigManager * cfg, const QString & postFix) + { + return cfg->value("AutoTrackLink", "settings_"+postFix); + } + + inline void setAutoTrackCfg(ConfigManager * cfg, const QString & postFix, int value) + { + cfg->setValue("AutoTrackLink", "settings_"+postFix, QString::number(value)); + } + // the mixer channels in the mixer. index 0 is always master. QVector m_mixerChannels; diff --git a/include/MixerLine.h b/include/MixerLine.h index decd5f3aa0c..3cce45276b9 100644 --- a/include/MixerLine.h +++ b/include/MixerLine.h @@ -82,6 +82,7 @@ class MixerLine : public QWidget bool eventFilter (QObject *dist, QEvent *event) override; + private: void drawMixerLine( QPainter* p, const MixerLine *mixerLine, bool isActive, bool sendToThis, bool receiveFromThis ); QString elideName( const QString & name ); @@ -89,6 +90,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; @@ -105,6 +107,7 @@ public slots: void resetColor(); void selectColor(); void randomizeColor(); + void refreshAutoTrackLinkStyle(); private slots: void renameFinished(); @@ -112,6 +115,7 @@ private slots: void removeUnusedChannels(); void moveChannelLeft(); void moveChannelRight(); + void toogleAutoTrackLink(); }; diff --git a/include/MixerView.h b/include/MixerView.h index a7b1b51919d..a634d039b0f 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -25,17 +25,20 @@ #ifndef MIXER_VIEW_H #define MIXER_VIEW_H +#include #include #include #include #include #include "ModelView.h" +#include "Mixer.h" #include "Engine.h" #include "Fader.h" #include "PixmapButton.h" #include "embed.h" #include "EffectRackView.h" +#include "Track.h" class QButtonGroup; @@ -101,7 +104,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 +112,16 @@ class LMMS_EXPORT MixerView : public QWidget, public ModelView, // useful for loading projects void refreshDisplay(); + // Auto track link support + void updateAfterTrackAdd(Track * track, QString name = ""); + void updateAfterTrackStyleModify(Track * track); + void trackMixerLineCreate(Track * track); + void trackMixerLineAssign(Track * track, int channelIndex); + void updateAfterTrackMove(Track * track); + void updateBeforeTrackDelete(Track * track); + void toggleAutoTrackLink(int index); + + public slots: int addNewChannel(); @@ -119,6 +131,8 @@ public slots: private slots: void updateFaders(); void toggledSolo(); + void updateAutoTrackLinkMenu(); + void toogleAutoLinkTrackConfig(); private: QVector m_mixerChannelViews; @@ -130,9 +144,22 @@ private slots: QWidget * m_channelAreaWidget; QStackedLayout * m_racksLayout; QWidget * m_racksWidget; + QPushButton * m_toogleAutoLinkTrackConfigBtn; + QPushButton * m_autoLinkTrackSettingsBtn; void updateMaxChannelSelector(); - + void swapChannels(int indexA, int indexB); + void updateAutoTrackSortOrder(bool autoSort = true); + void deleteChannelInternal(int index); + void setAutoLinkTrackConfig(bool enabled); + void updateAutoLinkTrackConfigBtn(bool enabled); + void setAutoTrackConstraints(); + void trackStyleToChannel(Track * track, MixerChannel * channel); + void channelStyleToTrack(MixerChannel * channel, Track * track); + void addAutoLinkTrackMenuEntry(QMenu* menu, const QString &text, bool state, + std::function setValue, + std::function after = nullptr); + friend class MixerChannelView; } ; diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 96d4fc8b2df..072bebac70a 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -1,4 +1,4 @@ -/* +/* * Mixer.cpp - effect mixer for LMMS * * Copyright (c) 2008-2011 Tobias Doerffel @@ -31,6 +31,7 @@ #include "MixHelpers.h" #include "Song.h" +#include "ConfigManager.h" #include "InstrumentTrack.h" #include "PatternStore.h" #include "SampleTrack.h" @@ -72,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) { @@ -284,46 +286,23 @@ 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 ) + processAssignedTracks( [index](Track *, IntModel * model, MixerChannel * ) { - if( t->type() == Track::InstrumentTrack ) + int curIndex = model->value(0); + if( curIndex == 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 mixer send + // send to master + model->setValue(0); } - else if( t->type() == Track::SampleTrack ) + else if ( curIndex > 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 + model->setValue(curIndex-1); } - } + }); + MixerChannel * ch = m_mixerChannels[index]; @@ -367,74 +346,167 @@ void Mixer::deleteChannel( int index ) Engine::audioEngine()->doneChangeInModel(); } +void Mixer::toggleAutoTrackLink(int index) +{ + m_mixerChannels[index]->m_autoTrackLinkModel.setValue(! m_mixerChannels[index]->m_autoTrackLinkModel.value()); +} -void Mixer::moveChannelLeft( int index ) +IntModel * Mixer::getChannelModelByTrack(Track * track) +{ + if( track->type() == Track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) track; + return inst->mixerChannelModel(); + } + else if( track->type() == Track::SampleTrack ) + { + SampleTrack * strk = (SampleTrack *) track; + return strk->mixerChannelModel(); + } + return nullptr; +} + +MixerChannel * Mixer::getCustomChannelByTrack(Track * track) { - // can't move master or first channel - if( index <= 1 || index >= m_mixerChannels.size() ) + IntModel * model = getChannelModelByTrack(track); + if (model != nullptr) + { + int channelIndex = model->value(); + if (channelIndex>0) + { + return mixerChannel(channelIndex); + } + } + return nullptr; +} + +void Mixer::processChannelTracks(MixerChannel * channel, std::function process) +{ + processAssignedTracks([channel, process](Track * track, IntModel *, MixerChannel * currentChannel) + { + if (currentChannel != nullptr && currentChannel->m_channelIndex == channel->m_channelIndex) + { + process(track); + } + }); +} + + +bool Mixer::isAutoTrackLinkToggleAllowed(int index) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled) return false; + if (mixerChannel( index )->m_autoTrackLinkModel.value()) return true; + std::vector usedChannelCounts = getUsedChannelCounts(); + return settings.linkMode == Mixer::autoTrackLinkSettings::LinkMode::OneToOne ? + usedChannelCounts[index] == 1 : + usedChannelCounts[index] >= 1; + return true; +} + +/* +bool Mixer::autoLinkTrackConfigEnabled() +{ + auto settings =mix->getAutoLinkTrackSettings(); +} +*/ + + + +void Mixer::processAssignedTracks(std::function process, + ProcessSortOrder sortOrder) +{ + TrackContainer::TrackList trackList; + if (sortOrder == ProcessSortOrder::SongPattern) + { + trackList += Engine::getSong()->tracks(); + trackList += Engine::patternStore()->tracks(); + } + else + { + trackList += Engine::patternStore()->tracks(); + trackList += Engine::getSong()->tracks(); + } + + for (Track* track: trackList) + { + IntModel * model = getChannelModelByTrack(track); + MixerChannel * channel = nullptr; + if (model != nullptr) + { + int channelIndex = model->value(); + if (channelIndex > 0 && channelIndex < m_mixerChannels.size() ) + { + channel = mixerChannel(channelIndex); + } + process(track,model, channel); + } + } +} + +std::vector Mixer::getUsedChannelCounts() +{ + std::vector used(m_mixerChannels.size(), 0); + processAssignedTracks([&used](Track *, IntModel * model, MixerChannel *) + mutable { + ++used[model->value()]; + }); + + return used; +} + + +void Mixer::swapChannels(int indexA, int indexB) +{ + // range check - can't move master or first channel + if (( indexA == indexB ) || + ( indexA < 1 || indexA >= m_mixerChannels.size() ) || + ( indexB < 1 || indexB >= m_mixerChannels.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) + processAssignedTracks( [a,b](Track *, IntModel * model, MixerChannel *) { - TrackContainer::TrackList trackList = trackLists[tl]; - for(int i=0; ivalue(0); + if( curIndex == 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); - } - } + model->setValue(b); } - } + else if ( curIndex == b ) + { + model->setValue(a); + } + }); // Swap positions in array - qSwap(m_mixerChannels[index], m_mixerChannels[index - 1]); + qSwap(m_mixerChannels[a], m_mixerChannels[b]); // Update m_channelIndex of both channels - m_mixerChannels[index]->m_channelIndex = index; - m_mixerChannels[index - 1]->m_channelIndex = index -1; + m_mixerChannels[a]->m_channelIndex = a; + m_mixerChannels[b]->m_channelIndex = b; } +void Mixer::moveChannelLeft( int index ) +{ + swapChannels(index - 1, index); +} + void Mixer::moveChannelRight( int index ) { - moveChannelLeft( index + 1 ); + swapChannels(index, index + 1 ); } @@ -725,6 +797,36 @@ void Mixer::clearChannel(mix_ch_t index) } } +Mixer::autoTrackLinkSettings Mixer::getAutoLinkTrackSettings() +{ + autoTrackLinkSettings settings = autoTrackLinkSettings(); + auto cfg = ConfigManager::inst(); + settings.enabled = settings.getAsBoolOrDefault(Mixer::getAutoTrackCfg(cfg,""), settings.enabled); + settings.autoDelete = settings.getAsBoolOrDefault(Mixer::getAutoTrackCfg(cfg,"autoDelete"), settings.autoDelete); + settings.autoSort = settings.getAsAutoSortOrDefault(Mixer::getAutoTrackCfg(cfg,"autoSort"), settings.autoSort); + settings.linkMode = settings.getAsLinkModeOrDefault(Mixer::getAutoTrackCfg(cfg,"linkMode"), settings.linkMode); + settings.patternEditor.autoAdd = settings.getAsAutoAddOrDefault(Mixer::getAutoTrackCfg(cfg,"pe_autoAdd"), settings.patternEditor.autoAdd); + settings.patternEditor.linkStyle = settings.getAsLinkStyleOrDefault(Mixer::getAutoTrackCfg(cfg,"pe_linkStyle"), settings.patternEditor.linkStyle); + settings.songEditor.autoAdd = settings.getAsAutoAddOrDefault(Mixer::getAutoTrackCfg(cfg,"se_autoAdd"), settings.songEditor.autoAdd); + settings.songEditor.linkStyle = settings.getAsLinkStyleOrDefault(Mixer::getAutoTrackCfg(cfg,"se_linkStyle"), settings.songEditor.linkStyle); + return settings; +} + +void Mixer::saveAutoLinkTrackSettings(autoTrackLinkSettings settings) +{ + auto cfg = ConfigManager::inst(); + Mixer::setAutoTrackCfg(cfg,"", settings.enabled); + Mixer::setAutoTrackCfg(cfg,"autoDelete", settings.autoDelete); + Mixer::setAutoTrackCfg(cfg,"autoSort", (int)settings.autoSort); + Mixer::setAutoTrackCfg(cfg,"linkMode", (int)settings.linkMode); + Mixer::setAutoTrackCfg(cfg,"pe_autoAdd", (int)settings.patternEditor.autoAdd); + Mixer::setAutoTrackCfg(cfg,"pe_linkStyle", (int)settings.patternEditor.linkStyle); + Mixer::setAutoTrackCfg(cfg,"se_autoAdd", (int)settings.songEditor.autoAdd); + Mixer::setAutoTrackCfg(cfg,"se_linkStyle", (int)settings.songEditor.linkStyle); + cfg->saveConfigFile(); +} + + void Mixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) { // save channels @@ -739,6 +841,7 @@ void Mixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) ch->m_volumeModel.saveSettings( _doc, mixch, "volume" ); ch->m_muteModel.saveSettings( _doc, mixch, "muted" ); ch->m_soloModel.saveSettings( _doc, mixch, "soloed" ); + ch->m_autoTrackLinkModel.saveSettings( _doc, mixch, "autoTrackLink" ); mixch.setAttribute( "num", i ); mixch.setAttribute( "name", ch->m_name ); if( ch->m_hasColor ) mixch.setAttribute( "color", ch->m_color.name() ); @@ -784,8 +887,11 @@ void Mixer::loadSettings( const QDomElement & _this ) 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_autoTrackLinkModel.loadSettings( mixch, "autoTrackLink" ); + m_mixerChannels[num]->m_name = mixch.attribute( "name" ); if( mixch.hasAttribute( "color" ) ) { diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 029683f9449..f97591d1f47 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 ); + gui::getGUI()->mixerView()->updateAfterTrackAdd(track); } - void Song::addAutomationTrack() { ( void )Track::create( Track::AutomationTrack, this ); @@ -873,17 +873,17 @@ void Song::clearProject() Engine::audioEngine()->requestChangeInModel(); - if( getGUI() != nullptr && getGUI()->patternEditor() ) + if( getGUI() != nullptr && getGUI()->patternEditor() ) { getGUI()->patternEditor()->m_editor->clearAllTracks(); } - if( getGUI() != nullptr && getGUI()->songEditor() ) + if( getGUI() != nullptr && getGUI()->songEditor() ) { - getGUI()->songEditor()->m_editor->clearAllTracks(); + getGUI()->songEditor()->m_editor->clearAllTracks(); } - if( getGUI() != nullptr && getGUI()->mixerView() ) + if( getGUI() != nullptr && getGUI()->mixerView() ) { - getGUI()->mixerView()->clear(); + getGUI()->mixerView()->clear(); } QCoreApplication::sendPostedEvents(); Engine::patternStore()->clearAllTracks(); diff --git a/src/gui/MixerLine.cpp b/src/gui/MixerLine.cpp index 900298496f6..38fc7190e80 100644 --- a/src/gui/MixerLine.cpp +++ b/src/gui/MixerLine.cpp @@ -134,13 +134,12 @@ MixerLine::MixerLine( QWidget * _parent, MixerView * _mv, int _channelIndex ) : proxyWidget->setRotation( -90 ); proxyWidget->setPos( 8, 145 ); - connect( m_renameLineEdit, SIGNAL(editingFinished()), this, SLOT(renameFinished())); - connect( &Engine::mixer()->mixerChannel( m_channelIndex )->m_muteModel, SIGNAL(dataChanged()), this, SLOT(update())); + connect( m_renameLineEdit, SIGNAL( editingFinished() ), this, SLOT( renameFinished() ) ); + connect( &Engine::mixer()->mixerChannel( m_channelIndex )->m_muteModel, SIGNAL( dataChanged() ), this, SLOT( update() ) ); + connect( &Engine::mixer()->mixerChannel( m_channelIndex )->m_autoTrackLinkModel, SIGNAL( dataChanged() ), this, SLOT( refreshAutoTrackLinkStyle() ) ); } - - MixerLine::~MixerLine() { delete m_sendKnob; @@ -156,6 +155,7 @@ void MixerLine::setChannelIndex( int index ) m_channelIndex = index; m_lcd->setValue( m_channelIndex ); m_lcd->update(); + refreshAutoTrackLinkStyle(); } @@ -170,7 +170,6 @@ void MixerLine::drawMixerLine( QPainter* p, const MixerLine *mixerLine, bool isA { m_renameLineEdit->setText( elidedName ); } - int width = mixerLine->rect().width(); int height = mixerLine->rect().height(); @@ -183,6 +182,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,13 +248,28 @@ void MixerLine::mouseDoubleClickEvent( QMouseEvent * ) void MixerLine::contextMenuEvent( QContextMenuEvent * ) { - QPointer contextMenu = new CaptionMenu( Engine::mixer()->mixerChannel( m_channelIndex )->m_name, this ); + Mixer * mix = Engine::mixer(); + CaptionMenu* contextMenu = new CaptionMenu( mix->mixerChannel( m_channelIndex )->m_name, this ); + bool autoTrackLink = mix->mixerChannel( 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())); + QAction * actionMoveleft =contextMenu->addAction( tr( "Move &left" ), this, SLOT( moveChannelLeft() ) ); + QAction * actionMoveRight =contextMenu->addAction( tr( "Move &right" ), this, SLOT( moveChannelRight() ) ); + + auto settings =mix->getAutoLinkTrackSettings(); + if (settings.enabled) + { + // we don't show the menu entry if the auto link functionality is completely disabled + QString marker = (autoTrackLink ? " *" : ""); + QAction * actionToggleAutoTrackLink = contextMenu->addAction( tr("Auto track link") + marker, this, [this](){ toogleAutoTrackLink(); } ); + actionToggleAutoTrackLink->setEnabled(mix->isAutoTrackLinkToggleAllowed(m_channelIndex)); + } + + actionMoveleft->setEnabled(m_channelIndex > 1); + actionMoveRight->setEnabled((m_channelIndex +1) < mix->numChannels()); } - contextMenu->addAction( tr( "Rename &channel" ), this, SLOT(renameChannel())); + + contextMenu->addAction( tr( "Rename &channel" ), this, SLOT( renameChannel() ) ); contextMenu->addSeparator(); if( m_channelIndex != 0 ) // no remove-option in master @@ -276,7 +291,28 @@ void MixerLine::contextMenuEvent( QContextMenuEvent * ) delete contextMenu; } +void MixerLine::toogleAutoTrackLink() +{ + MixerView * mixView = getGUI()->mixerView(); + mixView->toggleAutoTrackLink(m_channelIndex); +} +void MixerLine::refreshAutoTrackLinkStyle() +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + auto channel = mix->mixerChannel( m_channelIndex ); + if (settings.enabled && channel->m_autoTrackLinkModel.value()) + { + m_renameEditPalette.setColor(QPalette::Text,Qt::green); + } + else + { + m_renameEditPalette.setColor(QPalette::Text,Qt::white); + } + m_renameLineEdit->setPalette(m_renameEditPalette); + update(); +} void MixerLine::renameChannel() @@ -306,8 +342,18 @@ void MixerLine::renameFinished() setFocus(); if( !newName.isEmpty() && Engine::mixer()->mixerChannel( m_channelIndex )->m_name != newName ) { - Engine::mixer()->mixerChannel( m_channelIndex )->m_name = newName; + auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); + channel->m_name = newName; m_renameLineEdit->setText( elideName( newName ) ); + + if (channel->m_autoTrackLinkModel.value()) + { + Engine::mixer()->processChannelTracks(channel, [newName](Track * track) + { + track->setName(newName); + }); + } + Engine::getSong()->setModified(); } QString name = Engine::mixer()->mixerChannel( m_channelIndex )->m_name; @@ -319,8 +365,8 @@ void MixerLine::renameFinished() void MixerLine::removeChannel() { - MixerView * mix = getGUI()->mixerView(); - mix->deleteChannel( m_channelIndex ); + MixerView * mixView = getGUI()->mixerView(); + mixView->deleteChannel( m_channelIndex ); } @@ -328,8 +374,8 @@ void MixerLine::removeChannel() void MixerLine::removeUnusedChannels() { - MixerView * mix = getGUI()->mixerView(); - mix->deleteUnusedChannels(); + MixerView * mixView = getGUI()->mixerView(); + mixView->deleteUnusedChannels(); } @@ -337,8 +383,8 @@ void MixerLine::removeUnusedChannels() void MixerLine::moveChannelLeft() { - MixerView * mix = getGUI()->mixerView(); - mix->moveChannelLeft( m_channelIndex ); + MixerView * mixView = getGUI()->mixerView(); + mixView->moveChannelLeft( m_channelIndex ); } @@ -346,8 +392,8 @@ void MixerLine::moveChannelLeft() void MixerLine::moveChannelRight() { - MixerView * mix = getGUI()->mixerView(); - mix->moveChannelRight( m_channelIndex ); + MixerView * mixView = getGUI()->mixerView(); + mixView->moveChannelRight( m_channelIndex ); } @@ -435,9 +481,18 @@ void MixerLine::setStrokeInnerInactive( const QColor & c ) void MixerLine::selectColor() { auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); - auto new_color = ColorChooser(this).withPalette(ColorChooser::Palette::Mixer)->getColor(channel->m_color); - if(!new_color.isValid()) { return; } - channel->setColor (new_color); + auto newColor = ColorChooser(this).withPalette(ColorChooser::Palette::Mixer)->getColor(channel->m_color); + if(!newColor.isValid()) { return; } + channel->setColor (newColor); + + if (channel->m_autoTrackLinkModel.value()) + { + Engine::mixer()->processChannelTracks(channel, [newColor](Track * track) + { + track->setColor(newColor); + }); + } + Engine::getSong()->setModified(); update(); } @@ -446,7 +501,17 @@ void MixerLine::selectColor() // Disable the usage of color on this mixer line void MixerLine::resetColor() { - Engine::mixer()->mixerChannel( m_channelIndex )->m_hasColor = false; + auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); + channel->m_hasColor = false; + + if (channel->m_autoTrackLinkModel.value()) + { + Engine::mixer()->processChannelTracks(channel, [](Track * track) + { + track->resetColor(); + }); + } + Engine::getSong()->setModified(); update(); } @@ -456,7 +521,17 @@ void MixerLine::resetColor() void MixerLine::randomizeColor() { auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); - channel->setColor (ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]); + auto newColor = ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]; + channel->setColor (newColor); + + if (channel->m_autoTrackLinkModel.value()) + { + Engine::mixer()->processChannelTracks(channel, [newColor](Track * track) + { + track->setColor(newColor); + }); + } + Engine::getSong()->setModified(); update(); } diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 32355edd628..22281071691 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -22,7 +22,7 @@ * */ - +#include #include #include #include @@ -45,6 +45,7 @@ #include "Song.h" #include "SubWindow.h" #include "TrackContainer.h" // For TrackContainer::TrackList typedef +#include "gui_templates.h" namespace lmms::gui { @@ -130,12 +131,42 @@ MixerView::MixerView() : style()->pixelMetric( QStyle::PM_ScrollBarExtent ) ); ml->addWidget( channelArea, 1, Qt::AlignTop ); + QWidget * buttonAreaWidget = new QWidget; + buttonAreaWidget->setFixedSize(mixerLineSize); + QVBoxLayout * bl = new QVBoxLayout( buttonAreaWidget ); + bl->setSizeConstraint( QLayout::SetMinimumSize ); + bl->setSpacing( 0 ); + bl->setMargin( 0 ); + ml->addWidget(buttonAreaWidget); + // show the add new mixer channel button QPushButton * newChannelBtn = new QPushButton( embed::getIconPixmap( "new_channel" ), QString(), this ); newChannelBtn->setObjectName( "newChannelBtn" ); - newChannelBtn->setFixedSize( mixerLineSize ); connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); - ml->addWidget( newChannelBtn, 0, Qt::AlignTop ); + //ml->addWidget( newChannelBtn, 0, Qt::AlignTop ); + + QMenu * toMenu = new QMenu(this ); + toMenu->setFont( pointSize<9>( toMenu->font() ) ); + connect( toMenu, SIGNAL( aboutToShow() ), this, SLOT( updateAutoTrackLinkMenu() ) ); + + m_autoLinkTrackSettingsBtn = new QPushButton(embed::getIconPixmap( "trackop" ),QString(), this); + m_autoLinkTrackSettingsBtn->move( 12, 1 ); + m_autoLinkTrackSettingsBtn->setFocusPolicy( Qt::NoFocus ); + m_autoLinkTrackSettingsBtn->setMenu( toMenu ); + m_autoLinkTrackSettingsBtn->setToolTip(tr("Auto track link settings")); + + m_toogleAutoLinkTrackConfigBtn = new QPushButton(this); + m_toogleAutoLinkTrackConfigBtn->setToolTip(tr("Enable/Disable auto track linking")); + connect( m_toogleAutoLinkTrackConfigBtn, SIGNAL(clicked()), this, SLOT(toogleAutoLinkTrackConfig())); + updateAutoLinkTrackConfigBtn(Engine::mixer()->getAutoLinkTrackSettings().enabled); + + m_autoLinkTrackSettingsBtn->setFixedSize(mixerLineSize.width(), 34); + m_toogleAutoLinkTrackConfigBtn->setFixedSize(mixerLineSize.width(), 34); + newChannelBtn->setFixedSize(mixerLineSize.width(), mixerLineSize.height()-34*2); + + bl->addWidget(newChannelBtn, 0, Qt::AlignTop ); + bl->addWidget(m_autoLinkTrackSettingsBtn, 0, Qt::AlignTop ); + bl->addWidget(m_toogleAutoLinkTrackConfigBtn, 0, Qt::AlignTop ); // add the stacked layout for the effect racks of mixer channels @@ -164,6 +195,8 @@ MixerView::MixerView() : // we want to receive dataChanged-signals in order to update setModel( m ); + + setAutoTrackConstraints(); } MixerView::~MixerView() @@ -174,7 +207,371 @@ MixerView::~MixerView() } } +void MixerView::addAutoLinkTrackMenuEntry(QMenu* menu, + const QString &text, bool state, std::function setValue, std::function after) +{ + menu->addAction( text + + (state ? " *" :""), + this, [setValue, after]() + { + auto mix = Engine::mixer(); + auto settings = mix->getAutoLinkTrackSettings(); + setValue(&settings); + mix->saveAutoLinkTrackSettings(settings); + if (after != nullptr) after(); + }); +} + + +void MixerView::updateAutoTrackLinkMenu() +{ + QMenu* toMenu = m_autoLinkTrackSettingsBtn->menu(); + toMenu->clear(); + + QAction * captionMain = toMenu->addAction( "Auto track linking" ); + captionMain->setEnabled( false ); + QMenu* sortMenu = toMenu->addMenu(tr("Sort linked channels")); + + QMenu* settingsMenu = toMenu->addMenu(tr("Settings")); + QMenu* linkModeMenu = settingsMenu->addMenu(tr("Link mode")); + QMenu* linkStyleMenu = settingsMenu->addMenu(tr("Link style type")); + QMenu* sortAutoMenu = settingsMenu->addMenu(tr("Automatic sorting")); + QMenu* addAutoMenu = settingsMenu->addMenu(tr("Automatic add")); + QMenu* deleteAutoMenu = settingsMenu->addMenu(tr("Automatic delete")); + //QMenu* patternEditorMenu = settingsMenu->addMenu(tr("Pattern Editor")); + + auto mix = Engine::mixer(); + // remark: capturing "settings" (also as pointer) does not work as expected + auto settings = mix->getAutoLinkTrackSettings(); + + // immediate sort action menu + sortMenu->addAction( tr("Song Editor -> Pattern Editor"), this, [this]() + { + updateAutoTrackSortOrder(false); + }); + sortMenu->addAction( tr("Pattern Editor -> Song Editor"), this, [this]() + { + updateAutoTrackSortOrder(false); + }); + + // settings - link mode menu + addAutoLinkTrackMenuEntry(linkModeMenu, tr("One track to one channel"), + settings.linkMode == Mixer::autoTrackLinkSettings::LinkMode::OneToOne, + [](Mixer::autoTrackLinkSettings* s) { s->linkMode = Mixer::autoTrackLinkSettings::LinkMode::OneToOne;}, + [this]() { setAutoTrackConstraints(); }); + addAutoLinkTrackMenuEntry(linkModeMenu, tr("Multiple tracks to one channel"), + settings.linkMode == Mixer::autoTrackLinkSettings::LinkMode::OneToMany, + [](Mixer::autoTrackLinkSettings* s) { s->linkMode = Mixer::autoTrackLinkSettings::LinkMode::OneToMany;}, + [this]() { setAutoTrackConstraints(); }); + + // settings - link style menu + QAction * captionSongEditorLinkStyle = linkStyleMenu->addAction( "Song Editor" ); + captionSongEditorLinkStyle->setEnabled( false ); + + addAutoLinkTrackMenuEntry(linkStyleMenu,tr("Name and color"), + settings.songEditor.linkStyle == Mixer::autoTrackLinkSettings::LinkStyle::LinkNameAndColor, + [](Mixer::autoTrackLinkSettings* s) { s->songEditor.linkStyle = Mixer::autoTrackLinkSettings::LinkStyle::LinkNameAndColor;}); + addAutoLinkTrackMenuEntry(linkStyleMenu,tr("Color only"), + settings.songEditor.linkStyle == Mixer::autoTrackLinkSettings::LinkStyle::LinkColorOnly, + [](Mixer::autoTrackLinkSettings* s) { s->songEditor.linkStyle = Mixer::autoTrackLinkSettings::LinkStyle::LinkColorOnly;}); + addAutoLinkTrackMenuEntry(linkStyleMenu,tr("Disabled"), + settings.songEditor.linkStyle == Mixer::autoTrackLinkSettings::LinkStyle::Disabled, + [](Mixer::autoTrackLinkSettings* s) { s->songEditor.linkStyle = Mixer::autoTrackLinkSettings::LinkStyle::Disabled;}); + + QAction * captionPatternEditorLinkStyle = linkStyleMenu->addAction( "Pattern Editor" ); + captionPatternEditorLinkStyle->setEnabled( false ); + + addAutoLinkTrackMenuEntry(linkStyleMenu,tr("Name and color"), + settings.patternEditor.linkStyle == Mixer::autoTrackLinkSettings::LinkStyle::LinkNameAndColor, + [](Mixer::autoTrackLinkSettings* s) { s->patternEditor.linkStyle = Mixer::autoTrackLinkSettings::LinkStyle::LinkNameAndColor;}); + addAutoLinkTrackMenuEntry(linkStyleMenu,tr("Color only"), + settings.patternEditor.linkStyle == Mixer::autoTrackLinkSettings::LinkStyle::LinkColorOnly, + [](Mixer::autoTrackLinkSettings* s) { s->patternEditor.linkStyle = Mixer::autoTrackLinkSettings::LinkStyle::LinkColorOnly;}); + addAutoLinkTrackMenuEntry(linkStyleMenu,tr("Disabled"), + settings.patternEditor.linkStyle == Mixer::autoTrackLinkSettings::LinkStyle::Disabled, + [](Mixer::autoTrackLinkSettings* s) { s->patternEditor.linkStyle = Mixer::autoTrackLinkSettings::LinkStyle::Disabled;}); + + // settings - automatic sort menu + addAutoLinkTrackMenuEntry(sortAutoMenu, tr("Song Editor -> Pattern Editor"), + settings.autoSort == Mixer::autoTrackLinkSettings::AutoSort::LinkedPattern, + [](Mixer::autoTrackLinkSettings* s) { s->autoSort = Mixer::autoTrackLinkSettings::AutoSort::LinkedPattern;}, + [this]() { setAutoTrackConstraints(); }); + addAutoLinkTrackMenuEntry(sortAutoMenu, tr("Pattern Editor - > Song Editor"), + settings.autoSort == Mixer::autoTrackLinkSettings::AutoSort::PatternLinked, + [](Mixer::autoTrackLinkSettings* s) { s->autoSort = Mixer::autoTrackLinkSettings::AutoSort::PatternLinked;}, + [this]() { setAutoTrackConstraints(); }); + addAutoLinkTrackMenuEntry(sortAutoMenu,tr("Disabled"), + settings.autoSort == Mixer::autoTrackLinkSettings::AutoSort::Disabled, + [](Mixer::autoTrackLinkSettings* s) { s->autoSort = Mixer::autoTrackLinkSettings::AutoSort::Disabled;}); + + // settings - automatic add menu + QAction * captionSongEditorAddAuto = addAutoMenu->addAction( "Song Editor" ); + captionSongEditorAddAuto->setEnabled( false ); + + addAutoLinkTrackMenuEntry(addAutoMenu,tr("Create new channel") , + settings.songEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::CreateNew, + [](Mixer::autoTrackLinkSettings* s) { s->songEditor.autoAdd = Mixer::autoTrackLinkSettings::AutoAdd::CreateNew;}); + addAutoLinkTrackMenuEntry(addAutoMenu,tr("Disabled"), + settings.songEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::Disabled, + [](Mixer::autoTrackLinkSettings* s) { s->songEditor.autoAdd = Mixer::autoTrackLinkSettings::AutoAdd::Disabled;}); + + QAction * captionPatternEditorAddAuto = addAutoMenu->addAction( "Pattern Editor" ); + captionPatternEditorAddAuto->setEnabled( false ); + + addAutoLinkTrackMenuEntry(addAutoMenu,tr("Create new channel") , + settings.patternEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::CreateNew, + [](Mixer::autoTrackLinkSettings* s) { s->patternEditor.autoAdd = Mixer::autoTrackLinkSettings::AutoAdd::CreateNew;}); + addAutoLinkTrackMenuEntry(addAutoMenu,tr("Use first track's channel") , + settings.patternEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::UseFirstTrackOnly, + [](Mixer::autoTrackLinkSettings* s) { s->patternEditor.autoAdd = Mixer::autoTrackLinkSettings::AutoAdd::UseFirstTrackOnly;}); + addAutoLinkTrackMenuEntry(addAutoMenu,tr("Disabled"), + settings.patternEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::Disabled, + [](Mixer::autoTrackLinkSettings* s) { s->patternEditor.autoAdd = Mixer::autoTrackLinkSettings::AutoAdd::Disabled;}); + + // settings - delete menu + addAutoLinkTrackMenuEntry(deleteAutoMenu,tr("Enabled") , + settings.autoDelete == true, + [](Mixer::autoTrackLinkSettings* s) { s->autoDelete =true;}); + addAutoLinkTrackMenuEntry(deleteAutoMenu,tr("Disabled") , + settings.autoDelete == false, + [](Mixer::autoTrackLinkSettings* s) { s->autoDelete =false;}); + +} + +void MixerView::updateAfterTrackAdd(Track * track, QString name) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled || !settings.autoAdd()) return; + IntModel * model = mix->getChannelModelByTrack(track); + if ( model != nullptr) + { + auto isPatternEditor = track->trackContainer()->type() == TrackContainer::TrackContainerTypes::PatternContainer; + if (isPatternEditor) + { + if (settings.patternEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::Disabled) return; + + if (settings.patternEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::UseFirstTrackOnly) + { + auto trackList = Engine::patternStore()->tracks(); + if (!trackList.empty()) + { + auto channel = mix->getCustomChannelByTrack(trackList.first()); + if (channel != nullptr) + { + model->setValue(channel->m_channelIndex); + } + } + return; + } + } + else + { + if (settings.songEditor.autoAdd == Mixer::autoTrackLinkSettings::AutoAdd::Disabled) return; + } + + // all other cases can only be "add new channel" because "UseFirstTrackOnly" is not supported by song editor + int channelIndex = addNewChannel(); + model->setValue( channelIndex ); + mix->mixerChannel(channelIndex)->m_autoTrackLinkModel.setValue(true); + + // it may be that the track name is not available yet because of async loading + if (name != "") track->setName(name); + updateAfterTrackStyleModify(track); + + setCurrentMixerLine( channelIndex ); + } +} + +void MixerView::trackStyleToChannel(Track * track, MixerChannel * channel) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + auto isPatternEditor = track->trackContainer()->type() == TrackContainer::TrackContainerTypes::PatternContainer; + if (settings.linkName(isPatternEditor)) + { + channel->m_name = track->name(); + } + if (settings.linkColor(isPatternEditor)) + { + channel->setColor (track->color()); + channel->m_hasColor = track->useColor(); + } +} + +void MixerView::channelStyleToTrack(MixerChannel * channel, Track * track) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + auto isPatternEditor = track->trackContainer()->type() == TrackContainer::TrackContainerTypes::PatternContainer; + if (settings.linkName(isPatternEditor)) + { + track->setName(channel->m_name); + } + if (settings.linkColor(isPatternEditor)) + { + track->setColor(channel->m_color); + if (!channel->m_hasColor) track->resetColor(); + } +} + +void MixerView::updateAfterTrackStyleModify(Track * track) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled || !settings.linkAnyStyles()) return; + auto channel = mix->getCustomChannelByTrack(track); + if (channel != nullptr && channel->m_autoTrackLinkModel.value()) + { + trackStyleToChannel(track, channel); + mix->processAssignedTracks([this, channel](Track * otherTrack, IntModel * model, MixerChannel *) + mutable { + if (model->value() == channel->m_channelIndex) + { + channelStyleToTrack(channel, otherTrack); + } + }); + setCurrentMixerLine(channel->m_channelIndex); + } +} + +void MixerView::trackMixerLineAssign(Track * track, int channelIndex) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + + IntModel * model = mix->getChannelModelByTrack(track); + model->setValue( channelIndex ); + + if (settings.enabled) + { + IntModel * model = mix->getChannelModelByTrack(track); + if (model != nullptr) + { + setAutoTrackConstraints(); + } + } + setCurrentMixerLine(channelIndex); +} + +void MixerView::trackMixerLineCreate(Track * track) +{ + int channelIndex = addNewChannel(); + auto channel = Engine::mixer()->mixerChannel(channelIndex); + + channel->m_name = track->name(); + if (track->useColor()) { channel->setColor (track->color()); } + + IntModel * model = Engine::mixer()->getChannelModelByTrack(track); + model->setValue( channelIndex ); + + toggleAutoTrackLink(channelIndex); + + setCurrentMixerLine(channelIndex); +} + + +void MixerView::setAutoTrackConstraints() +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + std::vector usedChannelCounts = mix->getUsedChannelCounts(); + bool wasModified = false; + for(unsigned long i = 0; i < usedChannelCounts.size(); i++) + { + if (mix->mixerChannel(i)->m_autoTrackLinkModel.value()) + { + if (settings.enabled) + { + // no more linked tracks or too many linked tracks + if (usedChannelCounts[i] == 0 || (usedChannelCounts[i] > 1 && + settings.linkMode == Mixer::autoTrackLinkSettings::LinkMode::OneToOne)) + { + mix->mixerChannel(i)->m_autoTrackLinkModel.setValue(false); + wasModified = true; + } + } + // needed when global enabled is switched + m_mixerChannelViews[i]->m_mixerLine->refreshAutoTrackLinkStyle(); + } + } + if (!settings.enabled) return; + + // make sure that the tracks are updated according to the auto link settings + mix->processAssignedTracks([this](Track * track, IntModel *, MixerChannel * channel) + mutable { + if (channel != nullptr) + { + if (channel->m_autoTrackLinkModel.value()) + { + channelStyleToTrack(channel, track); + } + } + }); + if (wasModified) updateAutoTrackSortOrder(); +} + +void MixerView::updateAfterTrackMove(Track * track) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled || settings.autoSort == Mixer::autoTrackLinkSettings::AutoSort::Disabled) return; + auto channel = mix->getCustomChannelByTrack(track); + if (channel != nullptr && channel->m_autoTrackLinkModel.value()) + { + updateAutoTrackSortOrder(); + } +} + +void MixerView::updateBeforeTrackDelete(Track * track) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled) return; + auto channel = mix->getCustomChannelByTrack(track); + if (channel != nullptr && channel->m_autoTrackLinkModel.value()) + { + bool shouldDelete = true; + if (settings.linkMode == Mixer::autoTrackLinkSettings::LinkMode::OneToMany) + { + std::vector usedChannelCounts = mix->getUsedChannelCounts(); + // why "1" - because the track is not yet deleted + shouldDelete = usedChannelCounts[channel->m_channelIndex] == 1; + } + if (shouldDelete) + { + channel->m_autoTrackLinkModel.setValue(false); + if (settings.autoDelete && channel->m_receives.isEmpty()) + { + // we only delete if the setting allows and the channel does not receive another channel + deleteChannel(channel->m_channelIndex); + } + updateAutoTrackSortOrder(); + } + } +} +void MixerView::setAutoLinkTrackConfig(bool enabled) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + updateAutoLinkTrackConfigBtn(enabled); + settings.enabled = enabled; + mix->saveAutoLinkTrackSettings(settings); + setAutoTrackConstraints(); +} + +void MixerView::updateAutoLinkTrackConfigBtn(bool enabled) +{ + m_toogleAutoLinkTrackConfigBtn->setIcon(enabled ? embed::getIconPixmap( "auto_link_active" ) : embed::getIconPixmap( "auto_link_inactive" )); +} + +void MixerView::toogleAutoLinkTrackConfig() +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + setAutoLinkTrackConfig(!settings.enabled); +} int MixerView::addNewChannel() { @@ -189,11 +586,71 @@ int MixerView::addNewChannel() updateMixerLine(newChannelIndex); + updateAutoTrackSortOrder(); updateMaxChannelSelector(); return newChannelIndex; } +void MixerView::updateAutoTrackSortOrder(bool autoSort) +{ + Mixer * mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled) return; + if (autoSort && settings.autoSort == Mixer::autoTrackLinkSettings::AutoSort::Disabled) return; + + std::vector list(m_mixerChannelViews.size(), 0); + + int c = 1; + // add all non auto tracks first + for( int i = 1; imixerChannel(i)->m_autoTrackLinkModel.value()) + { + list[c++] = i; + } + } + + // add auto linked and assigned tracks in song/patter order + auto sortOrder = settings.autoSort == Mixer::autoTrackLinkSettings::AutoSort::LinkedPattern + ? Mixer::ProcessSortOrder::SongPattern + : Mixer::ProcessSortOrder::PatternSong; + mix->processAssignedTracks([&list,&c](Track *, IntModel * model, MixerChannel * channel) + mutable { + if (channel == nullptr) return; + if (channel->m_autoTrackLinkModel.value()) + { + list[c++] = model->value(); + } + }, sortOrder); + + // bubblesort here because: + // - list is normally almost ordered + // - stable sort and + // - "distance" between swapped items is normally small + bool swapped = false; + do + { + swapped = false; + for (int i=0; i list[i+1]) + { + swapChannels(list[i], list[i+1]); + int t = list[i]; + list[i] = list[i+1]; + list[i+1] = t; + swapped = true; + + } + } + c = c-1; + } while (swapped); + + // TODO: think about focus + // setCurrentMixerLine( index - 1 ); +} + void MixerView::refreshDisplay() { @@ -236,32 +693,15 @@ 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) + Mixer * mix = Engine::mixer(); + mix->processAssignedTracks([this](Track *, IntModel * model, MixerChannel *) { - 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); - } - } - } + model->setRange(0,m_mixerChannelViews.size()-1,1); + }); } + void MixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); @@ -360,7 +800,7 @@ void MixerView::updateMixerLine(int index) // does current channel send to this channel? int selIndex = m_currentMixerLine->channelIndex(); MixerLine * thisLine = m_mixerChannelViews[index]->m_mixerLine; - thisLine->setToolTip( Engine::mixer()->mixerChannel( index )->m_name ); + thisLine->setToolTip( mix->mixerChannel( index )->m_name ); FloatModel * sendModel = mix->channelSendModel(selIndex, index); if( sendModel == nullptr ) @@ -378,18 +818,35 @@ void MixerView::updateMixerLine(int index) // disable the send button if it would cause an infinite loop thisLine->m_sendBtn->setVisible(! mix->isInfiniteLoop(selIndex, index)); thisLine->m_sendBtn->updateLightStatus(); + thisLine->refreshAutoTrackLinkStyle(); thisLine->update(); } - void MixerView::deleteChannel(int index) { - // can't delete master if( index == 0 ) return; // remember selected line int selLine = m_currentMixerLine->channelIndex(); + deleteChannelInternal(index); + + // select the next channel + if( selLine >= m_mixerChannelViews.size() ) + { + selLine = m_mixerChannelViews.size()-1; + } + setCurrentMixerLine(selLine); + + updateMaxChannelSelector(); +} + + +void MixerView::deleteChannelInternal(int index) +{ + // can't delete master + if( index == 0 ) return; + // in case the deleted channel is soloed or the remaining // channels will be left in a muted state Engine::mixer()->clearChannel(index); @@ -416,86 +873,90 @@ void MixerView::deleteChannel(int index) m_mixerChannelViews[i]->m_mixerLine->setChannelIndex(i-1); } m_mixerChannelViews.remove(index); - - // select the next channel - if( selLine >= m_mixerChannelViews.size() ) - { - selLine = m_mixerChannelViews.size()-1; - } - setCurrentMixerLine(selLine); - - updateMaxChannelSelector(); } void MixerView::deleteUnusedChannels() { - TrackContainer::TrackList tracks; - tracks += Engine::getSong()->tracks(); - tracks += Engine::patternStore()->tracks(); + Mixer * mix = Engine::mixer(); + std::vector inUse = mix->getUsedChannelCounts(); - std::vector inUse(m_mixerChannelViews.size(), false); + bool needUpdateMax = false; - //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_mixerChannelViews.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) && mix->mixerChannel(i)->m_receives.isEmpty()) { - InstrumentTrack* inst = dynamic_cast(t); - channel = inst->mixerChannelModel()->value(); + deleteChannelInternal(i); + needUpdateMax = true; } - else if (t->type() == Track::SampleTrack) + } + if (needUpdateMax) updateMaxChannelSelector(); +} + + +void MixerView::toggleAutoTrackLink(int index) +{ + auto mix = Engine::mixer(); + auto settings =mix->getAutoLinkTrackSettings(); + if (!settings.enabled) return; + mix->toggleAutoTrackLink(index); + MixerChannel * channel = mix->mixerChannel(index); + if (!channel->m_autoTrackLinkModel.value()) return; + + Track * trackFound = nullptr; + mix->processAssignedTracks([&trackFound, index](Track * track, IntModel * model, MixerChannel *) + mutable { + if (model->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 != nullptr) { - if (!inUse[i] && Engine::mixer()->mixerChannel(i)->m_receives.isEmpty()) - { deleteChannel(i); } + updateAfterTrackStyleModify(trackFound); + updateAutoTrackSortOrder(); } } - -void MixerView::moveChannelLeft(int index, int focusIndex) +void MixerView::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_mixerChannelViews.size() ) || + ( indexB < 1 || indexB >= m_mixerChannelViews.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 ); + m_mixerChannelViews[indexA]->setChannelIndex(indexA); + m_mixerChannelViews[indexB]->setChannelIndex(indexB ); - // Focus on new position - setCurrentMixerLine( focusIndex ); + m_mixerChannelViews[indexA]->m_mixerLine->setChannelIndex(indexA); + m_mixerChannelViews[indexB]->m_mixerLine->setChannelIndex(indexB); } - void MixerView::moveChannelLeft(int index) { - moveChannelLeft( index, index - 1 ); + swapChannels( index, index - 1); + setCurrentMixerLine( index - 1 ); } void MixerView::moveChannelRight(int index) { - moveChannelLeft( index + 1, index + 1 ); + swapChannels( index , index + 1 ); + setCurrentMixerLine( index + 1 ); } diff --git a/src/gui/editors/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index 4c00942dbb6..527e55f5609 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -36,9 +36,9 @@ #include "Song.h" #include "StringPairDrag.h" #include "TrackView.h" - #include "MidiClip.h" - +#include "GuiApplication.h" +#include "MixerView.h" namespace lmms::gui { @@ -87,7 +87,8 @@ void PatternEditor::removeSteps() void PatternEditor::addSampleTrack() { - (void) Track::create( Track::SampleTrack, model() ); + Track * track = Track::create( Track::SampleTrack, model() ); + getGUI()->mixerView()->updateAfterTrackAdd(track); } diff --git a/src/gui/editors/TrackContainerView.cpp b/src/gui/editors/TrackContainerView.cpp index e7db85e4331..1ed0f04cc58 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 "MixerView.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()->mixerView()->updateBeforeTrackDelete(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()->mixerView()->updateAfterTrackMove(track); realignTracks(); } @@ -404,6 +408,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) m_tc ) ); InstrumentLoaderThread *ilt = new InstrumentLoaderThread( this, it, value ); + getGUI()->mixerView()->updateAfterTrackAdd(it, value); ilt->start(); //it->toggledInstrumentTrackButton( true ); _de->accept(); @@ -418,6 +423,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) PluginFactory::PluginInfoAndKey piakn = getPluginFactory()->pluginSupportingExtension(FileItem::extension(value)); Instrument * i = it->loadInstrument(piakn.info.name(), &piakn.key); + getGUI()->mixerView()->updateAfterTrackAdd(it); i->loadFile( value ); //it->toggledInstrumentTrackButton( true ); _de->accept(); @@ -430,6 +436,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) m_tc ) ); it->setSimpleSerializing(); it->loadSettings( dataFile.content().toElement() ); + getGUI()->mixerView()->updateAfterTrackAdd(it); //it->toggledInstrumentTrackButton( true ); _de->accept(); } diff --git a/src/gui/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index 5d01c8ae0d9..7b0a5f2972a 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -221,33 +221,20 @@ InstrumentTrackWindow * InstrumentTrackView::topLevelInstrumentTrackWindow() } - - /*! \brief Create and assign a new mixer Channel for this track */ void InstrumentTrackView::createMixerLine() { - int channelIndex = getGUI()->mixerView()->addNewChannel(); - auto channel = Engine::mixer()->mixerChannel(channelIndex); - - channel->m_name = getTrack()->name(); - if (getTrack()->useColor()) { channel->setColor (getTrack()->color()); } - - assignMixerLine(channelIndex); + getGUI()->mixerView()->trackMixerLineCreate(getTrack()); } - - /*! \brief Assign a specific mixer Channel for this track */ void InstrumentTrackView::assignMixerLine(int channelIndex) { - model()->mixerChannelModel()->setValue( channelIndex ); - - getGUI()->mixerView()->setCurrentMixerLine( channelIndex ); + getGUI()->mixerView()->trackMixerLineAssign(getTrack(), channelIndex); } - InstrumentTrackWindow * InstrumentTrackView::getInstrumentTrackWindow() { if (!m_window) diff --git a/src/gui/tracks/SampleTrackView.cpp b/src/gui/tracks/SampleTrackView.cpp index 83331ad1e49..f67038320b6 100644 --- a/src/gui/tracks/SampleTrackView.cpp +++ b/src/gui/tracks/SampleTrackView.cpp @@ -212,29 +212,17 @@ void SampleTrackView::dropEvent(QDropEvent *de) } - - /*! \brief Create and assign a new mixer Channel for this track */ void SampleTrackView::createMixerLine() { - int channelIndex = getGUI()->mixerView()->addNewChannel(); - auto channel = Engine::mixer()->mixerChannel(channelIndex); - - channel->m_name = getTrack()->name(); - if (getTrack()->useColor()) { channel->setColor (getTrack()->color()); } - - assignMixerLine(channelIndex); + getGUI()->mixerView()->trackMixerLineCreate(getTrack()); } - - /*! \brief Assign a specific mixer Channel for this track */ void SampleTrackView::assignMixerLine(int channelIndex) { - model()->mixerChannelModel()->setValue(channelIndex); - - getGUI()->mixerView()->setCurrentMixerLine(channelIndex); + getGUI()->mixerView()->trackMixerLineAssign(getTrack(), channelIndex); } diff --git a/src/gui/tracks/TrackLabelButton.cpp b/src/gui/tracks/TrackLabelButton.cpp index 4e0b2be358c..5b7580aaadf 100644 --- a/src/gui/tracks/TrackLabelButton.cpp +++ b/src/gui/tracks/TrackLabelButton.cpp @@ -37,6 +37,9 @@ #include "Song.h" #include "TrackRenameLineEdit.h" #include "TrackView.h" +#include "GuiApplication.h" +#include "MixerView.h" +#include "TrackView.h" namespace lmms::gui { @@ -64,7 +67,6 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : m_renameLineEdit->setFixedWidth( width() - 33 ); connect( m_renameLineEdit, SIGNAL(editingFinished()), this, SLOT(renameFinished())); } - setIconSize( QSize( 24, 24 ) ); connect( m_trackView->getTrack(), SIGNAL(dataChanged()), this, SLOT(update())); connect( m_trackView->getTrack(), SIGNAL(nameChanged()), this, SLOT(nameChanged())); @@ -84,7 +86,9 @@ void TrackLabelButton::rename() renameDlg.exec(); if( txt != text() ) { - m_trackView->getTrack()->setName( txt ); + Track * track = m_trackView->getTrack(); + track->setName( txt ); + getGUI()->mixerView()->updateAfterTrackStyleModify(track); Engine::getSong()->setModified(); } } @@ -111,7 +115,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()->mixerView()->updateAfterTrackStyleModify(track); Engine::getSong()->setModified(); } } diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index b3c8ac37946..b0cbc215a9b 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 "MixerView.h" namespace lmms::gui { @@ -275,6 +277,7 @@ void TrackOperationsWidget::selectTrackColor() auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); track->setColor(new_color); + getGUI()->mixerView()->updateAfterTrackStyleModify(track); Engine::getSong()->setModified(); } @@ -283,6 +286,7 @@ void TrackOperationsWidget::resetTrackColor() auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); track->resetColor(); + getGUI()->mixerView()->updateAfterTrackStyleModify(track); Engine::getSong()->setModified(); } @@ -292,6 +296,7 @@ void TrackOperationsWidget::randomizeTrackColor() auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); track->setColor(buffer); + getGUI()->mixerView()->updateAfterTrackStyleModify(track); Engine::getSong()->setModified(); }