Skip to content

Commit

Permalink
feat: add the ability to load a single stem track
Browse files Browse the repository at this point in the history
This allows players such as sampler or preview deck to load a selected
stem, instead of the main mix, as those players can only read stereo
tracks
  • Loading branch information
acolombier committed Jul 30, 2024
1 parent 67b2d61 commit 3d06cf2
Show file tree
Hide file tree
Showing 42 changed files with 820 additions and 169 deletions.
11 changes: 10 additions & 1 deletion src/engine/cachingreader/cachingreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,11 @@ CachingReaderChunkForOwner* CachingReader::lookupChunkAndFreshen(SINT chunkIndex
}

// Invoked from the UI thread!!
#ifdef __STEM__
void CachingReader::newTrack(TrackPointer pTrack, uint stemIdx) {
#else
void CachingReader::newTrack(TrackPointer pTrack) {
#endif
auto newState = pTrack ? STATE_TRACK_LOADING : STATE_TRACK_UNLOADING;
auto oldState = m_state.fetchAndStoreAcquire(newState);

Expand All @@ -220,7 +224,12 @@ void CachingReader::newTrack(TrackPointer pTrack) {
kLogger.warning()
<< "Loading a new track while loading a track may lead to inconsistent states";
}
m_worker.newTrack(std::move(pTrack));
m_worker.newTrack(std::move(pTrack)
#ifdef __STEM__
,
stemIdx
#endif
);
}

// Called from the engine thread
Expand Down
7 changes: 6 additions & 1 deletion src/engine/cachingreader/cachingreader.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ 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.
void newTrack(TrackPointer pTrack);
void newTrack(TrackPointer pTrack
#ifdef __STEM__
,
uint stemIdx = 0
#endif
);

void setScheduler(EngineWorkerScheduler* pScheduler) {
m_worker.setScheduler(pScheduler);
Expand Down
34 changes: 31 additions & 3 deletions src/engine/cachingreader/cachingreaderworker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,21 @@ ReaderStatusUpdate CachingReaderWorker::processReadRequest(
}

// WARNING: Always called from a different thread (GUI)
void CachingReaderWorker::newTrack(TrackPointer pTrack) {
void CachingReaderWorker::newTrack(TrackPointer pTrack
#ifdef __STEM__
,
uint stemIdx
#endif
) {
{
const auto locker = lockMutex(&m_newTrackMutex);
#ifdef __STEM__
m_pNewTrack = NewTrackRequest{
pTrack,
stemIdx};
#else
m_pNewTrack = pTrack;
#endif
m_newTrackAvailable.storeRelease(1);
}
workReady();
Expand All @@ -113,16 +124,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.stemIdx);
#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();
Expand Down Expand Up @@ -168,7 +188,12 @@ void CachingReaderWorker::unloadTrack() {
m_pReaderStatusFIFO->writeBlocking(&update, 1);
}

void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) {
void CachingReaderWorker::loadTrack(const TrackPointer& pTrack
#ifdef __STEM__
,
uint stemIdx
#endif
) {
// This emit is directly connected and returns synchronized
// after the engine has been stopped.
emit trackLoading();
Expand All @@ -190,6 +215,9 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) {

mixxx::AudioSource::OpenParams config;
config.setChannelCount(m_maxSupportedChannel);
#ifdef __STEM__
config.setStemIdx(stemIdx);
#endif
m_pAudioSource = SoundSourceProxy(pTrack).openAudioSource(config);
if (!m_pAudioSource) {
kLogger.warning()
Expand Down
24 changes: 22 additions & 2 deletions src/engine/cachingreader/cachingreaderworker.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ class CachingReaderWorker : public EngineWorker {
~CachingReaderWorker() override = default;

// Request to load a new track. wake() must be called afterwards.
void newTrack(TrackPointer pTrack);
void newTrack(TrackPointer pTrack
#ifdef __STEM__
,
uint stemIdx
#endif
);

// Run upkeep operations like loading tracks and reading from file. Run by a
// thread pool via the EngineWorkerScheduler.
Expand All @@ -122,6 +127,12 @@ class CachingReaderWorker : public EngineWorker {
void trackLoadFailed(TrackPointer pTrack, const QString& reason);

private:
#ifdef __STEM__
struct NewTrackRequest {
TrackPointer track;
uint stemIdx;
};
#endif
const QString m_group;
QString m_tag;

Expand All @@ -134,7 +145,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();

Expand All @@ -147,7 +162,12 @@ class CachingReaderWorker : public EngineWorker {
void unloadTrack();

/// Internal method to load a track. Emits trackLoaded when finished.
void loadTrack(const TrackPointer& pTrack);
void loadTrack(const TrackPointer& pTrack
#ifdef __STEM__
,
uint stemIdx
#endif
);

ReaderStatusUpdate processReadRequest(
const CachingReaderChunkReadRequest& request);
Expand Down
12 changes: 5 additions & 7 deletions src/engine/channels/enginedeck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

#ifdef __STEM__
namespace {
constexpr int kMaxSupportedStems = 4;

QString getGroupForStem(const QString& deckGroup, int stemIdx) {
DEBUG_ASSERT(deckGroup.endsWith("]"));
return QStringLiteral("%1Stem%2]")
Expand Down Expand Up @@ -72,9 +70,9 @@ EngineDeck::EngineDeck(
m_pStemCount = std::make_unique<ControlObject>(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 = 1; stemIdx <= mixxx::kMaxSupportedStems; stemIdx++) {
m_stemGain.emplace_back(std::make_unique<ControlPotmeter>(
ConfigKey(getGroupForStem(getGroup(), stemIdx), QStringLiteral("volume"))));
// The default value is ignored and override with the medium value by
Expand All @@ -96,7 +94,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);
;
Expand Down Expand Up @@ -150,7 +148,7 @@ void EngineDeck::cloneStemState(const EngineDeck* deckToClone) {
VERIFY_OR_DEBUG_ASSERT(deckToClone) {
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());
}
Expand Down
4 changes: 4 additions & 0 deletions src/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ static constexpr audio::ChannelCount kEngineChannelOutputCount =
audio::ChannelCount::stereo();
static constexpr audio::ChannelCount kMaxEngineChannelInputCount =
audio::ChannelCount::stem();
#ifdef __STEM__
constexpr int kMaxSupportedStems = 4;
constexpr uint kNoStemSelectedIdx = 0;
#endif

// Contains the information needed to process a buffer of audio
class EngineParameters final {
Expand Down
14 changes: 12 additions & 2 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1540,12 +1540,22 @@ void EngineBuffer::hintReader(const double dRate) {
}

// WARNING: This method runs in the GUI thread
void EngineBuffer::loadTrack(TrackPointer pTrack, bool play, EngineChannel* pChannelToCloneFrom) {
void EngineBuffer::loadTrack(TrackPointer pTrack,
#ifdef __STEM__
uint stemIdx,
#endif
bool play,
EngineChannel* pChannelToCloneFrom) {
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;
m_pReader->newTrack(pTrack);
m_pReader->newTrack(pTrack
#ifdef __STEM__
,
stemIdx
#endif
);
atomicStoreRelaxed(m_pChannelToCloneFrom, pChannelToCloneFrom);
} else {
// Loading a null track means "eject"
Expand Down
7 changes: 6 additions & 1 deletion src/engine/enginebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,12 @@ 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);
void loadTrack(TrackPointer pTrack,
#ifdef __STEM__
uint stemIdx,
#endif
bool play,
EngineChannel* pChannelToCloneFrom);

void setChannelIndex(int channelIndex) {
m_channelIndex = channelIndex;
Expand Down
6 changes: 5 additions & 1 deletion src/library/analysis/analysisfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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::kNoStemSelectedIdx,
#endif
false);
});
connect(m_pAnalysisView,
&DlgAnalysis::analyzeTracks,
Expand Down
12 changes: 10 additions & 2 deletions src/library/autodj/autodjprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,11 @@ class AutoDJProcessor : public QObject {
AutoDJError toggleAutoDJ(bool enable);

signals:
void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play);
void loadTrackToPlayer(TrackPointer pTrack, const QString& group,
#ifdef __STEM__
uint stemIdx,
#endif
bool play);
void autoDJStateChanged(AutoDJProcessor::AutoDJState state);
void autoDJError(AutoDJProcessor::AutoDJError error);
void transitionTimeChanged(int time);
Expand Down Expand Up @@ -229,7 +233,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::kNoStemSelectedIdx,
#endif
play);
}
virtual void emitAutoDJStateChanged(AutoDJProcessor::AutoDJState state) {
emit autoDJStateChanged(state);
Expand Down
6 changes: 5 additions & 1 deletion src/library/autodj/dlgautodj.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView {
signals:
void addRandomTrackButton(bool buttonChecked);
void loadTrack(TrackPointer tio);
void loadTrackToPlayer(TrackPointer tio, const QString& group, bool);
void loadTrackToPlayer(TrackPointer tio, const QString& group,
#ifdef __STEM__
uint stemIdx,
#endif
bool);
void trackSelected(TrackPointer pTrack);

private:
Expand Down
18 changes: 15 additions & 3 deletions src/library/library.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,13 +552,25 @@ void Library::slotLoadLocationToPlayer(const QString& location, const QString& g
auto trackRef = TrackRef::fromFilePath(location);
TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack(trackRef);
if (pTrack) {
emit loadTrackToPlayer(pTrack, group, play);
emit loadTrackToPlayer(pTrack, group,
#ifdef __STEM__
mixxx::kNoStemSelectedIdx,
#endif
play);
}
}

void Library::slotLoadTrackToPlayer(
TrackPointer pTrack, const QString& group, bool play) {
emit loadTrackToPlayer(pTrack, group, play);
TrackPointer pTrack, const QString& group,
#ifdef __STEM__
uint stemIdx,
#endif
bool play) {
emit loadTrackToPlayer(pTrack, group,
#ifdef __STEM__
stemIdx,
#endif
play);
}

void Library::slotRefreshLibraryModels() {
Expand Down
13 changes: 11 additions & 2 deletions src/library/library.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ class Library: public QObject {
void slotShowTrackModel(QAbstractItemModel* model);
void slotSwitchToView(const QString& view);
void slotLoadTrack(TrackPointer pTrack);
void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play);
void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group,
#ifdef __STEM__
uint stemIdx,
#endif
bool play);
void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play);
void slotRefreshLibraryModels();
void slotCreatePlaylist();
Expand All @@ -127,7 +131,12 @@ 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);
void loadTrackToPlayer(TrackPointer pTrack,
const QString& group,
#ifdef __STEM__
uint stemIdx,
#endif
bool play = false);
void restoreSearch(const QString&);
void search(const QString& text);
void disableSearch();
Expand Down
Loading

0 comments on commit 3d06cf2

Please sign in to comment.