Skip to content

Commit

Permalink
Merge pull request #1892 from iamcodemaker/seeing-double
Browse files Browse the repository at this point in the history
implement instant doubles functionality
  • Loading branch information
Be-ing authored Feb 8, 2019
2 parents 39bb625 + b6c923c commit 8aeda6f
Show file tree
Hide file tree
Showing 23 changed files with 294 additions and 145 deletions.
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();
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(
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);
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")));
}

// 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));
}
}

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

0 comments on commit 8aeda6f

Please sign in to comment.