diff --git a/CMakeLists.txt b/CMakeLists.txt index 94ecb9ce26b..c10a90cd3ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3480,6 +3480,7 @@ if (STEM) src/sources/soundsourcestem.cpp src/track/steminfoimporter.cpp src/track/steminfo.cpp + src/widget/wtrackstemmenu.cpp src/widget/wstemlabel.cpp ) if(QOPENGL) diff --git a/src/analyzer/constants.h b/src/analyzer/constants.h index 42d84db7217..5e670a37dc0 100644 --- a/src/analyzer/constants.h +++ b/src/analyzer/constants.h @@ -10,7 +10,6 @@ namespace mixxx { // fixed number of channels like the engine does, usually 2 = stereo. constexpr audio::ChannelCount kAnalysisChannels = mixxx::kEngineChannelOutputCount; constexpr audio::ChannelCount kAnalysisMaxChannels = mixxx::kMaxEngineChannelInputCount; -constexpr int kMaxSupportedStems = 4; constexpr SINT kAnalysisFramesPerChunk = 4096; constexpr SINT kAnalysisSamplesPerChunk = kAnalysisFramesPerChunk * kAnalysisMaxChannels; diff --git a/src/engine/cachingreader/cachingreader.cpp b/src/engine/cachingreader/cachingreader.cpp index d2b868fa353..2859dd96aa0 100644 --- a/src/engine/cachingreader/cachingreader.cpp +++ b/src/engine/cachingreader/cachingreader.cpp @@ -205,7 +205,11 @@ CachingReaderChunkForOwner* CachingReader::lookupChunkAndFreshen(SINT chunkIndex } // Invoked from the UI thread!! +#ifdef __STEM__ +void CachingReader::newTrack(TrackPointer pTrack, mixxx::StemChannelSelection stemMask) { +#else void CachingReader::newTrack(TrackPointer pTrack) { +#endif auto newState = pTrack ? STATE_TRACK_LOADING : STATE_TRACK_UNLOADING; auto oldState = m_state.fetchAndStoreAcquire(newState); @@ -220,7 +224,11 @@ void CachingReader::newTrack(TrackPointer pTrack) { kLogger.warning() << "Loading a new track while loading a track may lead to inconsistent states"; } +#ifdef __STEM__ + m_worker.newTrack(std::move(pTrack), stemMask); +#else m_worker.newTrack(std::move(pTrack)); +#endif } // Called from the engine thread diff --git a/src/engine/cachingreader/cachingreader.h b/src/engine/cachingreader/cachingreader.h index 384f12a8047..fc77167b927 100644 --- a/src/engine/cachingreader/cachingreader.h +++ b/src/engine/cachingreader/cachingreader.h @@ -116,7 +116,11 @@ class CachingReader : public QObject { // Request that the CachingReader load a new track. These requests are // processed in the work thread, so the reader must be woken up via wake() // for this to take effect. +#ifdef __STEM__ + void newTrack(TrackPointer pTrack, mixxx::StemChannelSelection stemMask = {}); +#else void newTrack(TrackPointer pTrack); +#endif void setScheduler(EngineWorkerScheduler* pScheduler) { m_worker.setScheduler(pScheduler); diff --git a/src/engine/cachingreader/cachingreaderworker.cpp b/src/engine/cachingreader/cachingreaderworker.cpp index cb4d12488e4..2caaf7af059 100644 --- a/src/engine/cachingreader/cachingreaderworker.cpp +++ b/src/engine/cachingreader/cachingreaderworker.cpp @@ -92,10 +92,20 @@ ReaderStatusUpdate CachingReaderWorker::processReadRequest( } // WARNING: Always called from a different thread (GUI) +#ifdef __STEM__ +void CachingReaderWorker::newTrack(TrackPointer pTrack, mixxx::StemChannelSelection stemMask) { +#else void CachingReaderWorker::newTrack(TrackPointer pTrack) { +#endif { const auto locker = lockMutex(&m_newTrackMutex); +#ifdef __STEM__ + m_pNewTrack = NewTrackRequest{ + pTrack, + stemMask}; +#else m_pNewTrack = pTrack; +#endif m_newTrackAvailable.storeRelease(1); } workReady(); @@ -113,16 +123,25 @@ void CachingReaderWorker::run() { // Request is initialized by reading from FIFO CachingReaderChunkReadRequest request; if (m_newTrackAvailable.loadAcquire()) { +#ifdef __STEM__ + NewTrackRequest pLoadTrack; +#else TrackPointer pLoadTrack; +#endif { // locking scope const auto locker = lockMutex(&m_newTrackMutex); pLoadTrack = m_pNewTrack; - m_pNewTrack.reset(); m_newTrackAvailable.storeRelease(0); } // implicitly unlocks the mutex +#ifdef __STEM__ + if (pLoadTrack.track) { + // in this case the engine is still running with the old track + loadTrack(pLoadTrack.track, pLoadTrack.stemMask); +#else if (pLoadTrack) { // in this case the engine is still running with the old track loadTrack(pLoadTrack); +#endif } else { // here, the engine is already stopped unloadTrack(); @@ -168,7 +187,12 @@ void CachingReaderWorker::unloadTrack() { m_pReaderStatusFIFO->writeBlocking(&update, 1); } +#ifdef __STEM__ +void CachingReaderWorker::loadTrack( + const TrackPointer& pTrack, mixxx::StemChannelSelection stemMask) { +#else void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { +#endif // This emit is directly connected and returns synchronized // after the engine has been stopped. emit trackLoading(); @@ -190,6 +214,9 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { mixxx::AudioSource::OpenParams config; config.setChannelCount(m_maxSupportedChannel); +#ifdef __STEM__ + config.setStemMask(stemMask); +#endif m_pAudioSource = SoundSourceProxy(pTrack).openAudioSource(config); if (!m_pAudioSource) { kLogger.warning() diff --git a/src/engine/cachingreader/cachingreaderworker.h b/src/engine/cachingreader/cachingreaderworker.h index a99bbb150a6..d7ecc2c04bb 100644 --- a/src/engine/cachingreader/cachingreaderworker.h +++ b/src/engine/cachingreader/cachingreaderworker.h @@ -104,7 +104,11 @@ class CachingReaderWorker : public EngineWorker { ~CachingReaderWorker() override = default; // Request to load a new track. wake() must be called afterwards. +#ifdef __STEM__ + void newTrack(TrackPointer pTrack, mixxx::StemChannelSelection stemMask); +#else void newTrack(TrackPointer pTrack); +#endif // Run upkeep operations like loading tracks and reading from file. Run by a // thread pool via the EngineWorkerScheduler. @@ -122,6 +126,12 @@ class CachingReaderWorker : public EngineWorker { void trackLoadFailed(TrackPointer pTrack, const QString& reason); private: +#ifdef __STEM__ + struct NewTrackRequest { + TrackPointer track; + mixxx::StemChannelSelection stemMask; + }; +#endif const QString m_group; QString m_tag; @@ -134,7 +144,11 @@ class CachingReaderWorker : public EngineWorker { // lock to touch. QMutex m_newTrackMutex; QAtomicInt m_newTrackAvailable; +#ifdef __STEM__ + NewTrackRequest m_pNewTrack; +#else TrackPointer m_pNewTrack; +#endif void discardAllPendingRequests(); @@ -147,7 +161,11 @@ class CachingReaderWorker : public EngineWorker { void unloadTrack(); /// Internal method to load a track. Emits trackLoaded when finished. +#ifdef __STEM__ + void loadTrack(const TrackPointer& pTrack, mixxx::StemChannelSelection stemMask); +#else void loadTrack(const TrackPointer& pTrack); +#endif ReaderStatusUpdate processReadRequest( const CachingReaderChunkReadRequest& request); diff --git a/src/engine/channels/enginedeck.cpp b/src/engine/channels/enginedeck.cpp index fd3697a88d6..49a9ba9896f 100644 --- a/src/engine/channels/enginedeck.cpp +++ b/src/engine/channels/enginedeck.cpp @@ -15,8 +15,6 @@ #ifdef __STEM__ namespace { -constexpr int kMaxSupportedStems = 4; - QString getGroupForStem(const QString& deckGroup, int stemIdx) { DEBUG_ASSERT(deckGroup.endsWith("]")); return QStringLiteral("%1Stem%2]") @@ -75,18 +73,18 @@ EngineDeck::EngineDeck( m_pStemCount = std::make_unique(ConfigKey(getGroup(), "stem_count")); m_pStemCount->setReadOnly(); - m_stemGain.reserve(kMaxSupportedStems); - m_stemMute.reserve(kMaxSupportedStems); - for (int stemIdx = 1; stemIdx <= kMaxSupportedStems; stemIdx++) { + m_stemGain.reserve(mixxx::kMaxSupportedStems); + m_stemMute.reserve(mixxx::kMaxSupportedStems); + for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) { m_stemGain.emplace_back(std::make_unique( - ConfigKey(getGroupForStem(getGroup(), stemIdx), QStringLiteral("volume")))); + ConfigKey(getGroupForStem(getGroup(), stemIdx + 1), QStringLiteral("volume")))); // The default value is ignored and override with the medium value by // ControlPotmeter. This is likely a bug but fixing might have a // disruptive impact, so setting the default explicitly m_stemGain.back()->set(1.0); m_stemGain.back()->setDefaultValue(1.0); auto pMuteButton = std::make_unique( - ConfigKey(getGroupForStem(getGroup(), stemIdx), QStringLiteral("mute"))); + ConfigKey(getGroupForStem(getGroup(), stemIdx + 1), QStringLiteral("mute"))); pMuteButton->setButtonMode(mixxx::control::ButtonMode::PowerWindow); m_stemMute.push_back(std::move(pMuteButton)); } @@ -101,7 +99,7 @@ void EngineDeck::slotTrackLoaded(TrackPointer pNewTrack, } if (m_pConfig->getValue( ConfigKey("[Mixer Profile]", "stem_auto_reset"), true)) { - for (int stemIdx = 0; stemIdx < kMaxSupportedStems; stemIdx++) { + for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) { m_stemGain[stemIdx]->set(1.0); m_stemMute[stemIdx]->set(0.0); ; @@ -213,13 +211,13 @@ void EngineDeck::cloneStemState(const EngineDeck* deckToClone) { if (!isPrimaryDeck() || !deckToClone->isPrimaryDeck()) { return; } - VERIFY_OR_DEBUG_ASSERT(m_stemGain.size() == kMaxSupportedStems && - m_stemMute.size() == kMaxSupportedStems && - deckToClone->m_stemGain.size() == kMaxSupportedStems && - deckToClone->m_stemMute.size() == kMaxSupportedStems) { + VERIFY_OR_DEBUG_ASSERT(m_stemGain.size() == mixxx::kMaxSupportedStems && + m_stemMute.size() == mixxx::kMaxSupportedStems && + deckToClone->m_stemGain.size() == mixxx::kMaxSupportedStems && + deckToClone->m_stemMute.size() == mixxx::kMaxSupportedStems) { return; } - for (int stemIdx = 0; stemIdx < kMaxSupportedStems; stemIdx++) { + for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) { m_stemGain[stemIdx]->set(deckToClone->m_stemGain[stemIdx]->get()); m_stemMute[stemIdx]->set(deckToClone->m_stemMute[stemIdx]->get()); } diff --git a/src/engine/engine.h b/src/engine/engine.h index 4df4ab6c270..c5912ba714d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -8,6 +8,22 @@ static constexpr audio::ChannelCount kEngineChannelOutputCount = audio::ChannelCount::stereo(); static constexpr audio::ChannelCount kMaxEngineChannelInputCount = audio::ChannelCount::stem(); +// The following constant is always defined as it used for the waveform data +// struct, which must stay consistent, whether the STEM feature is enabled or +// not. +constexpr int kMaxSupportedStems = 4; +#ifdef __STEM__ +enum class StemChannel { + First = 0x1, + Second = 0x2, + Third = 0x4, + Fourth = 0x8, + + None = 0, + All = First | Second | Third | Fourth +}; +Q_DECLARE_FLAGS(StemChannelSelection, StemChannel); +#endif // Contains the information needed to process a buffer of audio class EngineParameters final { diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index c743b149f3b..a01979966be 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1546,12 +1546,25 @@ void EngineBuffer::hintReader(const double dRate) { } // WARNING: This method runs in the GUI thread -void EngineBuffer::loadTrack(TrackPointer pTrack, bool play, EngineChannel* pChannelToCloneFrom) { +#ifdef __STEM__ +void EngineBuffer::loadTrack(TrackPointer pTrack, + mixxx::StemChannelSelection stemMask, + bool play, + EngineChannel* pChannelToCloneFrom) { +#else +void EngineBuffer::loadTrack(TrackPointer pTrack, + bool play, + EngineChannel* pChannelToCloneFrom) { +#endif if (pTrack) { // Signal to the reader to load the track. The reader will respond with // trackLoading and then either with trackLoaded or trackLoadFailed signals. m_bPlayAfterLoading = play; +#ifdef __STEM__ + m_pReader->newTrack(pTrack, stemMask); +#else m_pReader->newTrack(pTrack); +#endif atomicStoreRelaxed(m_pChannelToCloneFrom, pChannelToCloneFrom); } else { // Loading a null track means "eject" diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index ca61b55ebbf..f844235565c 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -215,7 +215,16 @@ class EngineBuffer : public EngineObject { // Request that the EngineBuffer load a track. Since the process is // asynchronous, EngineBuffer will emit a trackLoaded signal when the load // has completed. - void loadTrack(TrackPointer pTrack, bool play, EngineChannel* pChannelToCloneFrom); +#ifdef __STEM__ + void loadTrack(TrackPointer pTrack, + mixxx::StemChannelSelection stemMask, + bool play, + EngineChannel* pChannelToCloneFrom); +#else + void loadTrack(TrackPointer pTrack, + bool play, + EngineChannel* pChannelToCloneFrom); +#endif void setChannelIndex(int channelIndex) { m_channelIndex = channelIndex; diff --git a/src/library/analysis/analysisfeature.cpp b/src/library/analysis/analysisfeature.cpp index a713f57ddfc..3789b4a1295 100644 --- a/src/library/analysis/analysisfeature.cpp +++ b/src/library/analysis/analysisfeature.cpp @@ -81,7 +81,11 @@ void AnalysisFeature::bindLibraryWidget(WLibrary* libraryWidget, &DlgAnalysis::loadTrackToPlayer, this, [=, this](TrackPointer track, const QString& group) { - emit loadTrackToPlayer(track, group, false); + emit loadTrackToPlayer(track, group, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); }); connect(m_pAnalysisView, &DlgAnalysis::analyzeTracks, diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 654d77429f7..9da0cf0d6a5 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -202,7 +202,14 @@ class AutoDJProcessor : public QObject { AutoDJError toggleAutoDJ(bool enable); signals: +#ifdef __STEM__ + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play); +#else void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play); +#endif void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void autoDJError(AutoDJProcessor::AutoDJError error); void transitionTimeChanged(int time); @@ -231,7 +238,11 @@ class AutoDJProcessor : public QObject { protected: // The following virtual signal wrappers are used for testing virtual void emitLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play) { - emit loadTrackToPlayer(pTrack, group, play); + emit loadTrackToPlayer(pTrack, group, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + play); } virtual void emitAutoDJStateChanged(AutoDJProcessor::AutoDJState state) { emit autoDJStateChanged(state); diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index 9ac38a1e722..1e6d396f8b5 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -49,7 +49,14 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { signals: void addRandomTrackButton(bool buttonChecked); void loadTrack(TrackPointer tio); +#ifdef __STEM__ + void loadTrackToPlayer(TrackPointer tio, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool); +#else void loadTrackToPlayer(TrackPointer tio, const QString& group, bool); +#endif void trackSelected(TrackPointer pTrack); private: diff --git a/src/library/library.cpp b/src/library/library.cpp index 21f82be564e..91a5acf8f5a 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -552,14 +552,27 @@ void Library::slotLoadLocationToPlayer(const QString& location, const QString& g auto trackRef = TrackRef::fromFilePath(location); TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack(trackRef); if (pTrack) { +#ifdef __STEM__ + emit loadTrackToPlayer(pTrack, group, mixxx::StemChannelSelection(), play); +#else emit loadTrackToPlayer(pTrack, group, play); +#endif } } +#ifdef __STEM__ +void Library::slotLoadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play) { + emit loadTrackToPlayer(pTrack, group, stemMask, play); +} +#else void Library::slotLoadTrackToPlayer( TrackPointer pTrack, const QString& group, bool play) { emit loadTrackToPlayer(pTrack, group, play); } +#endif void Library::slotRefreshLibraryModels() { m_pMixxxLibraryFeature->refreshLibraryModels(); diff --git a/src/library/library.h b/src/library/library.h index e0190084eeb..d872b3c5d06 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -114,7 +114,14 @@ class Library: public QObject { void slotShowTrackModel(QAbstractItemModel* model); void slotSwitchToView(const QString& view); void slotLoadTrack(TrackPointer pTrack); +#ifdef __STEM__ + void slotLoadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play); +#else void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play); +#endif void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play); void slotRefreshLibraryModels(); void slotCreatePlaylist(); @@ -127,7 +134,16 @@ class Library: public QObject { void showTrackModel(QAbstractItemModel* model, bool restoreState = true); void switchToView(const QString& view); void loadTrack(TrackPointer pTrack); - void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false); +#ifdef __STEM__ + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play = false); +#else + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + bool play = false); +#endif void restoreSearch(const QString&); void search(const QString& text); void disableSearch(); diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index 5d4bba6dbab..b54bc75a825 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -38,6 +38,22 @@ LoadToGroupController::LoadToGroupController(LibraryControl* pParent, const QStr this, &LoadToGroupController::slotLoadToGroupAndPlay); +#ifdef __STEM__ + m_loadSelectedTrackStems = + std::make_unique(ConfigKey(group, "load_selected_track_stems")); + connect(m_loadSelectedTrackStems.get(), + &ControlObject::valueChanged, + this, + [this](double value) { + if (value >= 0 && value <= 2 << mixxx::kMaxSupportedStems) { + emit loadToGroup(m_group, + mixxx::StemChannelSelection::fromInt( + static_cast(value)), + false); + } + }); +#endif + connect(this, &LoadToGroupController::loadToGroup, pParent, @@ -48,13 +64,24 @@ LoadToGroupController::~LoadToGroupController() = default; void LoadToGroupController::slotLoadToGroup(double v) { if (v > 0) { - emit loadToGroup(m_group, false); + emit loadToGroup(m_group, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } } void LoadToGroupController::slotLoadToGroupAndPlay(double v) { if (v > 0) { - emit loadToGroup(m_group, true); +#ifdef __STEM__ + emit loadToGroup(m_group, + mixxx::StemChannelSelection(), + true); +#else + emit loadToGroup(m_group, + true); +#endif } } @@ -600,14 +627,23 @@ void LibraryControl::slotUpdateTrackMenuControl(bool visible) { m_pShowTrackMenu->setAndConfirm(visible ? 1.0 : 0.0); } +#ifdef __STEM__ +void LibraryControl::slotLoadSelectedTrackToGroup( + const QString& group, mixxx::StemChannelSelection stemMask, bool play) { +#else void LibraryControl::slotLoadSelectedTrackToGroup(const QString& group, bool play) { +#endif if (!m_pLibraryWidget) { return; } WTrackTableView* pTrackTableView = m_pLibraryWidget->getCurrentTrackTableView(); if (pTrackTableView) { +#ifdef __STEM__ + pTrackTableView->loadSelectedTrackToGroup(group, stemMask, play); +#else pTrackTableView->loadSelectedTrackToGroup(group, play); +#endif } } diff --git a/src/library/librarycontrol.h b/src/library/librarycontrol.h index 65e21013ea4..584d2df1a7a 100644 --- a/src/library/librarycontrol.h +++ b/src/library/librarycontrol.h @@ -5,6 +5,9 @@ #include "control/controlproxy.h" #include "library/library_decl.h" +#ifdef __STEM__ +#include "engine/engine.h" +#endif class ControlEncoder; class ControlObject; @@ -23,7 +26,14 @@ class LoadToGroupController : public QObject { virtual ~LoadToGroupController(); signals: - void loadToGroup(const QString& group, bool); +#ifdef __STEM__ + void loadToGroup(const QString& group, + mixxx::StemChannelSelection stemMask, + bool); +#else + void loadToGroup(const QString& group, + bool); +#endif public slots: void slotLoadToGroup(double v); @@ -33,6 +43,10 @@ class LoadToGroupController : public QObject { const QString m_group; std::unique_ptr m_pLoadControl; std::unique_ptr m_pLoadAndPlayControl; + +#ifdef __STEM__ + std::unique_ptr m_loadSelectedTrackStems; +#endif }; class LibraryControl : public QObject { @@ -54,7 +68,13 @@ class LibraryControl : public QObject { public slots: // Deprecated navigation slots +#ifdef __STEM__ + void slotLoadSelectedTrackToGroup(const QString& group, + mixxx::StemChannelSelection stemMask, + bool play); +#else void slotLoadSelectedTrackToGroup(const QString& group, bool play); +#endif void slotUpdateTrackMenuControl(bool visible); private slots: diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h index 61972192dcd..9a91e748ddd 100644 --- a/src/library/libraryfeature.h +++ b/src/library/libraryfeature.h @@ -12,6 +12,9 @@ #include "library/dao/trackdao.h" #include "library/treeitemmodel.h" #include "track/track_decl.h" +#ifdef __STEM__ +#include "engine/engine.h" +#endif class KeyboardEventFilter; class Library; @@ -135,7 +138,16 @@ class LibraryFeature : public QObject { void showTrackModel(QAbstractItemModel* model, bool restoreState = true); void switchToView(const QString& view); void loadTrack(TrackPointer pTrack); - void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false); +#ifdef __STEM__ + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play = false); +#else + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + bool play = false); +#endif /// saves the scroll, selection and current state of the library model void saveModelState(); /// restores the scroll, selection and current state of the library model diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index 8d715056319..a4832ce0392 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -6,6 +6,9 @@ #include "library/recording/ui_dlgrecording.h" #include "preferences/usersettings.h" #include "track/track_decl.h" +#ifdef __STEM__ +#include "engine/engine.h" +#endif class WLibrary; class WTrackTableView; @@ -37,7 +40,14 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib signals: void loadTrack(TrackPointer tio); - void loadTrackToPlayer(TrackPointer tio, const QString& group, bool play); +#ifdef __STEM__ + void loadTrackToPlayer(TrackPointer tio, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool); +#else + void loadTrackToPlayer(TrackPointer tio, const QString& group, bool); +#endif void restoreSearch(const QString& search); void restoreModelState(); diff --git a/src/library/tabledelegates/previewbuttondelegate.cpp b/src/library/tabledelegates/previewbuttondelegate.cpp index 87c897ec95f..32c0b4856eb 100644 --- a/src/library/tabledelegates/previewbuttondelegate.cpp +++ b/src/library/tabledelegates/previewbuttondelegate.cpp @@ -220,7 +220,11 @@ void PreviewButtonDelegate::buttonClicked() { TrackPointer pTrack = pTrackModel->getTrack(m_currentEditedCellIndex); if (pTrack && pTrack != pOldTrack) { // Load to preview deck and start playing - emit loadTrackToPlayer(pTrack, kPreviewDeckGroup, true); + emit loadTrackToPlayer(pTrack, kPreviewDeckGroup, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); startedPlaying = true; } else if (pTrack == pOldTrack && !isPreviewDeckPlaying()) { // Since the Preview deck might be hidden, starting at the main cue diff --git a/src/library/tabledelegates/previewbuttondelegate.h b/src/library/tabledelegates/previewbuttondelegate.h index a42b206bc12..261b576d1b3 100644 --- a/src/library/tabledelegates/previewbuttondelegate.h +++ b/src/library/tabledelegates/previewbuttondelegate.h @@ -6,6 +6,9 @@ #include "library/tabledelegates/tableitemdelegate.h" #include "track/track_decl.h" #include "util/parented_ptr.h" +#ifdef __STEM__ +#include "engine/engine.h" +#endif class ControlProxy; class WLibraryTableView; @@ -54,7 +57,14 @@ class PreviewButtonDelegate : public TableItemDelegate { const QModelIndex& index) const override; signals: - void loadTrackToPlayer(const TrackPointer& pTrack, const QString& group, bool play); +#ifdef __STEM__ + void loadTrackToPlayer(const TrackPointer& pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool); +#else + void loadTrackToPlayer(const TrackPointer& pTrack, const QString& group, bool); +#endif void buttonSetChecked(bool); public slots: diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 1b8162c7fe9..1891fec2c43 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -25,9 +25,6 @@ namespace { constexpr double kNoTrackColor = -1; constexpr double kShiftCuesOffsetMillis = 10; constexpr double kShiftCuesOffsetSmallMillis = 1; -#ifdef __STEM__ -constexpr int kMaxSupportedStems = 4; -#endif const QString kEffectGroupFormat = QStringLiteral("[EqualizerRack1_%1_Effect1]"); inline double trackColorToDouble(mixxx::RgbColor::optional_t color) { @@ -289,9 +286,9 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl( } #ifdef __STEM__ - m_pStemColors.reserve(kMaxSupportedStems); + m_pStemColors.reserve(mixxx::kMaxSupportedStems); QString group = getGroup(); - for (int stemIdx = 1; stemIdx <= kMaxSupportedStems; stemIdx++) { + for (int stemIdx = 1; stemIdx <= mixxx::kMaxSupportedStems; stemIdx++) { QString stemGroup = QStringLiteral("%1Stem%2]") .arg(group.left(group.size() - 1), QString::number(stemIdx)); @@ -420,7 +417,11 @@ void BaseTrackPlayerImpl::slotEjectTrack(double v) { if (elapsed < mixxx::Duration::fromMillis(kUnreplaceDelay)) { TrackPointer lastEjected = m_pPlayerManager->getSecondLastEjectedTrack(); if (lastEjected) { - slotLoadTrack(lastEjected, false); + slotLoadTrack(lastEjected, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } return; } @@ -429,7 +430,11 @@ void BaseTrackPlayerImpl::slotEjectTrack(double v) { if (!m_pLoadedTrack) { TrackPointer lastEjected = m_pPlayerManager->getLastEjectedTrack(); if (lastEjected) { - slotLoadTrack(lastEjected, false); + slotLoadTrack(lastEjected, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } return; } @@ -543,7 +548,14 @@ void BaseTrackPlayerImpl::disconnectLoadedTrack() { disconnect(m_pLoadedTrack.get(), nullptr, m_pKey.get(), nullptr); } -void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) { +#ifdef __STEM__ +void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, + mixxx::StemChannelSelection stemMask, + bool bPlay) { +#else +void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, + bool bPlay) { +#endif //qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup() << pNewTrack.get(); // Before loading the track, ensure we have access. This uses lazy // evaluation to make sure track isn't NULL before we dereference it. @@ -566,7 +578,17 @@ void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) { // Request a new track from EngineBuffer EngineBuffer* pEngineBuffer = m_pChannel->getEngineBuffer(); +#ifdef __STEM__ + pEngineBuffer->loadTrack(pNewTrack, + stemMask, + bPlay, + m_pChannelToCloneFrom); + + // Select a specific stem if requested + emit selectedStems(stemMask); +#else pEngineBuffer->loadTrack(pNewTrack, bPlay, m_pChannelToCloneFrom); +#endif } void BaseTrackPlayerImpl::slotLoadFailed(TrackPointer pTrack, const QString& reason) { @@ -698,7 +720,7 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack, #ifdef __STEM__ if (m_pStemColors.size()) { const auto& stemInfo = m_pLoadedTrack->getStemInfo(); - DEBUG_ASSERT(stemInfo.size() <= kMaxSupportedStems); + DEBUG_ASSERT(stemInfo.size() <= mixxx::kMaxSupportedStems); int stemIdx = 0; for (const auto& stemColorCo : m_pStemColors) { auto color = kNoTrackColor; @@ -777,7 +799,11 @@ void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) { m_pChannelToCloneFrom = pChannel; bool play = ControlObject::toBool(ConfigKey(m_pChannelToCloneFrom->getGroup(), "play")); - slotLoadTrack(pTrack, play); + slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + play); } void BaseTrackPlayerImpl::slotLoadTrackFromDeck(double d) { @@ -801,7 +827,11 @@ void BaseTrackPlayerImpl::loadTrackFromGroup(const QString& group) { return; } - slotLoadTrack(pTrack, false); + slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } bool BaseTrackPlayerImpl::isTrackMenuControlAvailable() { diff --git a/src/mixer/basetrackplayer.h b/src/mixer/basetrackplayer.h index 537fc092079..e3c596a85e2 100644 --- a/src/mixer/basetrackplayer.h +++ b/src/mixer/basetrackplayer.h @@ -3,6 +3,9 @@ #include #include +#ifdef __STEM__ +#include "engine/engine.h" +#endif #include "engine/channels/enginechannel.h" #include "mixer/baseplayer.h" #include "preferences/colorpalettesettings.h" @@ -46,7 +49,14 @@ class BaseTrackPlayer : public BasePlayer { }; public slots: - virtual void slotLoadTrack(TrackPointer pTrack, bool bPlay = false) = 0; +#ifdef __STEM__ + virtual void slotLoadTrack(TrackPointer pTrack, + mixxx::StemChannelSelection stemMask, + bool bPlay = false) = 0; +#else + virtual void slotLoadTrack(TrackPointer pTrack, + bool bPlay = false) = 0; +#endif virtual void slotCloneFromGroup(const QString& group) = 0; virtual void slotCloneDeck() = 0; virtual void slotEjectTrack(double) = 0; @@ -57,6 +67,9 @@ class BaseTrackPlayer : public BasePlayer { void newTrackLoaded(TrackPointer pLoadedTrack); void trackUnloaded(TrackPointer pUnloadedTrack); void loadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); +#ifdef __STEM__ + void selectedStems(mixxx::StemChannelSelection stemMask); +#endif void playerEmpty(); void noVinylControlInputConfigured(); void trackRatingChanged(int rating); @@ -94,7 +107,14 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { TrackPointer loadFakeTrack(bool bPlay, double filebpm); public slots: - void slotLoadTrack(TrackPointer track, bool bPlay) final; +#ifdef __STEM__ + void slotLoadTrack(TrackPointer track, + mixxx::StemChannelSelection stemMask, + bool bPlay) final; +#else + void slotLoadTrack(TrackPointer track, + bool bPlay) final; +#endif void slotEjectTrack(double) final; void slotCloneFromGroup(const QString& group) final; void slotCloneDeck() final; diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index d54d474f73f..965d153a883 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -670,7 +670,15 @@ void PlayerManager::slotCloneDeck(const QString& source_group, const QString& ta pPlayer->slotCloneFromGroup(source_group); } -void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play) { +#ifdef __STEM__ +void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play) { +#else +void PlayerManager::slotLoadTrackToPlayer( + TrackPointer pTrack, const QString& group, bool play) { +#endif // Do not lock mutex in this method unless it is changed to access // PlayerManager state. BaseTrackPlayer* pPlayer = getPlayer(group); @@ -721,7 +729,11 @@ void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& gr if (clone) { pPlayer->slotCloneDeck(); } else { +#ifdef __STEM__ + pPlayer->slotLoadTrack(pTrack, stemMask, play); +#else pPlayer->slotLoadTrack(pTrack, play); +#endif } m_lastLoadedPlayer = group; @@ -773,7 +785,11 @@ void PlayerManager::slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack) { return; } - pDeck->slotLoadTrack(pTrack, false); + pDeck->slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } void PlayerManager::slotLoadLocationIntoNextAvailableDeck(const QString& location, bool play) { @@ -796,7 +812,11 @@ void PlayerManager::slotLoadTrackIntoNextAvailableSampler(TrackPointer pTrack) { } locker.unlock(); +#ifdef __STEM__ + pSampler->slotLoadTrack(pTrack, mixxx::StemChannelSelection(), false); +#else pSampler->slotLoadTrack(pTrack, false); +#endif } void PlayerManager::slotAnalyzeTrack(TrackPointer track) { diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index 96300baba16..22a98c74831 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -194,7 +194,14 @@ class PlayerManager : public QObject, public PlayerManagerInterface { public slots: // Slots for loading tracks into a Player, which is either a Sampler or a Deck +#ifdef __STEM__ + void slotLoadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play); +#else void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play); +#endif void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play); void slotLoadLocationToPlayerMaybePlay(const QString& location, const QString& group); diff --git a/src/mixer/samplerbank.cpp b/src/mixer/samplerbank.cpp index 4e03f6cdea7..82624221f90 100644 --- a/src/mixer/samplerbank.cpp +++ b/src/mixer/samplerbank.cpp @@ -212,7 +212,12 @@ bool SamplerBank::loadSamplerBankFromPath(const QString& samplerBankPath) { } if (location.isEmpty()) { - m_pPlayerManager->slotLoadTrackToPlayer(TrackPointer(), group, false); + m_pPlayerManager->slotLoadTrackToPlayer( + TrackPointer(), group, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } else { m_pPlayerManager->slotLoadLocationToPlayer(location, group, false); } diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index fe691a203c9..5566fc012c4 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1095,6 +1095,17 @@ QWidget* LegacySkinParser::parseVisual(const QDomElement& node) { &BaseTrackPlayer::loadingTrack, viewer, &WWaveformViewer::slotLoadingTrack); +#ifdef __STEM__ + QObject::connect(pPlayer, + &BaseTrackPlayer::selectedStems, + viewer, + &WWaveformViewer::slotSelectStem); +#endif + + QObject::connect(pPlayer, + &BaseTrackPlayer::trackUnloaded, + viewer, + &WWaveformViewer::slotTrackUnloaded); connect(viewer, &WWaveformViewer::trackDropped, diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h index 8c007848d23..4f72b1f9959 100644 --- a/src/sources/audiosource.h +++ b/src/sources/audiosource.h @@ -193,7 +193,24 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour // Parameters for opening audio sources class OpenParams { public: +#ifdef __STEM__ + OpenParams() + : m_signalInfo(), + m_stemMask(mixxx::StemChannelSelection()) { + } + + OpenParams( + audio::ChannelCount channelCount, + audio::SampleRate sampleRate, + mixxx::StemChannelSelection stemMask = mixxx::StemChannelSelection()) + : m_signalInfo( + channelCount, + sampleRate), + m_stemMask(stemMask) { + } +#else OpenParams() = default; + OpenParams( audio::ChannelCount channelCount, audio::SampleRate sampleRate) @@ -201,16 +218,33 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour channelCount, sampleRate) { } +#endif const audio::SignalInfo& getSignalInfo() const { return m_signalInfo; } +#ifdef __STEM__ + mixxx::StemChannelSelection stemMask() const { + return m_stemMask; + } +#endif + void setChannelCount( audio::ChannelCount channelCount) { m_signalInfo.setChannelCount(channelCount); } +#ifdef __STEM__ + void setStemMask( + mixxx::StemChannelSelection stemMask) { + VERIFY_OR_DEBUG_ASSERT(stemMask <= 2 << mixxx::kMaxSupportedStems) { + return; + } + m_stemMask = stemMask; + } +#endif + void setSampleRate( audio::SampleRate sampleRate) { m_signalInfo.setSampleRate(sampleRate); @@ -218,6 +252,9 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour private: audio::SignalInfo m_signalInfo; +#ifdef __STEM__ + mixxx::StemChannelSelection m_stemMask; +#endif }; // Opens the AudioSource for reading audio data. diff --git a/src/sources/soundsourcestem.cpp b/src/sources/soundsourcestem.cpp index 8dff34f6511..a70924c2369 100644 --- a/src/sources/soundsourcestem.cpp +++ b/src/sources/soundsourcestem.cpp @@ -323,6 +323,12 @@ SoundSource::OpenResult SoundSourceSTEM::tryOpen( AVStream* firstAudioStream = nullptr; int stemCount = 0; + uint selectedStemMask = params.stemMask(); + VERIFY_OR_DEBUG_ASSERT(selectedStemMask <= 2 << mixxx::kMaxSupportedStems) { + kLogger.warning().noquote() + << "Invalid selected stem mask" << selectedStemMask; + return OpenResult::Failed; + } OpenParams stemParam = params; stemParam.setChannelCount(mixxx::audio::ChannelCount::stereo()); for (unsigned int streamIdx = 0; streamIdx < pavInputFormatContext->nb_streams; streamIdx++) { @@ -365,6 +371,11 @@ SoundSource::OpenResult SoundSourceSTEM::tryOpen( stemCount++; } + // StemIdx is equal to StreamIdx -1 (the main mix) + if (selectedStemMask && !(selectedStemMask & 1 << (streamIdx - 1))) { + continue; + } + m_pStereoStreams.emplace_back(std::make_unique(getUrl(), streamIdx)); if (m_pStereoStreams.back()->open(OpenMode::Strict /*Unused*/, stemParam) != OpenResult::Succeeded) { @@ -380,7 +391,16 @@ SoundSource::OpenResult SoundSourceSTEM::tryOpen( return OpenResult::Failed; } - if (params.getSignalInfo().getChannelCount() == mixxx::audio::ChannelCount::stereo()) { + VERIFY_OR_DEBUG_ASSERT(!m_pStereoStreams.empty()) { + kLogger.warning().noquote() + << "no stem track were selected"; + close(); + return OpenResult::Failed; + } + + if (params.getSignalInfo().getChannelCount() == + mixxx::audio::ChannelCount::stereo() || + selectedStemMask) { // Requesting a stereo stream (used for samples and preview decks) m_requestedChannelCount = mixxx::audio::ChannelCount::stereo(); initChannelCountOnce(mixxx::audio::ChannelCount::stereo()); @@ -434,13 +454,18 @@ ReadableSampleFrames SoundSourceSTEM::readSampleFramesClamped( std::size_t stemCount = m_pStereoStreams.size(); CSAMPLE* pBuffer = globalSampleFrames.writableData(); - if (m_requestedChannelCount == mixxx::audio::ChannelCount::stereo()) { + if (m_requestedChannelCount == mixxx::audio::ChannelCount::stereo() && stemCount != 1) { SampleUtil::clear(pBuffer, globalSampleFrames.writableLength()); } else { DEBUG_ASSERT(stemSampleLength * static_cast(stemCount) == globalSampleFrames.writableLength()); } + if (stemCount == 1) { + m_pStereoStreams[0]->readSampleFrames(globalSampleFrames); + return read; + } + for (std::size_t streamIdx = 0; streamIdx < stemCount; streamIdx++) { WritableSampleFrames currentStemFrame = WritableSampleFrames( globalSampleFrames.frameIndexRange(), diff --git a/src/test/autodjprocessor_test.cpp b/src/test/autodjprocessor_test.cpp index 047dcdee14a..069fedde01b 100644 --- a/src/test/autodjprocessor_test.cpp +++ b/src/test/autodjprocessor_test.cpp @@ -95,7 +95,11 @@ class FakeDeck : public BaseTrackPlayer { // a success or failure signal. To simulate a load success, call // fakeTrackLoadedEvent. To simulate a failure, call // fakeTrackLoadFailedEvent. - void slotLoadTrack(TrackPointer pTrack, bool bPlay) override { + void slotLoadTrack(TrackPointer pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection, +#endif + bool bPlay) override { loadedTrack = pTrack; samplerate.set(pTrack->getSampleRate()); play.set(bPlay); @@ -264,7 +268,11 @@ TEST_F(AutoDJProcessorTest, FullIntroOutro_LongerIntro) { // Pretend that track is 1 minute and 40 seconds long. pTrack->setDuration(100); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -281,7 +289,11 @@ TEST_F(AutoDJProcessorTest, FullIntroOutro_LongerIntro) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); // Set intro + outro cues. Outro is 10 seconds long; intro is 30 seconds. const double kSamplesPerSecond = kChannelCount * pTrack->getSampleRate(); @@ -338,7 +350,11 @@ TEST_F(AutoDJProcessorTest, FullIntroOutro_LongerOutro) { // Pretend that track is 1 minute and 40 seconds long. pTrack->setDuration(100); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -355,7 +371,11 @@ TEST_F(AutoDJProcessorTest, FullIntroOutro_LongerOutro) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); // Set intro + outro cues. Outro is 20 seconds long; intro is 10 seconds. const double kSamplesPerSecond = kChannelCount * pTrack->getSampleRate(); @@ -418,7 +438,11 @@ TEST_F(AutoDJProcessorTest, FadeAtOutroStart_LongerIntro) { // Pretend that track is 1 minute and 40 seconds long. pTrack->setDuration(100); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -435,7 +459,11 @@ TEST_F(AutoDJProcessorTest, FadeAtOutroStart_LongerIntro) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); // Set intro + outro cues. Outro is 10 seconds long; intro is 20 seconds. const double kSamplesPerSecond = kChannelCount * pTrack->getSampleRate(); @@ -494,7 +522,11 @@ TEST_F(AutoDJProcessorTest, FadeAtOutroStart_LongerOutro) { // Pretend that track is 1 minute and 40 seconds long. pTrack->setDuration(100); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -511,7 +543,11 @@ TEST_F(AutoDJProcessorTest, FadeAtOutroStart_LongerOutro) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); // Set intro + outro cues. Outro is 20 seconds long; intro is 10 seconds. const double kSamplesPerSecond = kChannelCount * pTrack->getSampleRate(); @@ -641,7 +677,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_DecksStopped) { // Load the track and mark it playing (as the loadTrackToPlayer signal would // have connected to this eventually). TrackPointer pTrack = trackCollectionManager()->getTrackById(testId); - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Signal that the request to load pTrack succeeded. deck1.fakeTrackLoadedEvent(pTrack); @@ -687,7 +727,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_DecksStopped_TrackLoadFails) { // Load the track and mark it playing (as the loadTrackToPlayer signal would // have connected to this eventually). TrackPointer pTrack(newTestTrack(testId)); - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Signal that the request to load pTrack failed. deck1.fakeTrackLoadFailedEvent(pTrack); @@ -705,7 +749,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_DecksStopped_TrackLoadFails) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Now pretend that the follow-up load request succeeded. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); deck1.fakeTrackLoadedEvent(pTrack); // Expect that we will receive a load call for [Channel2] after we get the @@ -753,7 +801,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_DecksStopped_TrackLoadFailsRightDeck) // Load the track and mark it playing (as the loadTrackToPlayer signal would // have connected to this eventually). TrackPointer pTrack(newTestTrack(testId)); - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Signal that the request to load pTrack to deck1 succeeded. deck1.fakeTrackLoadedEvent(pTrack); @@ -777,14 +829,22 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_DecksStopped_TrackLoadFailsRightDeck) EXPECT_CALL(*pProcessor, emitLoadTrackToPlayer(_, QString("[Channel2]"), false)); // Now pretend that the deck2 load request failed. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadFailedEvent(pTrack); // Check that we are still in ADJ_IDLE mode. EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the deck2 load request succeeded. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // Check that we are still in ADJ_IDLE mode and the left deck is playing. @@ -801,7 +861,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck1) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -823,7 +887,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck1) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -840,7 +908,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck1_TrackLoadFailed) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -869,7 +941,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck1_TrackLoadFailed) { EXPECT_CALL(*pProcessor, emitLoadTrackToPlayer(_, QString("[Channel2]"), false)); // Pretend the track load fails. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadFailedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -879,7 +955,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck1_TrackLoadFailed) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -896,7 +976,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck2) { // Pretend a track is playing on deck 2. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck2.slotLoadTrack(pTrack, true); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck2.fakeTrackLoadedEvent(pTrack); @@ -919,7 +1003,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck2) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); // Pretend the track load succeeds. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -936,7 +1024,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck2_TrackLoadFailed) { // Pretend a track is playing on deck 2. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck2.slotLoadTrack(pTrack, true); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck2.fakeTrackLoadedEvent(pTrack); @@ -965,7 +1057,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck2_TrackLoadFailed) { EXPECT_CALL(*pProcessor, emitLoadTrackToPlayer(_, QString("[Channel1]"), false)); // Pretend the track load fails. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadFailedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -975,7 +1071,11 @@ TEST_F(AutoDJProcessorTest, EnabledSuccess_PlayingDeck2_TrackLoadFailed) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); // Pretend the track load succeeds. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1010,7 +1110,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadSuccess) { // Pretend a track is playing on deck 2. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck2.slotLoadTrack(pTrack, true); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck2.fakeTrackLoadedEvent(pTrack); @@ -1035,7 +1139,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadSuccess) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); // Pretend the track load succeeds. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -1078,7 +1186,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadSuccess) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Pretend the track load request succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1097,7 +1209,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadFailed) { // Pretend a track is playing on deck 2. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck2.slotLoadTrack(pTrack, true); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck2.fakeTrackLoadedEvent(pTrack); @@ -1124,7 +1240,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadFailed) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); // Pretend the track load succeeds. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -1171,7 +1291,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadFailed) { EXPECT_CALL(*pProcessor, emitLoadTrackToPlayer(_, QString("[Channel2]"), false)); // Pretend the track load request fails. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadFailedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1181,7 +1305,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck1_LoadOnDeck2_TrackLoadFailed) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Pretend the second track load request succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1200,7 +1328,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadSuccess) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -1225,7 +1357,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadSuccess) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -1268,7 +1404,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadSuccess) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); // Pretend the track load request succeeds. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1287,7 +1427,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadFailed) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -1314,7 +1458,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadFailed) { EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -1361,7 +1509,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadFailed) { EXPECT_CALL(*pProcessor, emitLoadTrackToPlayer(_, QString("[Channel1]"), false)); // Pretend the track load request fails. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadFailedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1371,7 +1523,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_LoadOnDeck1_TrackLoadFailed) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); // Pretend the track load request succeeds. - deck1.slotLoadTrack(pTrack, false); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck1.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader, or play states. @@ -1393,7 +1549,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_Long_Transition) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -1410,7 +1570,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_Long_Transition) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // Set a long transition time @@ -1482,7 +1646,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_Pause_Transition) { // Pretend that track is 2 minutes long. pTrack->setDuration(120); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -1498,7 +1666,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_Pause_Transition) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // The track should have been cued at 0.0. @@ -1512,7 +1684,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_Pause_Transition) { EXPECT_DOUBLE_EQ(0.0, deck2.playposition.get()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // The newly loaded track should have been seeked back by the trackSamples of transition. @@ -1562,7 +1738,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_SeekEnd) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -1579,7 +1759,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_SeekEnd) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -1614,7 +1798,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_SeekBeforeTransition) { // Pretend a track is playing on deck 1. TrackPointer pTrack = newTestTrack(); // Load track and mark it playing. - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Indicate the track loaded successfully. deck1.fakeTrackLoadedEvent(pTrack); @@ -1631,7 +1819,11 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_SeekBeforeTransition) { EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); // Pretend the track load succeeds. - deck2.slotLoadTrack(pTrack, false); + deck2.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); deck2.fakeTrackLoadedEvent(pTrack); // No change to the mode, crossfader or play states. @@ -1690,7 +1882,11 @@ TEST_F(AutoDJProcessorTest, TrackZeroLength) { // have connected to this eventually). TrackPointer pTrack(newTestTrack(testId)); pTrack->setDuration(0); - deck1.slotLoadTrack(pTrack, true); + deck1.slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + true); // Expect that the track is rejected an a new one is loaded // Signal that the request to load pTrack succeeded. diff --git a/src/test/cuecontrol_test.cpp b/src/test/cuecontrol_test.cpp index 5666a9bf284..ad131aa216a 100644 --- a/src/test/cuecontrol_test.cpp +++ b/src/test/cuecontrol_test.cpp @@ -48,7 +48,11 @@ class CueControlTest : public BaseSignalPathTest { } void unloadTrack() { - m_pMixerDeck1->slotLoadTrack(TrackPointer(), false); + m_pMixerDeck1->slotLoadTrack(TrackPointer(), +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } mixxx::audio::FramePos getCurrentFramePos() { diff --git a/src/test/hotcuecontrol_test.cpp b/src/test/hotcuecontrol_test.cpp index 73eca9f01fe..556028e6a24 100644 --- a/src/test/hotcuecontrol_test.cpp +++ b/src/test/hotcuecontrol_test.cpp @@ -79,7 +79,11 @@ class HotcueControlTest : public BaseSignalPathTest { } void unloadTrack() { - m_pMixerDeck1->slotLoadTrack(TrackPointer(), false); + m_pMixerDeck1->slotLoadTrack(TrackPointer(), +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); } mixxx::audio::FramePos currentFramePosition() { diff --git a/src/test/playermanagertest.cpp b/src/test/playermanagertest.cpp index fbd28e0e91b..d1642b5a4f4 100644 --- a/src/test/playermanagertest.cpp +++ b/src/test/playermanagertest.cpp @@ -149,7 +149,11 @@ TEST_F(PlayerManagerTest, UnEjectTest) { ASSERT_NE(nullptr, pTrack1); TrackId testId1 = pTrack1->getId(); ASSERT_TRUE(testId1.isValid()); - deck1->slotLoadTrack(pTrack1, false); + deck1->slotLoadTrack(pTrack1, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); ASSERT_NE(nullptr, deck1->getLoadedTrack()); m_pEngine->process(1024); @@ -162,7 +166,11 @@ TEST_F(PlayerManagerTest, UnEjectTest) { // Load another track. TrackPointer pTrack2 = getOrAddTrackByLocation(getTestDir().filePath(kTrackLocationTest2)); ASSERT_NE(nullptr, pTrack2); - deck1->slotLoadTrack(pTrack2, false); + deck1->slotLoadTrack(pTrack2, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); // Ejecting in an empty deck loads the last-ejected track. auto deck2 = m_pPlayerManager->getDeck(2); @@ -183,7 +191,11 @@ TEST_F(PlayerManagerTest, UnEjectReplaceTrackTest) { ASSERT_NE(nullptr, pTrack1); TrackId testId1 = pTrack1->getId(); ASSERT_TRUE(testId1.isValid()); - deck1->slotLoadTrack(pTrack1, false); + deck1->slotLoadTrack(pTrack1, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); ASSERT_NE(nullptr, deck1->getLoadedTrack()); m_pEngine->process(1024); @@ -192,7 +204,11 @@ TEST_F(PlayerManagerTest, UnEjectReplaceTrackTest) { // Load another track, replacing the first, causing it to be unloaded. TrackPointer pTrack2 = getOrAddTrackByLocation(getTestDir().filePath(kTrackLocationTest2)); ASSERT_NE(nullptr, pTrack2); - deck1->slotLoadTrack(pTrack2, false); + deck1->slotLoadTrack(pTrack2, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); m_pEngine->process(1024); waitForTrackToBeLoaded(deck1); @@ -228,7 +244,11 @@ TEST_F(PlayerManagerTest, UnReplaceTest) { ASSERT_NE(nullptr, pTrack1); TrackId testId1 = pTrack1->getId(); ASSERT_TRUE(testId1.isValid()); - deck1->slotLoadTrack(pTrack1, false); + deck1->slotLoadTrack(pTrack1, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); m_pEngine->process(1024); waitForTrackToBeLoaded(deck1); ASSERT_NE(nullptr, deck1->getLoadedTrack()); @@ -236,7 +256,11 @@ TEST_F(PlayerManagerTest, UnReplaceTest) { // Load another track. TrackPointer pTrack2 = getOrAddTrackByLocation(getTestDir().filePath(kTrackLocationTest2)); ASSERT_NE(nullptr, pTrack2); - deck1->slotLoadTrack(pTrack2, false); + deck1->slotLoadTrack(pTrack2, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); m_pEngine->process(1024); waitForTrackToBeLoaded(deck1); ASSERT_NE(nullptr, deck1->getLoadedTrack()); diff --git a/src/test/signalpathtest.h b/src/test/signalpathtest.h index 4cf38a46532..619e80e21f8 100644 --- a/src/test/signalpathtest.h +++ b/src/test/signalpathtest.h @@ -169,7 +169,11 @@ class BaseSignalPathTest : public MixxxTest, SoundSourceProviderRegistration { if (pEngineDeck->getEngineBuffer()->isTrackLoaded()) { pEngineDeck->getEngineBuffer()->ejectTrack(); } - pDeck->slotLoadTrack(pTrack, false); + pDeck->slotLoadTrack(pTrack, +#ifdef __STEM__ + mixxx::StemChannelSelection(), +#endif + false); // Wait for the track to load. ProcessBuffer(); diff --git a/src/track/track.h b/src/track/track.h index f053b6b2166..0d181d3ab00 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -435,6 +435,16 @@ class Track : public QObject { void setAudioProperties( const mixxx::audio::StreamInfo& streamInfo); + // Information about the actual properties of the + // audio stream is only available after opening the + // source at least once. On this occasion the metadata + // stream info of the track need to be updated to reflect + // these values. + bool hasStreamInfoFromSource() const { + const auto locked = lockMutex(&m_qMutex); + return m_record.hasStreamInfoFromSource(); + } + signals: void artistChanged(const QString&); void titleChanged(const QString&); @@ -568,16 +578,6 @@ class Track : public QObject { ExportTrackMetadataResult exportMetadata( const mixxx::MetadataSource& metadataSource, const SyncTrackMetadataParams& syncParams); - - // Information about the actual properties of the - // audio stream is only available after opening the - // source at least once. On this occasion the metadata - // stream info of the track need to be updated to reflect - // these values. - bool hasStreamInfoFromSource() const { - const auto locked = lockMutex(&m_qMutex); - return m_record.hasStreamInfoFromSource(); - } void updateStreamInfoFromSource( mixxx::audio::StreamInfo&& streamInfo); diff --git a/src/waveform/renderers/allshader/waveformrendererstem.cpp b/src/waveform/renderers/allshader/waveformrendererstem.cpp index bacb18d32ca..5827904f513 100644 --- a/src/waveform/renderers/allshader/waveformrendererstem.cpp +++ b/src/waveform/renderers/allshader/waveformrendererstem.cpp @@ -4,6 +4,7 @@ #include #include +#include "engine/engine.h" #include "track/track.h" #include "util/math.h" #include "waveform/renderers/allshader/matrixforwidgetgeometry.h" @@ -11,10 +12,6 @@ #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/waveform.h" -namespace { -constexpr int kMaxSupportedStems = 4; -} // anonymous namespace - namespace allshader { WaveformRendererStem::WaveformRendererStem( @@ -33,7 +30,7 @@ void WaveformRendererStem::initializeGL() { m_shader.init(); m_textureShader.init(); auto group = m_pEQEnabled->getKey().group; - for (int stemIdx = 1; stemIdx <= kMaxSupportedStems; stemIdx++) { + for (int stemIdx = 1; stemIdx <= mixxx::kMaxSupportedStems; stemIdx++) { DEBUG_ASSERT(group.endsWith("]")); QString stemGroup = QStringLiteral("%1Stem%2]") .arg(group.left(group.size() - 1), @@ -79,6 +76,8 @@ void WaveformRendererStem::paintGL() { return; } + uint selectedStems = m_waveformRenderer->getSelectedStems(); + const float devicePixelRatio = m_waveformRenderer->getDevicePixelRatio(); const int length = static_cast(m_waveformRenderer->getLength() * devicePixelRatio); @@ -125,7 +124,7 @@ void WaveformRendererStem::paintGL() { const double maxSamplingRange = visualIncrementPerPixel / 2.0; for (int visualIdx = 0; visualIdx < length; ++visualIdx) { - for (int stemIdx = 0; stemIdx < 4; stemIdx++) { + for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) { // Stem is drawn twice with different opacity level, this allow to // see the maximum signal by transparency for (int layerIdx = 0; layerIdx < 2; layerIdx++) { @@ -160,7 +159,9 @@ void WaveformRendererStem::paintGL() { // Apply the gains if (layerIdx) { - max *= m_pStemMute[stemIdx]->toBool() + max *= m_pStemMute[stemIdx]->toBool() || + (selectedStems && + !(selectedStems & 1 << stemIdx)) ? 0.f : static_cast(m_pStemGain[stemIdx]->get()); } diff --git a/src/waveform/renderers/waveformwidgetrenderer.cpp b/src/waveform/renderers/waveformwidgetrenderer.cpp index ffefe5d6d45..91e7717649f 100644 --- a/src/waveform/renderers/waveformwidgetrenderer.cpp +++ b/src/waveform/renderers/waveformwidgetrenderer.cpp @@ -21,6 +21,9 @@ constexpr int kDefaultDimBrightThreshold = 127; WaveformWidgetRenderer::WaveformWidgetRenderer(const QString& group) : m_group(group), +#ifdef __STEM__ + m_selectedStems(mixxx::StemChannelSelection()), +#endif m_orientation(Qt::Horizontal), m_dimBrightThreshold(kDefaultDimBrightThreshold), m_height(-1), @@ -416,6 +419,12 @@ void WaveformWidgetRenderer::setDisplayBeatGridAlpha(int alpha) { m_alphaBeatGrid = alpha; } +#ifdef __STEM__ +void WaveformWidgetRenderer::selectStem(mixxx::StemChannelSelection stemMask) { + m_selectedStems = stemMask; +} +#endif + void WaveformWidgetRenderer::setTrack(TrackPointer track) { m_pTrack = track; //used to postpone first display until track sample is actually available diff --git a/src/waveform/renderers/waveformwidgetrenderer.h b/src/waveform/renderers/waveformwidgetrenderer.h index 47cd21b67a6..71ad9de5829 100644 --- a/src/waveform/renderers/waveformwidgetrenderer.h +++ b/src/waveform/renderers/waveformwidgetrenderer.h @@ -46,6 +46,12 @@ class WaveformWidgetRenderer { return m_pTrack; } +#ifdef __STEM__ + uint getSelectedStems() const { + return m_selectedStems; + } +#endif + bool isSlipActive() const { return m_pos[::WaveformRendererAbstract::Play] != m_pos[::WaveformRendererAbstract::Slip]; } @@ -170,6 +176,9 @@ class WaveformWidgetRenderer { return renderer; } +#ifdef __STEM__ + void selectStem(mixxx::StemChannelSelection stemMask); +#endif void setTrack(TrackPointer track); void setMarkPositions(const QList& markPositions) { m_markPositions = markPositions; @@ -195,6 +204,9 @@ class WaveformWidgetRenderer { protected: const QString m_group; TrackPointer m_pTrack; +#ifdef __STEM__ + uint m_selectedStems; +#endif QList m_rendererStack; Qt::Orientation m_orientation; int m_dimBrightThreshold; diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index 6a63ecc2de5..ad060872f2f 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -8,6 +8,9 @@ #include "library/libraryview.h" #include "preferences/usersettings.h" #include "track/track_decl.h" +#ifdef __STEM__ +#include "engine/engine.h" +#endif class QFont; @@ -56,7 +59,12 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { signals: void loadTrack(TrackPointer pTrack); - void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false); + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, +#ifdef __STEM__ + mixxx::StemChannelSelection stemMask, +#endif + bool play = false); void trackSelected(TrackPointer pTrack); void onlyCachedCoverArt(bool); void scrollValueChanged(int); diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index c09652f85c7..ed3ffb91910 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -50,6 +50,9 @@ // WStarRating is required for DlgTrackInfo #include "widget/wstarrating.h" #include "widget/wstarratingaction.h" +#ifdef __STEM__ +#include "widget/wtrackstemmenu.h" +#endif constexpr WTrackMenu::Features WTrackMenu::kDeckTrackMenuFeatures; @@ -299,13 +302,6 @@ void WTrackMenu::createActions() { connect(m_pAutoDJReplaceAct, &QAction::triggered, this, &WTrackMenu::slotAddToAutoDJReplace); } - if (featureIsEnabled(Feature::LoadTo)) { - m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), m_pLoadToMenu); - // currently there is only one preview deck so just map it here. - QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); - connect(m_pAddToPreviewDeck, &QAction::triggered, this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); - } - if (featureIsEnabled(Feature::Remove)) { // Keyboard shortcuts are set here just to have them displayed in the menu. // Actual keypress is handled in WTrackTableView::keyPressEvent(). @@ -588,14 +584,6 @@ void WTrackMenu::setupActions() { } if (featureIsEnabled(Feature::LoadTo)) { - m_pLoadToMenu->addMenu(m_pDeckMenu); - - m_pLoadToMenu->addMenu(m_pSamplerMenu); - - if (m_pNumPreviewDecks.get() > 0.0) { - m_pLoadToMenu->addAction(m_pAddToPreviewDeck); - } - addMenu(m_pLoadToMenu); addSeparator(); } @@ -897,11 +885,53 @@ CoverInfo WTrackMenu::getCoverInfoOfLastTrack() const { } } +void WTrackMenu::generateTrackLoadMenu(const QString& group, + const QString& label, + TrackPointer pTrack, + QMenu* pParentMenu, + bool primaryDeck, + bool enabled) { +#ifdef __STEM__ + if (pTrack && !pTrack->hasStreamInfoFromSource()) { + // The stem metadata are loaded on stream info refresh, which occurs + // when the file gets loaded for the time in the session. If there is no + // stream info from source, when open the file, which lead to loading + // the stem manifest. + mixxx::AudioSource::OpenParams config; + config.setChannelCount(mixxx::kMaxEngineChannelInputCount); + SoundSourceProxy(pTrack).openAudioSource(config); + } + if (enabled && pTrack && pTrack->hasStem()) { + auto* pStemMenu = new WTrackStemMenu( + label, pParentMenu, primaryDeck, group, pTrack->getStemInfo()); + connect(pStemMenu, + &WTrackStemMenu::selectedStem, + this, + [this](const QString& group, mixxx::StemChannelSelection stemMask) { + loadSelectionToGroup(group, stemMask); + close(); + }); + pParentMenu->addMenu(pStemMenu); + } else { +#endif + QAction* pAction = new QAction(label, this); + pAction->setEnabled(enabled); + pParentMenu->addAction(pAction); + connect(pAction, &QAction::triggered, this, [this, group] { loadSelectionToGroup(group); }); +#ifdef __STEM__ + } +#endif +} + void WTrackMenu::updateMenus() { if (isEmpty()) { return; } + if (m_pLoadToMenu) { + m_pLoadToMenu->clear(); + } + // Gray out some stuff if multiple songs were selected. const bool singleTrackSelected = getTrackCount() == 1; @@ -949,12 +979,16 @@ void WTrackMenu::updateMenus() { bool deckEnabled = (!deckPlaying || allowLoadTrackIntoPlayingDeck) && singleTrackSelected; - QAction* pAction = new QAction(tr("Deck %1").arg(i), this); - pAction->setEnabled(deckEnabled); - m_pDeckMenu->addAction(pAction); - connect(pAction, &QAction::triggered, this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); + auto pTrack = getFirstTrackPointer(); + generateTrackLoadMenu(deckGroup, + tr("Deck %1").arg(i), + getFirstTrackPointer(), + m_pDeckMenu, + true, + deckEnabled); } } + m_pLoadToMenu->addMenu(m_pDeckMenu); int iNumSamplers = static_cast(m_pNumSamplers.get()); const int maxSamplersPerMenu = 16; @@ -963,6 +997,7 @@ void WTrackMenu::updateMenus() { if (singleTrackSelected && iNumSamplers > 0) { QMenu* pMenu = m_pSamplerMenu; int samplersInMenu = 0; + TrackPointer pTrack = getFirstTrackPointer(); for (int i = 1; i <= iNumSamplers; ++i) { if (samplersInMenu == maxSamplersPerMenu) { samplersInMenu = 0; @@ -977,17 +1012,25 @@ void WTrackMenu::updateMenus() { bool samplerPlaying = ControlObject::get( ConfigKey(samplerGroup, "play")) > 0.0; bool samplerEnabled = !samplerPlaying && singleTrackSelected; - QAction* pAction = new QAction(samplerTrString(i), pMenu); - pAction->setEnabled(samplerEnabled); - pMenu->addAction(pAction); - connect(pAction, - &QAction::triggered, - this, - [this, samplerGroup] { - loadSelectionToGroup(samplerGroup); - }); + + generateTrackLoadMenu(samplerGroup, + samplerTrString(i), + pTrack, + pMenu, + false, + samplerEnabled); } } + m_pLoadToMenu->addMenu(m_pSamplerMenu); + + if (m_pNumPreviewDecks.get() > 0.0) { + // currently there is only one preview deck so just map it here. + generateTrackLoadMenu(PlayerManager::groupForPreviewDeck(0), + tr("Preview Deck"), + getFirstTrackPointer(), + m_pLoadToMenu, + false); + } } if (featureIsEnabled(Feature::Playlist)) { @@ -1856,7 +1899,14 @@ void WTrackMenu::slotColorPicked(const mixxx::RgbColor::optional_t& color) { hide(); } -void WTrackMenu::loadSelectionToGroup(const QString& group, bool play) { +#ifdef __STEM__ +void WTrackMenu::loadSelectionToGroup(const QString& group, + mixxx::StemChannelSelection stemMask, + bool play) { +#else +void WTrackMenu::loadSelectionToGroup(const QString& group, + bool play) { +#endif TrackPointer pTrack = getFirstTrackPointer(); if (!pTrack) { return; @@ -1876,7 +1926,11 @@ void WTrackMenu::loadSelectionToGroup(const QString& group, bool play) { // TODO: load track from this class without depending on // external slot to load track +#ifdef __STEM__ + emit loadTrackToPlayer(pTrack, group, stemMask, play); +#else emit loadTrackToPlayer(pTrack, group, play); +#endif } namespace { diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index bcbd2ced503..28345fd8c73 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -110,7 +110,16 @@ class WTrackMenu : public QMenu { const QString getDeckGroup() const; signals: - void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false); +#ifdef __STEM__ + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + mixxx::StemChannelSelection stemMask, + bool play = false); +#else + void loadTrackToPlayer(TrackPointer pTrack, + const QString& group, + bool play = false); +#endif void trackMenuVisible(bool visible); void saveCurrentViewState(); void restoreCurrentViewStateOrIndex(); @@ -214,6 +223,13 @@ class WTrackMenu : public QMenu { void setupActions(); void updateMenus(); + void generateTrackLoadMenu(const QString& group, + const QString& label, + TrackPointer pTrack, + QMenu* pParentMenu, + bool primaryDeck, + bool enabled = true); + bool featureIsEnabled(Feature flag) const; void addSelectionToPlaylist(int iPlaylistId); @@ -225,7 +241,14 @@ class WTrackMenu : public QMenu { void clearBeats(); void lockBpm(bool lock); - void loadSelectionToGroup(const QString& group, bool play = false); +#ifdef __STEM__ + void loadSelectionToGroup(const QString& group, + mixxx::StemChannelSelection stemMask = mixxx::StemChannelSelection(), + bool play = false); +#else + void loadSelectionToGroup(const QString& group, + bool play = false); +#endif void clearTrackSelection(); std::pair getTrackBpmLockStates() const; @@ -279,9 +302,6 @@ class WTrackMenu : public QMenu { // Save Track Metadata Action: QAction* m_pExportMetadataAct{}; - // Load Track to PreviewDeck - QAction* m_pAddToPreviewDeck{}; - // Send to Auto-DJ Action QAction* m_pAutoDJBottomAct{}; QAction* m_pAutoDJTopAct{}; diff --git a/src/widget/wtrackstemmenu.cpp b/src/widget/wtrackstemmenu.cpp new file mode 100644 index 00000000000..65d77b0d713 --- /dev/null +++ b/src/widget/wtrackstemmenu.cpp @@ -0,0 +1,117 @@ +#include "widget/wtrackstemmenu.h" + +#include + +#include "moc_wtrackstemmenu.cpp" + +namespace { +const QList stemTracks = { + + mixxx::StemChannel::First, + mixxx::StemChannel::Second, + mixxx::StemChannel::Third, + mixxx::StemChannel::Fourth, +}; +} + +WTrackStemMenu::WTrackStemMenu(const QString& label, + QWidget* parent, + bool primaryDeck, + const QString& group, + const QList& stemInfo) + : QMenu(label, parent), + m_group(group), + m_selectMode(false), + m_stemInfo(stemInfo), + m_currentSelection() { + if (primaryDeck) { + QAction* pAction = new QAction(tr("Load for stem mixing"), this); + addAction(pAction); + connect(pAction, &QAction::triggered, this, [this, group] { + emit selectedStem(group, {mixxx::StemChannelSelection()}); + }); + } + + QAction* pAction = new QAction(tr("Load pre-mixed stereo track"), this); + addAction(pAction); + connect(pAction, &QAction::triggered, this, [this, group] { + emit selectedStem(group, mixxx::StemChannel::All); + }); + addSeparator(); + + DEBUG_ASSERT(stemTracks.count() == mixxx::kMaxSupportedStems); + int stemIdx = 0; + for (const auto& stemTrack : stemTracks) { + m_stemActions.emplace_back( + make_parented(tr("Load the \"%1\" stem") + .arg(m_stemInfo.at(stemIdx).getLabel()), + this)); + addAction(m_stemActions.back().get()); + connect(m_stemActions.back().get(), &QAction::triggered, this, [this, stemTrack] { + emit selectedStem(m_group, stemTrack); + }); + connect(m_stemActions.back().get(), + &QAction::toggled, + this, + [this, stemTrack](bool checked) { + m_currentSelection.setFlag(stemTrack, checked); + }); + stemIdx++; + } + m_selectAction = make_parented(this); + m_selectAction->setToolTip(tr("Load multiple stem into a stereo deck")); + m_selectAction->setDisabled(true); + addAction(m_selectAction.get()); + installEventFilter(this); +} + +bool WTrackStemMenu::eventFilter(QObject* pObj, QEvent* e) { + QInputEvent* pInputEvent = dynamic_cast(e); + if (pInputEvent != nullptr && + (pInputEvent->modifiers() & Qt::ControlModifier) != m_selectMode) { + m_selectMode = pInputEvent->modifiers() & Qt::ControlModifier; + updateActions(); + } + + if (m_selectMode && (e->type() == QEvent::MouseButtonRelease)) { + QAction* pAction = activeAction(); + if (pAction && pAction->isCheckable()) { + pAction->setChecked(!pAction->isChecked()); + updateActions(); + return true; + } + } + return QObject::eventFilter(pObj, e); +} +void WTrackStemMenu::updateActions() { + for (const auto& pAction : m_stemActions) { + pAction->setCheckable(m_selectMode); + } + m_selectAction->setText(m_selectMode + ? !m_currentSelection ? tr("Select stems to load") + : tr("Release \"CTRL\" to load the " + "current selection") + : tr("Use \"CTRL\" to select multiple stems")); +} + +void WTrackStemMenu::showEvent(QShowEvent* pQEvent) { + updateActions(); + QMenu::showEvent(pQEvent); +} + +void WTrackStemMenu::keyPressEvent(QKeyEvent* pQEvent) { + m_selectMode = pQEvent->modifiers() & Qt::ControlModifier; + updateActions(); + pQEvent->accept(); +} + +void WTrackStemMenu::keyReleaseEvent(QKeyEvent* pQEvent) { + bool selectMode = pQEvent->modifiers() & Qt::ControlModifier; + if (!selectMode && m_selectMode && m_currentSelection) { + emit selectedStem(m_group, m_currentSelection); + m_currentSelection = mixxx::StemChannelSelection(); + } + m_selectMode = selectMode; + updateActions(); + pQEvent->accept(); +} diff --git a/src/widget/wtrackstemmenu.h b/src/widget/wtrackstemmenu.h new file mode 100644 index 00000000000..0b965febf26 --- /dev/null +++ b/src/widget/wtrackstemmenu.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "engine/engine.h" +#include "track/steminfo.h" +#include "util/parented_ptr.h" + +class QAction; + +class WTrackStemMenu : public QMenu { + Q_OBJECT + public: + WTrackStemMenu(const QString& label, + QWidget* parent, + bool primaryDeck, + const QString& group, + const QList& stemInfo); + + signals: + void selectedStem(const QString& group, mixxx::StemChannelSelection stemMask); + + protected: + virtual void showEvent(QShowEvent* pQEvent) override; + virtual void keyPressEvent(QKeyEvent* pQEvent) override; + virtual void keyReleaseEvent(QKeyEvent* pQEvent) override; + virtual bool eventFilter(QObject* pObj, QEvent* e) override; + + private: + void updateActions(); + + QString m_group; + bool m_selectMode; + QList m_stemInfo; + mixxx::StemChannelSelection m_currentSelection; + + std::vector> m_stemActions; + parented_ptr m_selectAction; +}; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 427c68814ac..e4dde35967c 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1275,7 +1275,14 @@ void WTrackTableView::activateSelectedTrack() { slotMouseDoubleClicked(indices.at(0)); } -void WTrackTableView::loadSelectedTrackToGroup(const QString& group, bool play) { +#ifdef __STEM__ +void WTrackTableView::loadSelectedTrackToGroup(const QString& group, + mixxx::StemChannelSelection stemMask, + bool play) { +#else +void WTrackTableView::loadSelectedTrackToGroup(const QString& group, + bool play) { +#endif const QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; @@ -1309,7 +1316,12 @@ void WTrackTableView::loadSelectedTrackToGroup(const QString& group, bool play) auto* pTrackModel = getTrackModel(); TrackPointer pTrack; if (pTrackModel && (pTrack = pTrackModel->getTrack(index))) { +#ifdef __STEM__ + DEBUG_ASSERT(!stemMask || pTrack->hasStem()); + emit loadTrackToPlayer(pTrack, group, stemMask, play); +#else emit loadTrackToPlayer(pTrack, group, play); +#endif } } diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 9a8561b3705..bb09541b576 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -11,6 +11,9 @@ #include "util/duration.h" #include "util/parented_ptr.h" #include "widget/wlibrarytableview.h" +#ifdef __STEM__ +#include "engine/engine.h" +#endif class ControlProxy; class DlgTagFetcher; @@ -38,7 +41,14 @@ class WTrackTableView : public WLibraryTableView { void keyPressEvent(QKeyEvent* event) override; void resizeEvent(QResizeEvent* event) override; void activateSelectedTrack(); - void loadSelectedTrackToGroup(const QString& group, bool play); +#ifdef __STEM__ + void loadSelectedTrackToGroup(const QString& group, + mixxx::StemChannelSelection stemMask, + bool play); +#else + void loadSelectedTrackToGroup(const QString& group, + bool play); +#endif void assignNextTrackColor() override; void assignPreviousTrackColor() override; TrackModel::SortColumnId getColumnIdFromCurrentIndex() override; diff --git a/src/widget/wwaveformviewer.cpp b/src/widget/wwaveformviewer.cpp index 22629abc884..f9e41713aca 100644 --- a/src/widget/wwaveformviewer.cpp +++ b/src/widget/wwaveformviewer.cpp @@ -227,6 +227,19 @@ void WWaveformViewer::slotTrackLoaded(TrackPointer track) { } } +#ifdef __STEM__ +void WWaveformViewer::slotSelectStem(mixxx::StemChannelSelection stemMask) { + if (m_waveformWidget) { + m_waveformWidget->selectStem(stemMask); + update(); + } +} +#endif + +void WWaveformViewer::slotTrackUnloaded(TrackPointer pOldTrack) { + slotLoadingTrack(pOldTrack, TrackPointer()); +} + void WWaveformViewer::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) { Q_UNUSED(pNewTrack); Q_UNUSED(pOldTrack); diff --git a/src/widget/wwaveformviewer.h b/src/widget/wwaveformviewer.h index eead266f540..854bbe87ee6 100644 --- a/src/widget/wwaveformviewer.h +++ b/src/widget/wwaveformviewer.h @@ -43,7 +43,11 @@ class WWaveformViewer : public WWidget, public TrackDropTarget { public slots: void slotTrackLoaded(TrackPointer track); + void slotTrackUnloaded(TrackPointer pOldTrack); void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); +#ifdef __STEM__ + void slotSelectStem(mixxx::StemChannelSelection stemMask); +#endif protected: void showEvent(QShowEvent* event) override;