Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement instant doubles functionality (clone deck) #1892

Merged
merged 38 commits into from
Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b91ed2d
add method to copy playback state from another deck
iamcodemaker Nov 5, 2018
b011249
perform copy operation after track is loaded
iamcodemaker Nov 6, 2018
8104700
rework instant doubles copying
iamcodemaker Nov 9, 2018
b54baed
perform the seek operation in the engine thread
iamcodemaker Nov 13, 2018
0487864
use EngineSync::pickNonSyncSyncTarget() to determine the copy source
iamcodemaker Nov 14, 2018
f45e39b
add comment
iamcodemaker Nov 14, 2018
c2c4fb0
when cloning, copy loop info from the target track
iamcodemaker Nov 14, 2018
d30ad67
fix autodjprocessor_test.cpp
iamcodemaker Nov 15, 2018
0bfdf66
whitespace
iamcodemaker Nov 18, 2018
0254949
perform a clone operation instead of a load on double tap
iamcodemaker Nov 18, 2018
ba22b9e
use getVisualPlayPos() when cloning play pos from other buffers
iamcodemaker Nov 18, 2018
67892b6
don't reset m_iSyncModeQueued when copying position
iamcodemaker Nov 18, 2018
0c56199
wrap long lines
iamcodemaker Nov 18, 2018
21a4d18
reset m_cloneFromChannel on track load failure
iamcodemaker Nov 18, 2018
17fc56a
explicitly pass the target channel when cloning a buffer
iamcodemaker Nov 18, 2018
594447f
specify the source channel when cloning a deck
iamcodemaker Nov 18, 2018
677a43e
copy play state closer to the position sync operation
iamcodemaker Nov 18, 2018
93fcaab
add slotCloneDeck to BaseTrackPlayer
iamcodemaker Nov 22, 2018
e982aca
perform a deck clone when dragging a track from one deck to another
iamcodemaker Nov 22, 2018
5d87ce4
add function to get the exact play position
iamcodemaker Nov 22, 2018
3000ecb
use new getExactPlayPos() function
iamcodemaker Nov 22, 2018
ab69e32
use QAtomicPointer and rename variable
iamcodemaker Nov 30, 2018
dbd6bbb
only clone when doing drag and drop if the shift key is pressed
iamcodemaker Nov 30, 2018
8cd6f03
change double load timeout to half a second
iamcodemaker Dec 6, 2018
860a7e2
rename requestSyncPosition to requestClonePosition
iamcodemaker Feb 1, 2019
25c4745
rename EngineBuffer::m_pSyncFromChannel to m_pChannelToSyncFrom
iamcodemaker Feb 1, 2019
dac3da7
rename BaseTrackPlayerImpl::m_syncFromChannel to m_pChannelToCloneFrom
iamcodemaker Feb 1, 2019
e431ba1
use nullptr instead of NULL
iamcodemaker Feb 1, 2019
b88eb60
use qt5 function pointer syntax for signal/slot connections
iamcodemaker Feb 1, 2019
5e4d94f
don't allow deck clone on drag and drop if shift is pressed
iamcodemaker Feb 1, 2019
e121137
use nullptr instead of NULL
iamcodemaker Feb 1, 2019
586c62a
use nullptr instead of NULL
iamcodemaker Feb 1, 2019
ebca29e
use nullptr instead of NULL
iamcodemaker Feb 1, 2019
4a235b4
mock clone slots in autodjprocessor_test.cpp
iamcodemaker Feb 1, 2019
2188c6f
add CloneFromDeck control object
iamcodemaker Feb 5, 2019
1d5b40a
round value passed to CloneFromDeck
iamcodemaker Feb 5, 2019
27d760b
factor out track drag and drop code
iamcodemaker Feb 8, 2019
b6c923c
move clone code to EngineBuffer::processSeek()
iamcodemaker Feb 8, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/engine/controls/bpmcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,7 @@ double BpmControl::getNearestPositionInPhase(
return dThisPosition;
}

double dOtherLength = ControlObject::getControl(
ConfigKey(pOtherEngineBuffer->getGroup(), "track_samples"))->get();
double dOtherEnginePlayPos = pOtherEngineBuffer->getVisualPlayPos();
double dOtherPosition = dOtherLength * dOtherEnginePlayPos;
double dOtherPosition = pOtherEngineBuffer->getExactPlayPos();

if (!BpmControl::getBeatContext(otherBeats, dOtherPosition,
NULL, NULL, NULL, &dOtherBeatFraction)) {
Expand Down
18 changes: 18 additions & 0 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@ void EngineBuffer::requestSyncMode(SyncMode mode) {
}
}

void EngineBuffer::requestClonePosition(EngineChannel* pChannel) {
m_pChannelToCloneFrom.store(pChannel);
}

void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) {
if (!m_bCrossfadeReady) {
// Read buffer, as if there where no parameter change
Expand All @@ -420,6 +424,10 @@ void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) {
}
}

void EngineBuffer::seekCloneBuffer(EngineBuffer* pOtherBuffer) {
doSeekPlayPos(pOtherBuffer->getExactPlayPos(), SEEK_EXACT);
}

// WARNING: This method is not thread safe and must not be called from outside
// the engine callback!
void EngineBuffer::setNewPlaypos(double newpos, bool adjustingPhase) {
Expand Down Expand Up @@ -1104,6 +1112,12 @@ void EngineBuffer::processSyncRequests() {
}

void EngineBuffer::processSeek(bool paused) {
// Check if we are cloning another channel before doing any seeking.
EngineChannel* pChannel = m_pChannelToCloneFrom.fetchAndStoreRelaxed(NULL);
if (pChannel) {
seekCloneBuffer(pChannel->getEngineBuffer());
}

// We need to read position just after reading seekType, to ensure that we
// read the matching position to seek_typ or a position from a new (second)
// seek just queued from another thread
Expand Down Expand Up @@ -1281,6 +1295,10 @@ void EngineBuffer::slotEjectTrack(double v) {
}
}

double EngineBuffer::getExactPlayPos() {
return getVisualPlayPos() * getTrackSamples();
}

double EngineBuffer::getVisualPlayPos() {
return m_visualPlayPos->getEnginePlayPos();
}
Expand Down
6 changes: 6 additions & 0 deletions src/engine/enginebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class EngineBuffer : public EngineObject {
void requestSyncPhase();
void requestEnableSync(bool enabled);
void requestSyncMode(SyncMode mode);
void requestClonePosition(EngineChannel* pChannel);

// The process methods all run in the audio callback.
void process(CSAMPLE* pOut, const int iBufferSize);
Expand All @@ -142,6 +143,7 @@ class EngineBuffer : public EngineObject {
bool isTrackLoaded();
TrackPointer getLoadedTrack() const;

double getExactPlayPos();
iamcodemaker marked this conversation as resolved.
Show resolved Hide resolved
double getVisualPlayPos();
double getTrackSamples();

Expand Down Expand Up @@ -224,6 +226,9 @@ class EngineBuffer : public EngineObject {
// to prevent pops.
void readToCrossfadeBuffer(const int iBufferSize);

// Copy the play position from the given buffer
void seekCloneBuffer(EngineBuffer* pOtherBuffer);

// Reset buffer playpos and set file playpos.
void setNewPlaypos(double playpos, bool adjustingPhase);

Expand Down Expand Up @@ -380,6 +385,7 @@ class EngineBuffer : public EngineObject {
QAtomicInt m_iEnableSyncQueued;
QAtomicInt m_iSyncModeQueued;
ControlValueAtomic<double> m_queuedSeekPosition;
QAtomicPointer<EngineChannel> m_pChannelToCloneFrom;

// Is true if the previous buffer was silent due to pausing
QAtomicInt m_iTrackLoading;
Expand Down
150 changes: 126 additions & 24 deletions src/mixer/basetrackplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "mixer/basetrackplayer.h"
#include "mixer/playerinfo.h"
#include "mixer/playermanager.h"

#include "control/controlobject.h"
#include "control/controlpotmeter.h"
Expand Down Expand Up @@ -37,7 +38,8 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent,
m_pConfig(pConfig),
m_pEngineMaster(pMixingEngine),
m_pLoadedTrack(),
m_replaygainPending(false) {
m_replaygainPending(false),
m_pChannelToCloneFrom(nullptr) {
ChannelHandleAndGroup channelGroup =
pMixingEngine->registerChannelGroup(group);
m_pChannel = new EngineDeck(channelGroup, pConfig, pMixingEngine,
Expand Down Expand Up @@ -77,6 +79,13 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent,
m_pDuration = std::make_unique<ControlObject>(
ConfigKey(getGroup(), "duration"));

// Deck cloning
m_pCloneFromDeck = std::make_unique<ControlObject>(
ConfigKey(getGroup(), "CloneFromDeck"),
false);
connect(m_pCloneFromDeck.get(), &ControlObject::valueChanged,
this, &BaseTrackPlayerImpl::slotCloneFromDeck);

// Waveform controls
// This acts somewhat like a ControlPotmeter, but the normal _up/_down methods
// do not work properly with this CO.
Expand Down Expand Up @@ -109,6 +118,8 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent,
m_pPlay->connectValueChanged(this, &BaseTrackPlayerImpl::slotPlayToggled);

pVisualsManager->addDeck(group);

m_cloneTimer.start();
}

BaseTrackPlayerImpl::~BaseTrackPlayerImpl() {
Expand Down Expand Up @@ -167,21 +178,31 @@ void BaseTrackPlayerImpl::loadTrack(TrackPointer pTrack) {

// The loop in and out points must be set here and not in slotTrackLoaded
// so LoopingControl::trackLoaded can access them.
const QList<CuePointer> trackCues(m_pLoadedTrack->getCuePoints());
QListIterator<CuePointer> it(trackCues);
while (it.hasNext()) {
CuePointer pCue(it.next());
if (pCue->getType() == Cue::LOOP) {
double loopStart = pCue->getPosition();
double loopEnd = loopStart + pCue->getLength();
if (loopStart != kNoTrigger && loopEnd != kNoTrigger && loopStart <= loopEnd) {
m_pLoopInPoint->set(loopStart);
m_pLoopOutPoint->set(loopEnd);
break;
if (!m_pChannelToCloneFrom) {
const QList<CuePointer> trackCues(m_pLoadedTrack->getCuePoints());
QListIterator<CuePointer> it(trackCues);
while (it.hasNext()) {
CuePointer pCue(it.next());
if (pCue->getType() == Cue::LOOP) {
double loopStart = pCue->getPosition();
double loopEnd = loopStart + pCue->getLength();
if (loopStart != kNoTrigger && loopEnd != kNoTrigger && loopStart <= loopEnd) {
m_pLoopInPoint->set(loopStart);
m_pLoopOutPoint->set(loopEnd);
break;
}
}
}
} else {
// copy loop in and out points from other deck because any new loops
// won't be saved yet
m_pLoopInPoint->set(ControlObject::get(
Be-ing marked this conversation as resolved.
Show resolved Hide resolved
ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_start_position")));
m_pLoopOutPoint->set(ControlObject::get(
ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_end_position")));
}


connectLoadedTrack();
}

Expand Down Expand Up @@ -244,6 +265,18 @@ void BaseTrackPlayerImpl::disconnectLoadedTrack() {
}

void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) {
mixxx::Duration elapsed = m_cloneTimer.restart();
if (elapsed < mixxx::Duration::fromSeconds(0.5)) {
// load pressed twice quickly, clone instead of loading
EngineChannel* pChannel = m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be replaced with something more specific and predictable, but I don't know what that is. I'll need some guidance here.

slotCloneChannel(pChannel);
}
else {
slotLoadTrackInternal(pNewTrack, bPlay);
}
}

void BaseTrackPlayerImpl::slotLoadTrackInternal(TrackPointer pNewTrack, bool bPlay) {
qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup();
// Before loading the track, ensure we have access. This uses lazy
// evaluation to make sure track isn't NULL before we dereference it.
Expand Down Expand Up @@ -278,6 +311,7 @@ void BaseTrackPlayerImpl::slotLoadFailed(TrackPointer pTrack, QString reason) {
} else {
qDebug() << "Failed to load track (NULL track object)" << reason;
}
m_pChannelToCloneFrom = nullptr;
// Alert user.
QMessageBox::warning(NULL, tr("Couldn't load track."), reason);
}
Expand Down Expand Up @@ -342,22 +376,50 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
ConfigKey("[Mixer Profile]", "GainAutoReset"), false)) {
m_pPreGain->set(1.0);
}
int reset = m_pConfig->getValue<int>(
ConfigKey("[Controls]", "SpeedAutoReset"), RESET_PITCH);
if (reset == RESET_SPEED || reset == RESET_PITCH_AND_SPEED) {
// Avoid resetting speed if master sync is enabled and other decks with sync enabled
// are playing, as this would change the speed of already playing decks.
if (!m_pEngineMaster->getEngineSync()->otherSyncedPlaying(getGroup())) {
if (m_pRateSlider != NULL) {
m_pRateSlider->set(0.0);

if (!m_pChannelToCloneFrom) {
int reset = m_pConfig->getValue<int>(
ConfigKey("[Controls]", "SpeedAutoReset"), RESET_PITCH);
if (reset == RESET_SPEED || reset == RESET_PITCH_AND_SPEED) {
// Avoid resetting speed if master sync is enabled and other decks with sync enabled
// are playing, as this would change the speed of already playing decks.
if (!m_pEngineMaster->getEngineSync()->otherSyncedPlaying(getGroup())) {
if (m_pRateSlider != NULL) {
m_pRateSlider->set(0.0);
}
}
}
}
if (reset == RESET_PITCH || reset == RESET_PITCH_AND_SPEED) {
if (m_pPitchAdjust != NULL) {
m_pPitchAdjust->set(0.0);
if (reset == RESET_PITCH || reset == RESET_PITCH_AND_SPEED) {
if (m_pPitchAdjust != NULL) {
m_pPitchAdjust->set(0.0);
}
}
} else {
// perform a clone of the given channel

// copy rate
if (m_pRateSlider != nullptr) {
m_pRateSlider->set(ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "rate")));
}

// copy pitch
if (m_pPitchAdjust != nullptr) {
m_pPitchAdjust->set(ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "pitch_adjust")));
}
iamcodemaker marked this conversation as resolved.
Show resolved Hide resolved

// copy play state
ControlObject::set(ConfigKey(getGroup(), "play"),
ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "play")));

// copy the play position
m_pChannel->getEngineBuffer()->requestClonePosition(m_pChannelToCloneFrom);

// copy the loop state
if (ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_enabled")) == 1.0) {
ControlObject::set(ConfigKey(getGroup(), "reloop_toggle"), 1.0);
}
}

emit(newTrackLoaded(m_pLoadedTrack));
} else {
// this is the result from an outdated load or unload signal
Expand All @@ -366,6 +428,8 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
qDebug() << "stray BaseTrackPlayerImpl::slotTrackLoaded()";
}

m_pChannelToCloneFrom = nullptr;

// Update the PlayerInfo class that is used in EngineBroadcast to replace
// the metadata of a stream
PlayerInfo::instance().setTrackInfo(getGroup(), m_pLoadedTrack);
Expand All @@ -375,6 +439,44 @@ TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const {
return m_pLoadedTrack;
}

void BaseTrackPlayerImpl::slotCloneDeck(const QString& group) {
EngineChannel* pChannel = m_pEngineMaster->getChannel(group);
if (!pChannel) {
return;
}

slotCloneChannel(pChannel);
}

void BaseTrackPlayerImpl::slotCloneFromDeck(double d) {
int deck = std::lround(d);
if (deck < 1) {
slotCloneChannel(m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel));
} else {
slotCloneDeck(PlayerManager::groupForDeck(deck-1));
iamcodemaker marked this conversation as resolved.
Show resolved Hide resolved
}
}

void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) {
// don't clone from ourselves
if (pChannel == m_pChannel) {
return;
}

m_pChannelToCloneFrom = pChannel;
if (!m_pChannelToCloneFrom) {
return;
}

TrackPointer pTrack = m_pChannelToCloneFrom->getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
m_pChannelToCloneFrom = nullptr;
return;
}

slotLoadTrackInternal(pTrack, false);
}

void BaseTrackPlayerImpl::slotSetReplayGain(mixxx::ReplayGain replayGain) {
// Do not change replay gain when track is playing because
// this may lead to an unexpected volume change
Expand Down
12 changes: 12 additions & 0 deletions src/mixer/basetrackplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "mixer/baseplayer.h"
#include "track/track.h"
#include "util/memory.h"
#include "util/performancetimer.h"

class EngineMaster;
class ControlObject;
Expand Down Expand Up @@ -38,6 +39,8 @@ class BaseTrackPlayer : public BasePlayer {

public slots:
virtual void slotLoadTrack(TrackPointer pTrack, bool bPlay = false) = 0;
virtual void slotCloneChannel(EngineChannel* pChannel) = 0;
virtual void slotCloneDeck(const QString& group) = 0;

signals:
void newTrackLoaded(TrackPointer pLoadedTrack);
Expand Down Expand Up @@ -74,12 +77,16 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer {

public slots:
void slotLoadTrack(TrackPointer track, bool bPlay) final;
void slotCloneChannel(EngineChannel* pChannel) final;
void slotCloneDeck(const QString& group) final;
void slotTrackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack);
void slotLoadFailed(TrackPointer pTrack, QString reason);
void slotSetReplayGain(mixxx::ReplayGain replayGain);
void slotPlayToggled(double);

private slots:
void slotLoadTrackInternal(TrackPointer pNewTrack, bool bPlay);
void slotCloneFromDeck(double deck);
void slotPassthroughEnabled(double v);
void slotVinylControlEnabled(double v);
void slotWaveformZoomValueChangeRequest(double pressed);
Expand All @@ -101,6 +108,11 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer {
TrackPointer m_pLoadedTrack;
EngineDeck* m_pChannel;
bool m_replaygainPending;
EngineChannel* m_pChannelToCloneFrom;
PerformanceTimer m_cloneTimer;

// Deck clone control
std::unique_ptr<ControlObject> m_pCloneFromDeck;

// Waveform display related controls
std::unique_ptr<ControlObject> m_pWaveformZoom;
Expand Down
11 changes: 11 additions & 0 deletions src/mixer/playermanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,17 @@ Auxiliary* PlayerManager::getAuxiliary(unsigned int auxiliary) const {
return m_auxiliaries[auxiliary - 1];
}

void PlayerManager::slotCloneDeck(QString source_group, QString target_group) {
BaseTrackPlayer* pPlayer = getPlayer(target_group);

if (pPlayer == nullptr) {
qWarning() << "Invalid group argument " << target_group << " to slotCloneDeck.";
return;
}

pPlayer->slotCloneDeck(source_group);
}

void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, QString group, bool play) {
// Do not lock mutex in this method unless it is changed to access
// PlayerManager state.
Expand Down
1 change: 1 addition & 0 deletions src/mixer/playermanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class PlayerManager : public QObject, public PlayerManagerInterface {
// Slots for loading tracks into a Player, which is either a Sampler or a Deck
void slotLoadTrackToPlayer(TrackPointer pTrack, QString group, bool play = false);
void slotLoadToPlayer(QString location, QString group);
void slotCloneDeck(QString source_group, QString target_group);

// Slots for loading tracks to decks
void slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack);
Expand Down
Loading