diff --git a/src/engine/bpmcontrol.cpp b/src/engine/bpmcontrol.cpp index 123ab756fdd..3978e2d5efb 100644 --- a/src/engine/bpmcontrol.cpp +++ b/src/engine/bpmcontrol.cpp @@ -26,15 +26,15 @@ const SINT kSamplesPerFrame = 2; } BpmControl::BpmControl(QString group, - UserSettingsPointer pConfig) : - EngineControl(group, pConfig), - m_dSyncTargetBeatDistance(0.0), - m_dSyncInstantaneousBpm(0.0), - m_dLastSyncAdjustment(1.0), - m_resetSyncAdjustment(false), - m_dUserOffset(0.0), - m_tapFilter(this, kFilterLength, kMaxInterval), - m_sGroup(group) { + UserSettingsPointer pConfig) + : EngineControl(group, pConfig), + m_tapFilter(this, kFilterLength, kMaxInterval), + m_dSyncInstantaneousBpm(0.0), + m_dLastSyncAdjustment(1.0), + m_sGroup(group) { + m_dSyncTargetBeatDistance.setValue(0.0); + m_dUserOffset.setValue(0.0); + m_pPlayButton = new ControlProxy(group, "play", this); m_pReverseButton = new ControlProxy(group, "reverse", this); m_pRateSlider = new ControlProxy(group, "rate", this); @@ -125,7 +125,7 @@ BpmControl::BpmControl(QString group, // Measures distance from last beat in percentage: 0.5 = half-beat away. m_pThisBeatDistance = new ControlProxy(group, "beat_distance", this); - m_pSyncMode = ControlObject::getControl(ConfigKey(group, "sync_mode")); + m_pSyncMode = new ControlProxy(group, "sync_mode", this); } BpmControl::~BpmControl() { @@ -153,10 +153,11 @@ void BpmControl::slotFileBpmChanged(double bpm) { // Adjust the file-bpm with the current setting of the rate to get the // engine BPM. We only do this for SYNC_NONE decks because EngineSync will // set our BPM if the file BPM changes. See SyncControl::fileBpmChanged(). - if (m_pBeats) { + BeatsPointer pBeats = m_pBeats; + if (pBeats) { const double beats_bpm = - m_pBeats->getBpmAroundPosition(getCurrentSample(), - kLocalBpmSpan); + pBeats->getBpmAroundPosition( + getSampleOfTrack().current, kLocalBpmSpan); if (beats_bpm != -1) { m_pLocalBpm->set(beats_bpm); } else { @@ -172,34 +173,37 @@ void BpmControl::slotFileBpmChanged(double bpm) { } void BpmControl::slotAdjustBeatsFaster(double v) { - if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_SETBPM)) { - double new_bpm = math_min(200.0, m_pBeats->getBpm() + .01); - m_pBeats->setBpm(new_bpm); + BeatsPointer pBeats = m_pBeats; + if (v > 0 && pBeats && (pBeats->getCapabilities() & Beats::BEATSCAP_SETBPM)) { + double new_bpm = math_min(200.0, pBeats->getBpm() + .01); + pBeats->setBpm(new_bpm); } } void BpmControl::slotAdjustBeatsSlower(double v) { - if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_SETBPM)) { - double new_bpm = math_max(10.0, m_pBeats->getBpm() - .01); - m_pBeats->setBpm(new_bpm); + BeatsPointer pBeats = m_pBeats; + if (v > 0 && pBeats && (pBeats->getCapabilities() & Beats::BEATSCAP_SETBPM)) { + double new_bpm = math_max(10.0, pBeats->getBpm() - .01); + pBeats->setBpm(new_bpm); } } void BpmControl::slotTranslateBeatsEarlier(double v) { - if (v > 0 && m_pTrack && m_pBeats && - (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { - // TODO(rryan): Track::getSampleRate is possibly inaccurate! - const int translate_dist = m_pTrack->getSampleRate() * -.01; - m_pBeats->translate(translate_dist); + BeatsPointer pBeats = m_pBeats; + if (v > 0 && pBeats && + (pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { + const int translate_dist = getSampleOfTrack().rate * -.01; + pBeats->translate(translate_dist); } } void BpmControl::slotTranslateBeatsLater(double v) { - if (v > 0 && m_pTrack && m_pBeats && - (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { + BeatsPointer pBeats = m_pBeats; + if (v > 0 && pBeats && + (pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { // TODO(rryan): Track::getSampleRate is possibly inaccurate! - const int translate_dist = m_pTrack->getSampleRate() * .01; - m_pBeats->translate(translate_dist); + const int translate_dist = getSampleOfTrack().rate * .01; + pBeats->translate(translate_dist); } } @@ -219,7 +223,7 @@ void BpmControl::slotTapFilter(double averageLength, int numSamples) { if (numSamples < 4) return; - auto pBeats = m_pBeats; + BeatsPointer pBeats = m_pBeats; if (!pBeats) return; @@ -390,14 +394,14 @@ double BpmControl::calcSyncedRate(double userTweak) { // If we are not quantized, or there are no beats, or we're master, // or we're in reverse, just return the rate as-is. if (!m_pQuantize->get() || getSyncMode() == SYNC_MASTER || - m_pBeats == NULL || m_pReverseButton->get()) { + !m_pBeats || m_pReverseButton->get()) { m_resetSyncAdjustment = true; return rate + userTweak; } // Now we need to get our beat distance so we can figure out how // out of phase we are. - double dThisPosition = getCurrentSample(); + double dThisPosition = getSampleOfTrack().current; double dBeatLength; double my_percentage; if (!BpmControl::getBeatContextNoLookup(dThisPosition, @@ -424,8 +428,8 @@ double BpmControl::calcSyncedRate(double userTweak) { } double BpmControl::calcSyncAdjustment(double my_percentage, bool userTweakingSync) { - if (m_resetSyncAdjustment) { - m_resetSyncAdjustment = false; + int resetSyncAdjustment = m_resetSyncAdjustment.fetchAndStoreRelaxed(0); + if (resetSyncAdjustment) { m_dLastSyncAdjustment = 1.0; } @@ -442,7 +446,7 @@ double BpmControl::calcSyncAdjustment(double my_percentage, bool userTweakingSyn // than modular 1.0 beat fractions. This will allow sync to work across loop // boundaries too. - double master_percentage = m_dSyncTargetBeatDistance; + double master_percentage = m_dSyncTargetBeatDistance.getValue(); double shortest_distance = shortestPercentageChange( master_percentage, my_percentage); @@ -457,9 +461,9 @@ double BpmControl::calcSyncAdjustment(double my_percentage, bool userTweakingSyn if (userTweakingSync) { // Don't do anything else, leave it adjustment = 1.0; - m_dUserOffset = shortest_distance; + m_dUserOffset.setValue(shortest_distance); } else { - double error = shortest_distance - m_dUserOffset; + double error = shortest_distance - m_dUserOffset.getValue(); // Threshold above which we do sync adjustment. const double kErrorThreshold = 0.01; // Threshold above which sync is really, really bad, so much so that we @@ -505,7 +509,7 @@ double BpmControl::getBeatDistance(double dThisPosition) const { double dNextBeat = m_pNextBeat->get(); if (dPrevBeat == -1 || dNextBeat == -1) { - return 0.0 - m_dUserOffset; + return 0.0 - m_dUserOffset.getValue(); } double dBeatLength = dNextBeat - dPrevBeat; @@ -516,7 +520,7 @@ double BpmControl::getBeatDistance(double dThisPosition) const { if (dBeatPercentage < 0) ++dBeatPercentage; if (dBeatPercentage > 1) --dBeatPercentage; - return dBeatPercentage - m_dUserOffset; + return dBeatPercentage - m_dUserOffset.getValue(); } // static @@ -576,9 +580,11 @@ bool BpmControl::getBeatContextNoLookup( return true; } -double BpmControl::getNearestPositionInPhase(double dThisPosition, bool respectLoops, bool playing) { +double BpmControl::getNearestPositionInPhase( + double dThisPosition, bool respectLoops, bool playing) { // Without a beatgrid, we don't know the phase offset. - if (!m_pBeats) { + BeatsPointer pBeats = m_pBeats; + if (!pBeats) { return dThisPosition; } // Master buffer is always in sync! @@ -594,7 +600,7 @@ double BpmControl::getNearestPositionInPhase(double dThisPosition, bool respectL // There's a chance the COs might be out of date, so do a lookup. // TODO: figure out a way so that quantized control can take care of // this so this call isn't necessary. - if (!getBeatContext(m_pBeats, dThisPosition, + if (!getBeatContext(pBeats, dThisPosition, &dThisPrevBeat, &dThisNextBeat, &dThisBeatLength, NULL)) { return dThisPosition; @@ -610,7 +616,7 @@ double BpmControl::getNearestPositionInPhase(double dThisPosition, bool respectL double dOtherBeatFraction; if (getSyncMode() == SYNC_FOLLOWER) { // If we're a follower, it's easy to get the other beat fraction - dOtherBeatFraction = m_dSyncTargetBeatDistance; + dOtherBeatFraction = m_dSyncTargetBeatDistance.getValue(); } else { // If not, we have to figure it out EngineBuffer* pOtherEngineBuffer = pickSyncTarget(); @@ -665,13 +671,13 @@ double BpmControl::getNearestPositionInPhase(double dThisPosition, bool respectL // infinite beatgrids because the assumption that findNthBeat(-2) always // works will be wrong then. - double dNewPlaypos = (dOtherBeatFraction + m_dUserOffset) * dThisBeatLength; + double dNewPlaypos = (dOtherBeatFraction + m_dUserOffset.getValue()) * dThisBeatLength; if (this_near_next == other_near_next) { dNewPlaypos += dThisPrevBeat; } else if (this_near_next && !other_near_next) { dNewPlaypos += dThisNextBeat; } else { //!this_near_next && other_near_next - dThisPrevBeat = m_pBeats->findNthBeat(dThisPosition, -2); + dThisPrevBeat = pBeats->findNthBeat(dThisPosition, -2); dNewPlaypos += dThisPrevBeat; } @@ -745,8 +751,8 @@ void BpmControl::slotUpdateRateSlider() { m_pRateSlider->set(dRateSlider); } -void BpmControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); +// called from an engine worker thread +void BpmControl::trackLoaded(TrackPointer pNewTrack) { if (m_pTrack) { disconnect(m_pTrack.get(), SIGNAL(beatsUpdated()), this, SLOT(slotUpdatedTrackBeats())); @@ -767,41 +773,45 @@ void BpmControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { } void BpmControl::slotUpdatedTrackBeats() { - if (m_pTrack) { + TrackPointer pTrack = m_pTrack; + if (pTrack) { resetSyncAdjustment(); - m_pBeats = m_pTrack->getBeats(); + m_pBeats = pTrack->getBeats(); } } void BpmControl::slotBeatsTranslate(double v) { - if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { - double currentSample = getCurrentSample(); - double closestBeat = m_pBeats->findClosestBeat(currentSample); + BeatsPointer pBeats = m_pBeats; + if (v > 0 && pBeats && (pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { + double currentSample = getSampleOfTrack().current; + double closestBeat = pBeats->findClosestBeat(currentSample); int delta = currentSample - closestBeat; if (delta % 2 != 0) { delta--; } - m_pBeats->translate(delta); + pBeats->translate(delta); } } void BpmControl::slotBeatsTranslateMatchAlignment(double v) { - if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { + BeatsPointer pBeats = m_pBeats; + if (v > 0 && pBeats && (pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { // Must reset the user offset *before* calling getPhaseOffset(), // otherwise it will always return 0 if master sync is active. - m_dUserOffset = 0.0; + m_dUserOffset.setValue(0.0); - double offset = getPhaseOffset(getCurrentSample()); - m_pBeats->translate(-offset); + double offset = getPhaseOffset(getSampleOfTrack().current); + pBeats->translate(-offset); } } double BpmControl::updateLocalBpm() { double prev_local_bpm = m_pLocalBpm->get(); double local_bpm = 0; - if (m_pBeats) { - local_bpm = m_pBeats->getBpmAroundPosition(getCurrentSample(), - kLocalBpmSpan); + BeatsPointer pBeats = m_pBeats; + if (pBeats) { + local_bpm = pBeats->getBpmAroundPosition( + getSampleOfTrack().current, kLocalBpmSpan); if (local_bpm == -1) { local_bpm = m_pFileBpm->get(); } @@ -816,16 +826,16 @@ double BpmControl::updateLocalBpm() { } double BpmControl::updateBeatDistance() { - double beat_distance = getBeatDistance(getCurrentSample()); + double beat_distance = getBeatDistance(getSampleOfTrack().current); m_pThisBeatDistance->set(beat_distance); if (getSyncMode() == SYNC_NONE) { - m_dUserOffset = 0.0; + m_dUserOffset.setValue(0.0); } return beat_distance; } void BpmControl::setTargetBeatDistance(double beatDistance) { - m_dSyncTargetBeatDistance = beatDistance; + m_dSyncTargetBeatDistance.setValue(beatDistance); } void BpmControl::setInstantaneousBpm(double instantaneousBpm) { @@ -834,31 +844,32 @@ void BpmControl::setInstantaneousBpm(double instantaneousBpm) { void BpmControl::resetSyncAdjustment() { // Immediately edit the beat distance to reflect the new reality. - double new_distance = m_pThisBeatDistance->get() + m_dUserOffset; + double new_distance = m_pThisBeatDistance->get() + m_dUserOffset.getValue(); m_pThisBeatDistance->set(new_distance); - m_dUserOffset = 0.0; + m_dUserOffset.setValue(0.0); m_resetSyncAdjustment = true; } void BpmControl::collectFeatures(GroupFeatureState* pGroupFeatures) const { // Without a beatgrid we don't know any beat details. - if (!m_pBeats) { + SampleOfTrack sot = getSampleOfTrack(); + if (!sot.rate || !m_pBeats) { return; } // Get the current position of this deck. - double dThisPosition = getCurrentSample(); double dThisPrevBeat = m_pPrevBeat->get(); double dThisNextBeat = m_pNextBeat->get(); double dThisBeatLength; double dThisBeatFraction; - if (getBeatContextNoLookup(dThisPosition, + if (getBeatContextNoLookup(sot.current, dThisPrevBeat, dThisNextBeat, &dThisBeatLength, &dThisBeatFraction)) { pGroupFeatures->has_beat_length_sec = true; + // Note: dThisBeatLength is fractional frames count * 2 (stereo samples) pGroupFeatures->beat_length_sec = dThisBeatLength / kSamplesPerFrame - / m_pTrack->getSampleRate() / calcRateRatio(); + / sot.rate / calcRateRatio(); pGroupFeatures->has_beat_fraction = true; pGroupFeatures->beat_fraction = dThisBeatFraction; diff --git a/src/engine/bpmcontrol.h b/src/engine/bpmcontrol.h index 12e28297aa0..5d53225aed1 100644 --- a/src/engine/bpmcontrol.h +++ b/src/engine/bpmcontrol.h @@ -70,9 +70,7 @@ class BpmControl : public EngineControl { // Example: shortestPercentageChange(0.99, 0.01) == 0.02 static double shortestPercentageChange(const double& current_percentage, const double& target_percentage); - - public slots: - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; + void trackLoaded(TrackPointer pNewTrack) override; private slots: void slotFileBpmChanged(double); @@ -146,22 +144,25 @@ class BpmControl : public EngineControl { // Button that translates beats to match another playing deck ControlPushButton* m_pBeatsTranslateMatchAlignment; - // Master Sync objects and values. - ControlObject* m_pSyncMode; ControlProxy* m_pThisBeatDistance; - double m_dSyncTargetBeatDistance; + ControlValueAtomic m_dSyncTargetBeatDistance; + ControlValueAtomic m_dUserOffset; + QAtomicInt m_resetSyncAdjustment; + ControlProxy* m_pSyncMode; + + TapFilter m_tapFilter; // threadsave + + // used in the engine thread only double m_dSyncInstantaneousBpm; double m_dLastSyncAdjustment; - bool m_resetSyncAdjustment; - FRIEND_TEST(EngineSyncTest, UserTweakBeatDistance); - double m_dUserOffset; - - TapFilter m_tapFilter; + // objects below are written from an engine worker thread TrackPointer m_pTrack; BeatsPointer m_pBeats; - QString m_sGroup; + const QString m_sGroup; + + FRIEND_TEST(EngineSyncTest, UserTweakBeatDistance); }; diff --git a/src/engine/clockcontrol.cpp b/src/engine/clockcontrol.cpp index 841b4662159..0a38d566006 100644 --- a/src/engine/clockcontrol.cpp +++ b/src/engine/clockcontrol.cpp @@ -17,9 +17,8 @@ ClockControl::~ClockControl() { delete m_pCOSampleRate; } -void ClockControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); - +// called from an engine worker thread +void ClockControl::trackLoaded(TrackPointer pNewTrack) { // Clear on-beat control m_pCOBeatActive->set(0.0); @@ -41,16 +40,15 @@ void ClockControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { } void ClockControl::slotBeatsUpdated() { - if(m_pTrack) { - m_pBeats = m_pTrack->getBeats(); + TrackPointer pTrack = m_pTrack; + if(pTrack) { + m_pBeats = pTrack->getBeats(); } } void ClockControl::process(const double dRate, - const double currentSample, - const double totalSamples, - const int iBuffersize) { - Q_UNUSED(totalSamples); + const double currentSample, + const int iBuffersize) { Q_UNUSED(iBuffersize); double samplerate = m_pCOSampleRate->get(); @@ -61,8 +59,9 @@ void ClockControl::process(const double dRate, // by the rate. const double blinkIntervalSamples = 2.0 * samplerate * (1.0 * dRate) * blinkSeconds; - if (m_pBeats) { - double closestBeat = m_pBeats->findClosestBeat(currentSample); + BeatsPointer pBeats = m_pBeats; + if (pBeats) { + double closestBeat = pBeats->findClosestBeat(currentSample); double distanceToClosestBeat = fabs(currentSample - closestBeat); m_pCOBeatActive->set(distanceToClosestBeat < blinkIntervalSamples / 2.0); } diff --git a/src/engine/clockcontrol.h b/src/engine/clockcontrol.h index 3aa83a87d66..132edd454b7 100644 --- a/src/engine/clockcontrol.h +++ b/src/engine/clockcontrol.h @@ -19,15 +19,17 @@ class ClockControl: public EngineControl { ~ClockControl() override; void process(const double dRate, const double currentSample, - const double totalSamples, const int iBufferSize) override; + const int iBufferSize) override; public slots: - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; + void trackLoaded(TrackPointer pNewTrack) override; void slotBeatsUpdated(); private: ControlObject* m_pCOBeatActive; ControlProxy* m_pCOSampleRate; + + // objects below are written from an engine worker thread TrackPointer m_pTrack; BeatsPointer m_pBeats; }; diff --git a/src/engine/cuecontrol.cpp b/src/engine/cuecontrol.cpp index 76edcfd5c6c..13fda8aebb9 100644 --- a/src/engine/cuecontrol.cpp +++ b/src/engine/cuecontrol.cpp @@ -187,10 +187,8 @@ void CueControl::detachCue(int hotCue) { pControl->resetCue(); } -void CueControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { +void CueControl::trackLoaded(TrackPointer pNewTrack) { QMutexLocker lock(&m_mutex); - - DEBUG_ASSERT(m_pLoadedTrack == pOldTrack); if (m_pLoadedTrack) { disconnect(m_pLoadedTrack.get(), 0, this, 0); for (int i = 0; i < m_iNumHotCues; ++i) { @@ -334,7 +332,7 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { double closestBeat = m_pClosestBeat->get(); double cuePosition = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? - closestBeat : getCurrentSample(); + closestBeat : getSampleOfTrack().current; pCue->setPosition(cuePosition); pCue->setHotCue(hotcue); pCue->setLabel(""); @@ -576,7 +574,7 @@ void CueControl::cueSet(double v) { QMutexLocker lock(&m_mutex); double closestBeat = m_pClosestBeat->get(); double cue = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? - closestBeat : getCurrentSample(); + closestBeat : getSampleOfTrack().current; m_pCuePoint->set(cue); TrackPointer pLoadedTrack = m_pLoadedTrack; lock.unlock(); @@ -667,6 +665,7 @@ void CueControl::cueCDJ(double v) { QMutexLocker lock(&m_mutex); const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching(); + TrackAt trackAt = getTrackAt(); if (v) { if (m_iCurrentlyPreviewingHotcues) { @@ -675,7 +674,7 @@ void CueControl::cueCDJ(double v) { m_bPreviewing = true; lock.unlock(); seekAbs(m_pCuePoint->get()); - } else if (freely_playing || atEndPosition()) { + } else if (freely_playing || trackAt == TrackAt::End) { // Jump to cue when playing or when at end position // Just in case. @@ -686,7 +685,7 @@ void CueControl::cueCDJ(double v) { lock.unlock(); seekAbs(m_pCuePoint->get()); - } else if (isTrackAtCue()) { + } else if (trackAt == TrackAt::Cue) { // pause at cue point m_bPreviewing = true; m_pPlay->set(1.0); @@ -733,6 +732,7 @@ void CueControl::cueDenon(double v) { QMutexLocker lock(&m_mutex); bool playing = (m_pPlay->toBool()); + TrackAt trackAt = getTrackAt(); if (v) { if (m_iCurrentlyPreviewingHotcues) { @@ -741,7 +741,7 @@ void CueControl::cueDenon(double v) { m_bPreviewing = true; lock.unlock(); seekAbs(m_pCuePoint->get()); - } else if (!playing && isTrackAtCue()) { + } else if (!playing && trackAt == TrackAt::Cue) { // pause at cue point m_bPreviewing = true; m_pPlay->set(1.0); @@ -777,6 +777,7 @@ void CueControl::cuePlay(double v) { QMutexLocker lock(&m_mutex); const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching(); + TrackAt trackAt = getTrackAt(); // pressed if (v) { @@ -788,7 +789,7 @@ void CueControl::cuePlay(double v) { lock.unlock(); seekAbs(m_pCuePoint->get()); - } else if (!isTrackAtCue() && getCurrentSample() <= getTotalSamples()) { + } else if (trackAt == TrackAt::ElseWhere) { // Pause not at cue point and not at end position cueSet(v); // Just in case. @@ -802,11 +803,10 @@ void CueControl::cuePlay(double v) { seekAbs(m_pCuePoint->get()); } } - } else if (isTrackAtCue()){ + } else if (trackAt == TrackAt::Cue){ m_bPreviewing = false; m_pPlay->set(1.0); lock.unlock(); - } } @@ -874,6 +874,8 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) } } + TrackAt trackAt = getTrackAt(); + if (!playPossible) { // play not possible newPlay = false; @@ -887,7 +889,7 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) // Pause: m_pStopButton->set(1.0); if (cueMode == CUE_MODE_DENON) { - if (isTrackAtCue() || previewing) { + if (trackAt == TrackAt::Cue || previewing) { m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } else { // Flashing indicates that a following play would move cue point @@ -904,8 +906,7 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) if (cueMode != CUE_MODE_DENON && cueMode != CUE_MODE_NUMARK) { if (m_pCuePoint->get() != -1) { - if (newPlay == 0.0 && !isTrackAtCue() && - !atEndPosition()) { + if (newPlay == 0.0 && trackAt == TrackAt::ElseWhere) { if (cueMode == CUE_MODE_MIXXX) { // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); @@ -927,14 +928,16 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) return newPlay; } +// called from the engine thread void CueControl::updateIndicators() { // No need for mutex lock because we are only touching COs. double cueMode = m_pCueMode->get(); + TrackAt trackAt = getTrackAt(); if (cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) { // Cue button is only lit at cue point bool playing = m_pPlay->toBool(); - if (isTrackAtCue()) { + if (trackAt == TrackAt::Cue) { // at cue point if (!playing) { m_pCueIndicator->setBlinkValue(ControlIndicator::ON); @@ -943,7 +946,7 @@ void CueControl::updateIndicators() { } else { m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); if (!playing) { - if (!atEndPosition() && cueMode != CUE_MODE_NUMARK) { + if (trackAt != TrackAt::End && cueMode != CUE_MODE_NUMARK) { // Play will move cue point m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } else { @@ -958,24 +961,26 @@ void CueControl::updateIndicators() { if (!m_bPreviewing) { const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching(); if (!freely_playing) { - if (!isTrackAtCue()) { - if (!atEndPosition()) { - if (cueMode == CUE_MODE_MIXXX) { - // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point - m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); - } else if (cueMode == CUE_MODE_MIXXX_NO_BLINK) { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } else { - // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point - m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_250MS); - } - } else { - // At track end + switch (trackAt) { + case TrackAt::ElseWhere: + if (cueMode == CUE_MODE_MIXXX) { + // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point + m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); + } else if (cueMode == CUE_MODE_MIXXX_NO_BLINK) { m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); + } else { + // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point + m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_250MS); } - } else if (m_pCuePoint->get() != -1) { + break; + case TrackAt::End: + // At track end + m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); + break; + case TrackAt::Cue: // Next Press is preview m_pCueIndicator->setBlinkValue(ControlIndicator::ON); + break; } } else { // Cue indicator should be off when freely playing @@ -985,8 +990,21 @@ void CueControl::updateIndicators() { } } -bool CueControl::isTrackAtCue() { - return (fabs(getCurrentSample() - m_pCuePoint->get()) < 1.0f); +void CueControl::resetIndicators() { + m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); + m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); +} + +CueControl::TrackAt CueControl::getTrackAt() const { + SampleOfTrack sot = getSampleOfTrack(); + if (sot.current >= sot.total) { + return TrackAt::End; + } + double cue = m_pCuePoint->get(); + if (cue != -1 && fabs(sot.current - cue) < 1.0f) { + return TrackAt::Cue; + } + return TrackAt::ElseWhere; } bool CueControl::isPlayingByPlayButton() { diff --git a/src/engine/cuecontrol.h b/src/engine/cuecontrol.h index 7b8faddb0cd..c9d82c06f84 100644 --- a/src/engine/cuecontrol.h +++ b/src/engine/cuecontrol.h @@ -99,12 +99,10 @@ class CueControl : public EngineControl { virtual void hintReader(HintVector* pHintList) override; bool updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible); void updateIndicators(); - bool isTrackAtCue(); + void resetIndicators(); bool isPlayingByPlayButton(); bool getPlayFlashingAtPause(); - - public slots: - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; + void trackLoaded(TrackPointer pNewTrack) override; private slots: void cueUpdated(); @@ -131,10 +129,17 @@ class CueControl : public EngineControl { void playStutter(double v); private: + enum class TrackAt { + Cue, + End, + ElseWhere + }; + // These methods are not thread safe, only call them when the lock is held. void createControls(); void attachCue(CuePointer pCue, int hotcueNumber); void detachCue(int hotcueNumber); + TrackAt getTrackAt() const; bool m_bPreviewing; ControlObject* m_pPlay; @@ -165,7 +170,7 @@ class CueControl : public EngineControl { ControlProxy* m_pVinylControlEnabled; ControlProxy* m_pVinylControlMode; - TrackPointer m_pLoadedTrack; + TrackPointer m_pLoadedTrack; // is written from an engine worker thread // Tells us which controls map to which hotcue QMap m_controlMap; diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 25819d9dac3..aab9c94ad4b 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -53,14 +53,14 @@ EngineBuffer::EngineBuffer(QString group, UserSettingsPointer pConfig, EngineChannel* pChannel, EngineMaster* pMixingEngine) : m_group(group), m_pConfig(pConfig), - m_pLoopingControl(NULL), - m_pSyncControl(NULL), - m_pVinylControlControl(NULL), - m_pRateControl(NULL), - m_pBpmControl(NULL), - m_pKeyControl(NULL), - m_pReadAheadManager(NULL), - m_pReader(NULL), + m_pLoopingControl(nullptr), + m_pSyncControl(nullptr), + m_pVinylControlControl(nullptr), + m_pRateControl(nullptr), + m_pBpmControl(nullptr), + m_pKeyControl(nullptr), + m_pReadAheadManager(nullptr), + m_pReader(nullptr), m_filepos_play(0.), m_speed_old(0), m_tempo_ratio_old(1.), @@ -69,7 +69,7 @@ EngineBuffer::EngineBuffer(QString group, UserSettingsPointer pConfig, m_pitch_old(0), m_baserate_old(0), m_rate_old(0.), - m_trackSamplesOld(-1), + m_trackSamplesOld(0), m_trackSampleRateOld(0), m_iSamplesCalculated(0), m_iUiSlowTick(0), @@ -77,9 +77,9 @@ EngineBuffer::EngineBuffer(QString group, UserSettingsPointer pConfig, m_dSlipRate(1.0), m_slipEnabled(0), m_bSlipEnabledProcessing(false), - m_pRepeat(NULL), - m_startButton(NULL), - m_endButton(NULL), + m_pRepeat(nullptr), + m_startButton(nullptr), + m_endButton(nullptr), m_bScalerOverride(false), m_iSeekQueued(SEEK_NONE), m_iSeekPhaseQueued(0), @@ -171,7 +171,6 @@ EngineBuffer::EngineBuffer(QString group, UserSettingsPointer pConfig, m_pRepeat = new ControlPushButton(ConfigKey(m_group, "repeat")); m_pRepeat->setButtonMode(ControlPushButton::TOGGLE); - // Sample rate m_pSampleRate = new ControlProxy("[Master]", "samplerate", this); m_pKeylockEngine = new ControlProxy("[Master]", "keylock_engine", this); @@ -320,7 +319,7 @@ EngineBuffer::~EngineBuffer() { double EngineBuffer::fractionalPlayposFromAbsolute(double absolutePlaypos) { double fFractionalPlaypos = 0.0; - if (m_trackSamplesOld != 0.) { + if (m_trackSamplesOld) { fFractionalPlaypos = math_min(absolutePlaypos, m_trackSamplesOld); fFractionalPlaypos /= m_trackSamplesOld; } @@ -372,7 +371,7 @@ double EngineBuffer::getLocalBpm() { } void EngineBuffer::setEngineMaster(EngineMaster* pEngineMaster) { - foreach (EngineControl* pControl, m_engineControls) { + for (const auto& pControl: qAsConst(m_engineControls)) { pControl->setEngineMaster(pEngineMaster); } } @@ -461,9 +460,7 @@ void EngineBuffer::setNewPlaypos(double newpos, bool adjustingPhase) { m_iSamplesCalculated = 1000000; // Must hold the engineLock while using m_engineControls - for (QList::iterator it = m_engineControls.begin(); - it != m_engineControls.end(); ++it) { - EngineControl *pControl = *it; + for (const auto& pControl: qAsConst(m_engineControls)) { pControl->notifySeek(m_filepos_play, adjustingPhase); } @@ -503,7 +500,7 @@ void EngineBuffer::loadFakeTrack(TrackPointer pTrack, bool bPlay) { slotTrackLoaded(pTrack, pTrack->getSampleRate(), pTrack->getSampleRate() * pTrack->getDurationInt()); m_pSyncControl->setLocalBpm(pTrack->getBpm()); - m_pSyncControl->trackLoaded(pTrack, TrackPointer()); + m_pSyncControl->trackLoaded(pTrack); } // WARNING: Always called from the EngineWorker thread pool @@ -516,8 +513,6 @@ void EngineBuffer::slotTrackLoaded(TrackPointer pTrack, m_pause.lock(); m_visualPlayPos->setInvalid(); m_pCurrentTrack = pTrack; - m_trackSampleRateOld = iTrackSampleRate; - m_trackSamplesOld = iTrackNumSamples; m_pTrackSamples->set(iTrackNumSamples); m_pTrackSampleRate->set(iTrackSampleRate); // Reset slip mode @@ -530,8 +525,7 @@ void EngineBuffer::slotTrackLoaded(TrackPointer pTrack, // Reset the pitch value for the new track. m_pause.unlock(); - // All EngineControls are connected directly - emit(trackLoaded(pTrack, pOldTrack)); + notifyTrackLoaded(pTrack, pOldTrack); // Start buffer processing after all EngineContols are up to date // with the current track e.g track is seeked to Cue m_iTrackLoading = 0; @@ -561,15 +555,13 @@ void EngineBuffer::ejectTrack() { m_pTrackSampleRate->set(0); TrackPointer pTrack = m_pCurrentTrack; m_pCurrentTrack.reset(); - m_trackSampleRateOld = 0; - m_trackSamplesOld = 0; m_playButton->set(0.0); m_visualBpm->set(0.0); m_visualKey->set(0.0); m_timeElapsed->set(0); m_timeRemaining->set(0); m_playposSlider->set(0); - m_pCueControl->updateIndicators(); + m_pCueControl->resetIndicators(); doSeekFractional(0.0, SEEK_EXACT); m_pause.unlock(); @@ -577,7 +569,7 @@ void EngineBuffer::ejectTrack() { m_pReader->newTrack(TrackPointer()); if (pTrack) { - emit(trackLoaded(TrackPointer(), pTrack)); + notifyTrackLoaded(TrackPointer(), pTrack); } } @@ -608,16 +600,11 @@ void EngineBuffer::doSeekFractional(double fractionalPos, enum SeekRequest seekT if (isnan(fractionalPos)) { return; } - double newSamplePosition = fractionalPos * m_trackSamplesOld; + double newSamplePosition = fractionalPos * m_pTrackSamples->get(); doSeekPlayPos(newSamplePosition, seekType); } void EngineBuffer::doSeekPlayPos(double new_playpos, enum SeekRequest seekType) { - // Don't allow the playposition to go past the end. - if (new_playpos > m_trackSamplesOld) { - new_playpos = m_trackSamplesOld; - } - #ifdef __VINYLCONTROL__ // Notify the vinyl control that a seek has taken place in case it is in // absolute mode and needs be switched to relative. @@ -636,7 +623,7 @@ bool EngineBuffer::updateIndicatorsAndModifyPlay(bool newPlay) { bool playPossible = true; if ((!m_pCurrentTrack && load_atomic(m_iTrackLoading) == 0) || (m_pCurrentTrack && load_atomic(m_iTrackLoading) == 0 && - m_filepos_play >= m_trackSamplesOld && + m_filepos_play >= m_pTrackSamples->get() && !load_atomic(m_iSeekQueued))) { // play not possible playPossible = false; @@ -727,338 +714,337 @@ void EngineBuffer::slotKeylockEngineChanged(double dIndex) { } } -void EngineBuffer::process(CSAMPLE* pOutput, const int iBufferSize) { - // Bail if we receive a buffer size with incomplete sample frames. Assert in debug builds. - VERIFY_OR_DEBUG_ASSERT((iBufferSize % kSamplesPerFrame) == 0) { - return; - } - m_pReader->process(); - // Steps: - // - Lookup new reader information - // - Calculate current rate - // - Scale the audio with m_pScale, copy the resulting samples into the - // output buffer - // - Give EngineControl's a chance to do work / request seeks, etc - // - Process repeat mode if we're at the end or beginning of a track - // - Set last sample value (m_fLastSampleValue) so that rampOut works? Other - // miscellaneous upkeep issues. +void EngineBuffer::processTrackLocked( + CSAMPLE* pOutput, const int iBufferSize, int sample_rate) { + ScopedTimer t("EngineBuffer::process_pauselock"); - bool bCurBufferPaused = false; - double rate = 0; - int sample_rate = static_cast(m_pSampleRate->get()); + m_trackSampleRateOld = m_pTrackSampleRate->get(); + m_trackSamplesOld = m_pTrackSamples->get(); - // If the sample rate has changed, force Rubberband to reset so that - // it doesn't reallocate when the user engages keylock during playback. - // We do this even if rubberband is not active. - if (sample_rate != m_iSampleRate) { - m_pScaleLinear->setSampleRate(sample_rate); - m_pScaleST->setSampleRate(sample_rate); - m_pScaleRB->setSampleRate(sample_rate); - m_iSampleRate = sample_rate; + double baserate = 0.0; + if (sample_rate > 0) { + baserate = m_trackSampleRateOld / sample_rate; } - bool bTrackLoading = load_atomic(m_iTrackLoading) != 0; - if (!bTrackLoading && m_pause.tryLock()) { - ScopedTimer t("EngineBuffer::process_pauselock"); - - double baserate = 0.0; - if (sample_rate > 0) { - baserate = ((double)m_trackSampleRateOld / sample_rate); - } - - // Note: play is also active during cue preview - bool paused = !m_playButton->toBool(); + // Note: play is also active during cue preview + bool paused = !m_playButton->toBool(); KeyControl::PitchTempoRatio pitchTempoRatio = m_pKeyControl->getPitchTempoRatio(); - // The pitch adjustment in Ratio (1.0 being normal - // pitch. 2.0 is a full octave shift up). - double pitchRatio = pitchTempoRatio.pitchRatio; - double tempoRatio = pitchTempoRatio.tempoRatio; - const bool keylock_enabled = pitchTempoRatio.keylock; - - bool is_scratching = false; - bool is_reverse = false; - - // Update the slipped position and seek if it was disabled. - processSlip(iBufferSize); - processSyncRequests(); - - // Note: This may effects the m_filepos_play, play, scaler and crossfade buffer - processSeek(paused); - - // speed is the ratio between track-time and real-time - // (1.0 being normal rate. 2.0 plays at 2x speed -- 2 track seconds - // pass for every 1 real second). Depending on whether - // keylock is enabled, this is applied to either the rate or the tempo. - double speed = m_pRateControl->calculateSpeed( - baserate, tempoRatio, paused, iBufferSize, &is_scratching, &is_reverse); - - // The cue indicator may change when scratch state is changed - if (is_scratching != m_scratching_old) { - m_pCueControl->updateIndicators(); + // The pitch adjustment in Ratio (1.0 being normal + // pitch. 2.0 is a full octave shift up). + double pitchRatio = pitchTempoRatio.pitchRatio; + double tempoRatio = pitchTempoRatio.tempoRatio; + const bool keylock_enabled = pitchTempoRatio.keylock; + + bool is_scratching = false; + bool is_reverse = false; + + // Update the slipped position and seek if it was disabled. + processSlip(iBufferSize); + processSyncRequests(); + + // Note: This may effects the m_filepos_play, play, scaler and crossfade buffer + processSeek(paused); + + // speed is the ratio between track-time and real-time + // (1.0 being normal rate. 2.0 plays at 2x speed -- 2 track seconds + // pass for every 1 real second). Depending on whether + // keylock is enabled, this is applied to either the rate or the tempo. + double speed = m_pRateControl->calculateSpeed( + baserate, tempoRatio, paused, iBufferSize, &is_scratching, &is_reverse); + + bool useIndependentPitchAndTempoScaling = false; + + // TODO(owen): Maybe change this so that rubberband doesn't disable + // keylock on scratch. (just check m_pScaleKeylock == m_pScaleST) + if (is_scratching || fabs(speed) > 1.9) { + // Scratching and high speeds with always disables keylock + // because Soundtouch sounds terrible in these conditions. Rubberband + // sounds better, but still has some problems (it may reallocate in + // a party-crashing manner at extremely slow speeds). + // High seek speeds also disables keylock. Our pitch slider could go + // to 90%, so that's the cutoff point. + + // Force pitchRatio to the linear pitch set by speed + pitchRatio = speed; + // This is for the natural speed pitch found on turn tables + } else if (fabs(speed) < 0.1) { + // We have pre-allocated big buffers in Rubberband and Soundtouch for + // a minimum speed of 0.1. Slower speeds will re-allocate much bigger + // buffers which may cause underruns. + // Disable keylock under these conditions. + + // Force pitchRatio to the linear pitch set by speed + pitchRatio = speed; + } else if (keylock_enabled) { + // always use IndependentPitchAndTempoScaling + // to avoid clicks when crossing the linear pitch + // in this case it is most likely that the user + // will have an non linear pitch + // Note: We have undesired noise when cossfading between scalers + useIndependentPitchAndTempoScaling = true; + } else { + // We might have have temporary speed change, so adjust pitch if not locked + // Note: This will not update key and tempo widgets + if (tempoRatio) { + pitchRatio *= (speed / tempoRatio); } - bool useIndependentPitchAndTempoScaling = false; - - // TODO(owen): Maybe change this so that rubberband doesn't disable - // keylock on scratch. (just check m_pScaleKeylock == m_pScaleST) - if (is_scratching || fabs(speed) > 1.9) { - // Scratching and high speeds with always disables keylock - // because Soundtouch sounds terrible in these conditions. Rubberband - // sounds better, but still has some problems (it may reallocate in - // a party-crashing manner at extremely slow speeds). - // High seek speeds also disables keylock. Our pitch slider could go - // to 90%, so that's the cutoff point. - - // Force pitchRatio to the linear pitch set by speed - pitchRatio = speed; - // This is for the natural speed pitch found on turn tables - } else if (fabs(speed) < 0.1) { - // We have pre-allocated big buffers in Rubberband and Soundtouch for - // a minimum speed of 0.1. Slower speeds will re-allocate much bigger - // buffers which may cause underruns. - // Disable keylock under these conditions. - - // Force pitchRatio to the linear pitch set by speed - pitchRatio = speed; - } else if (keylock_enabled) { - // always use IndependentPitchAndTempoScaling - // to avoid clicks when crossing the linear pitch - // in this case it is most likely that the user - // will have an non linear pitch - // Note: We have undesired noise when cossfading between scalers - useIndependentPitchAndTempoScaling = true; - } else { - // We might have have temporary speed change, so adjust pitch if not locked - // Note: This will not update key and tempo widgets - if (tempoRatio) { - pitchRatio *= (speed / tempoRatio); - } - - // Check if we are off-linear (musical key has been adjusted - // independent from speed) to determine if the keylock scaler - // should be used even though keylock is disabled. - if (speed != 0.0) { - double offlinear = pitchRatio / speed; - if (offlinear > kLinearScalerElipsis || - offlinear < 1 / kLinearScalerElipsis) { - // only enable keylock scaler if pitch adjustment is at - // least 1 cent. Everything below is not hear-able. - useIndependentPitchAndTempoScaling = true; - } + // Check if we are off-linear (musical key has been adjusted + // independent from speed) to determine if the keylock scaler + // should be used even though keylock is disabled. + if (speed != 0.0) { + double offlinear = pitchRatio / speed; + if (offlinear > kLinearScalerElipsis || + offlinear < 1 / kLinearScalerElipsis) { + // only enable keylock scaler if pitch adjustment is at + // least 1 cent. Everything below is not hear-able. + useIndependentPitchAndTempoScaling = true; } } + } - if (speed != 0.0) { - // Do not switch scaler when we have no transport - enableIndependentPitchTempoScaling(useIndependentPitchAndTempoScaling, - iBufferSize); - } else if (m_speed_old && !is_scratching) { - // we are stopping, collect samples for fade out + if (speed != 0.0) { + // Do not switch scaler when we have no transport + enableIndependentPitchTempoScaling(useIndependentPitchAndTempoScaling, + iBufferSize); + } else if (m_speed_old && !is_scratching) { + // we are stopping, collect samples for fade out + readToCrossfadeBuffer(iBufferSize); + // Clear the scaler information + m_pScale->clear(); + } + + // How speed/tempo/pitch are related: + // Processing is done in two parts, the first part is calculated inside + // the KeyKontrol class and effects the visual key/pitch widgets. + // The Speed slider controls the tempoRatio and a speedSliderPitchRatio, + // the pitch amount caused by it. + // By default the speed slider controls pitch and tempo with the same + // value. + // If key lock is enabled, the speedSliderPitchRatio is decoupled from + // the speed slider (const). + // + // With preference mode KeylockMode = kLockOriginalKey + // the speedSliderPitchRatio is reset to 1 and back to the tempoRatio + // (natural vinyl Pitch) when keylock is disabled and enabled. + // + // With preference mode KeylockMode = kCurrentKey + // the speedSliderPitchRatio is not reseted when keylock is enabled. + // This mode allows to enable keylock + // while the track is already played. You can reset to the tracks + // original pitch by resetting the pitch knob to center. When disabling + // keylock the pitch is reset to the linear vinyl pitch. + + // The Pitch knob turns if the speed slider is moved without keylock. + // This is useful to get always an analog impression of current pitch, + // and its distance to the original track pitch + // + // The Pitch_Adjust knob does not reflect the speedSliderPitchRatio. + // So it is is useful for controller mappings, because it is not + // changed by the speed slider or keylock. + + // In the second part all other speed changing controls are processed. + // They may produce an additional pitch if keylock is disabled or + // override the pitch in scratching case. + // If pitch ratio and tempo ratio are equal, a linear scaler is used, + // otherwise tempo and pitch are processed individual + + // If we were scratching, and scratching is over, and we're a follower, + // and we're quantized, and not paused, + // we need to sync phase or we'll be totally out of whack and the sync + // adjuster will kick in and push the track back in to sync with the + // master. + if (m_scratching_old && !is_scratching && m_pQuantize->toBool() + && m_pSyncControl->getSyncMode() == SYNC_FOLLOWER && !paused) { + // TODO() The resulting seek is processed in the following callback + // That is to late + requestSyncPhase(); + } + + double rate = 0; + // If the baserate, speed, or pitch has changed, we need to update the + // scaler. Also, if we have changed scalers then we need to update the + // scaler. + if (baserate != m_baserate_old || speed != m_speed_old || + pitchRatio != m_pitch_old || tempoRatio != m_tempo_ratio_old || + m_bScalerChanged) { + // The rate returned by the scale object can be different from the + // wanted rate! Make sure new scaler has proper position. This also + // crossfades between the old scaler and new scaler to prevent + // clicks. + + // Handle direction change. + // The linear scaler supports ramping though zero. + // This is used for scratching, but not for reverse + // For the other, crossfade forward and backward samples + if ((m_speed_old * speed < 0) && // Direction has changed! + (m_pScale != m_pScaleVinyl || // only m_pScaleLinear supports going though 0 + m_reverse_old != is_reverse)) { // no pitch change when reversing + //XXX: Trying to force RAMAN to read from correct + // playpos when rate changes direction - Albert readToCrossfadeBuffer(iBufferSize); // Clear the scaler information m_pScale->clear(); } - // How speed/tempo/pitch are related: - // Processing is done in two parts, the first part is calculated inside - // the KeyKontrol class and effects the visual key/pitch widgets. - // The Speed slider controls the tempoRatio and a speedSliderPitchRatio, - // the pitch amount caused by it. - // By default the speed slider controls pitch and tempo with the same - // value. - // If key lock is enabled, the speedSliderPitchRatio is decoupled from - // the speed slider (const). - // - // With preference mode KeylockMode = kLockOriginalKey - // the speedSliderPitchRatio is reset to 1 and back to the tempoRatio - // (natural vinyl Pitch) when keylock is disabled and enabled. - // - // With preference mode KeylockMode = kCurrentKey - // the speedSliderPitchRatio is not reseted when keylock is enabled. - // This mode allows to enable keylock - // while the track is already played. You can reset to the tracks - // original pitch by resetting the pitch knob to center. When disabling - // keylock the pitch is reset to the linear vinyl pitch. - - // The Pitch knob turns if the speed slider is moved without keylock. - // This is useful to get always an analog impression of current pitch, - // and its distance to the original track pitch - // - // The Pitch_Adjust knob does not reflect the speedSliderPitchRatio. - // So it is is useful for controller mappings, because it is not - // changed by the speed slider or keylock. - - // In the second part all other speed changing controls are processed. - // They may produce an additional pitch if keylock is disabled or - // override the pitch in scratching case. - // If pitch ratio and tempo ratio are equal, a linear scaler is used, - // otherwise tempo and pitch are processed individual - - // If we were scratching, and scratching is over, and we're a follower, - // and we're quantized, and not paused, - // we need to sync phase or we'll be totally out of whack and the sync - // adjuster will kick in and push the track back in to sync with the - // master. - if (m_scratching_old && !is_scratching && m_pQuantize->toBool() - && m_pSyncControl->getSyncMode() == SYNC_FOLLOWER && !paused) { - // TODO() The resulting seek is processed in the following callback - // That is to late - requestSyncPhase(); - } + m_baserate_old = baserate; + m_speed_old = speed; + m_pitch_old = pitchRatio; + m_tempo_ratio_old = tempoRatio; + m_reverse_old = is_reverse; + + // Now we need to update the scaler with the master sample rate, the + // base rate (ratio between sample rate of the source audio and the + // master samplerate), the deck speed, the pitch shift, and whether + // the deck speed should affect the pitch. + + m_pScale->setScaleParameters(baserate, + &speed, + &pitchRatio); + + // The way we treat rate inside of EngineBuffer is actually a + // description of "sample consumption rate" or percentage of samples + // consumed relative to playing back the track at its native sample + // rate and normal speed. pitch_adjust does not change the playback + // rate. + rate = baserate * speed; + + // Scaler is up to date now. + m_bScalerChanged = false; + } else { + // Scaler did not need updating. By definition this means we are at + // our old rate. + rate = m_rate_old; + } - // If the baserate, speed, or pitch has changed, we need to update the - // scaler. Also, if we have changed scalers then we need to update the - // scaler. - if (baserate != m_baserate_old || speed != m_speed_old || - pitchRatio != m_pitch_old || tempoRatio != m_tempo_ratio_old || - m_bScalerChanged) { - // The rate returned by the scale object can be different from the - // wanted rate! Make sure new scaler has proper position. This also - // crossfades between the old scaler and new scaler to prevent - // clicks. - - // Handle direction change. - // The linear scaler supports ramping though zero. - // This is used for scratching, but not for reverse - // For the other, crossfade forward and backward samples - if ((m_speed_old * speed < 0) && // Direction has changed! - (m_pScale != m_pScaleVinyl || // only m_pScaleLinear supports going though 0 - m_reverse_old != is_reverse)) { // no pitch change when reversing - //XXX: Trying to force RAMAN to read from correct - // playpos when rate changes direction - Albert - readToCrossfadeBuffer(iBufferSize); - // Clear the scaler information - m_pScale->clear(); - } + bool at_start = m_filepos_play <= 0; + bool at_end = m_filepos_play >= m_trackSamplesOld; + bool backwards = rate < 0; - m_baserate_old = baserate; - m_speed_old = speed; - m_pitch_old = pitchRatio; - m_tempo_ratio_old = tempoRatio; - m_reverse_old = is_reverse; - - // Now we need to update the scaler with the master sample rate, the - // base rate (ratio between sample rate of the source audio and the - // master samplerate), the deck speed, the pitch shift, and whether - // the deck speed should affect the pitch. - - m_pScale->setScaleParameters(baserate, - &speed, - &pitchRatio); - - // The way we treat rate inside of EngineBuffer is actually a - // description of "sample consumption rate" or percentage of samples - // consumed relative to playing back the track at its native sample - // rate and normal speed. pitch_adjust does not change the playback - // rate. - rate = baserate * speed; - - // Scaler is up to date now. - m_bScalerChanged = false; + bool bCurBufferPaused = false; + if (at_end && !backwards) { + // do not play past end + bCurBufferPaused = true; + } else if (rate == 0 && !is_scratching) { + // do not process samples if have no transport + // the linear scaler supports ramping down to 0 + // this is used for pause by scratching only + bCurBufferPaused = true; + } + + m_rate_old = rate; + + // If the buffer is not paused, then scale the audio. + if (!bCurBufferPaused) { + // Perform scaling of Reader buffer into buffer. + double framesRead = + m_pScale->scaleBuffer(pOutput, iBufferSize); + + // TODO(XXX): The result framesRead might not be an integer value. + // Converting to samples here does not make sense. All positional + // calculations should be done in frames instead of samples! Otherwise + // rounding errors might occur when converting from samples back to + // frames later. + double samplesRead = framesRead * kSamplesPerFrame; + + if (m_bScalerOverride) { + // If testing, we don't have a real log so we fake the position. + m_filepos_play += samplesRead; } else { - // Scaler did not need updating. By definition this means we are at - // our old rate. - rate = m_rate_old; + // Adjust filepos_play by the amount we processed. + m_filepos_play = + m_pReadAheadManager->getFilePlaypositionFromLog( + m_filepos_play, samplesRead); } - - bool at_start = m_filepos_play <= 0; - bool at_end = m_filepos_play >= m_trackSamplesOld; - bool backwards = rate < 0; - - if (at_end && !backwards) { - // do not play past end - bCurBufferPaused = true; - } else if (rate == 0 && !is_scratching) { - // do not process samples if have no transport - // the linear scaler supports ramping down to 0 - // this is used for pause by scratching only - bCurBufferPaused = true; + if (m_bCrossfadeReady) { + SampleUtil::linearCrossfadeBuffers( + pOutput, m_pCrossfadeBuffer, pOutput, iBufferSize); } - - m_rate_old = rate; - - // If the buffer is not paused, then scale the audio. - if (!bCurBufferPaused) { - // Perform scaling of Reader buffer into buffer. - double framesRead = - m_pScale->scaleBuffer(pOutput, iBufferSize); - - // TODO(XXX): The result framesRead might not be an integer value. - // Converting to samples here does not make sense. All positional - // calculations should be done in frames instead of samples! Otherwise - // rounding errors might occur when converting from samples back to - // frames later. - double samplesRead = framesRead * kSamplesPerFrame; - - if (m_bScalerOverride) { - // If testing, we don't have a real log so we fake the position. - m_filepos_play += samplesRead; - } else { - // Adjust filepos_play by the amount we processed. - m_filepos_play = - m_pReadAheadManager->getFilePlaypositionFromLog( - m_filepos_play, samplesRead); - } - if (m_bCrossfadeReady) { - SampleUtil::linearCrossfadeBuffers( - pOutput, m_pCrossfadeBuffer, pOutput, iBufferSize); - } - // Note: we do not fade here if we pass the end or the start of - // the track in reverse direction - // because we assume that the track samples itself start and stop - // towards zero. - // If it turns out that ramping is required be aware that the end - // or start may pass in the middle of the buffer. + // Note: we do not fade here if we pass the end or the start of + // the track in reverse direction + // because we assume that the track samples itself start and stop + // towards zero. + // If it turns out that ramping is required be aware that the end + // or start may pass in the middle of the buffer. + } else { + // Pause + if (m_bCrossfadeReady) { + // We don't ramp here, since EnginePregain handles fades + // from and to speed == 0 + SampleUtil::copy(pOutput, m_pCrossfadeBuffer, iBufferSize); } else { - // Pause - if (m_bCrossfadeReady) { - // We don't ramp here, since EnginePregain handles fades - // from and to speed == 0 - SampleUtil::copy(pOutput, m_pCrossfadeBuffer, iBufferSize); - } else { - SampleUtil::clear(pOutput, iBufferSize); - } + SampleUtil::clear(pOutput, iBufferSize); } + } - QListIterator it(m_engineControls); - while (it.hasNext()) { - EngineControl* pControl = it.next(); - pControl->setCurrentSample(m_filepos_play, m_trackSamplesOld); - pControl->process(rate, m_filepos_play, m_trackSamplesOld, iBufferSize); - } + for (const auto& pControl: qAsConst(m_engineControls)) { + pControl->setCurrentSample(m_filepos_play, m_trackSamplesOld, m_trackSampleRateOld); + pControl->process(rate, m_filepos_play, iBufferSize); + } - m_scratching_old = is_scratching; + m_scratching_old = is_scratching; - // Handle repeat mode - at_start = m_filepos_play <= 0; - at_end = m_filepos_play >= m_trackSamplesOld; + // Handle repeat mode + at_start = m_filepos_play <= 0; + at_end = m_filepos_play >= m_trackSamplesOld; - bool repeat_enabled = m_pRepeat->get() != 0.0; + bool repeat_enabled = m_pRepeat->get() != 0.0; - bool end_of_track = //(at_start && backwards) || + bool end_of_track = //(at_start && backwards) || (at_end && !backwards); - // If playbutton is pressed, check if we are at start or end of track + // If playbutton is pressed, check if we are at start or end of track if ((m_playButton->get() || (m_fwdButton->get() || m_backButton->get())) && end_of_track) { - if (repeat_enabled) { - double fractionalPos = at_start ? 1.0 : 0; - doSeekFractional(fractionalPos, SEEK_STANDARD); - } else { - m_playButton->set(0.); - } + if (repeat_enabled) { + double fractionalPos = at_start ? 1.0 : 0; + doSeekFractional(fractionalPos, SEEK_STANDARD); + } else { + m_playButton->set(0.); } + } - // Give the Reader hints as to which chunks of the current song we - // really care about. It will try very hard to keep these in memory - hintReader(rate); + // Give the Reader hints as to which chunks of the current song we + // really care about. It will try very hard to keep these in memory + hintReader(rate); +} +void EngineBuffer::process(CSAMPLE* pOutput, const int iBufferSize) { + // Bail if we receive a buffer size with incomplete sample frames. Assert in debug builds. + VERIFY_OR_DEBUG_ASSERT((iBufferSize % kSamplesPerFrame) == 0) { + return; + } + m_pReader->process(); + // Steps: + // - Lookup new reader information + // - Calculate current rate + // - Scale the audio with m_pScale, copy the resulting samples into the + // output buffer + // - Give EngineControl's a chance to do work / request seeks, etc + // - Process repeat mode if we're at the end or beginning of a track + // - Set last sample value (m_fLastSampleValue) so that rampOut works? Other + // miscellaneous upkeep issues. + + int sample_rate = static_cast(m_pSampleRate->get()); + + // If the sample rate has changed, force Rubberband to reset so that + // it doesn't reallocate when the user engages keylock during playback. + // We do this even if rubberband is not active. + if (sample_rate != m_iSampleRate) { + m_pScaleLinear->setSampleRate(sample_rate); + m_pScaleST->setSampleRate(sample_rate); + m_pScaleRB->setSampleRate(sample_rate); + m_iSampleRate = sample_rate; + } + + bool bTrackLoading = load_atomic(m_iTrackLoading) != 0; + if (!bTrackLoading && m_pause.tryLock()) { + processTrackLocked(pOutput, iBufferSize, sample_rate); // release the pauselock m_pause.unlock(); - } else { // if (!bTrackLoading && m_pause.tryLock()) { + } else { // We are loading a new Track - bCurBufferPaused = true; // Here the old track was playing and loading the new track is in // progress. We can't predict when it happens, so we are not able @@ -1157,6 +1143,11 @@ void EngineBuffer::processSeek(bool paused) { m_iSeekQueued.fetchAndStoreRelease(SEEK_NONE)); double position = m_queuedSeekPosition.getValue(); + // Don't allow the playposition to go past the end. + if (position > m_trackSamplesOld) { + position = m_trackSamplesOld; + } + // Add SEEK_PHASE bit, if any if (m_iSeekPhaseQueued.fetchAndStoreRelease(0)) { seekType |= SEEK_PHASE; @@ -1218,6 +1209,10 @@ void EngineBuffer::postProcess(const int iBufferSize) { } void EngineBuffer::updateIndicators(double speed, int iBufferSize) { + if (!m_trackSampleRateOld || !m_trackSamplesOld) { + // no track loaded + return; + } // Increase samplesCalculated by the buffer size m_iSamplesCalculated += iBufferSize; @@ -1235,7 +1230,7 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { // Update indicators that are only updated after every // sampleRate/kiUpdateRate samples processed. (e.g. playposSlider) - if (m_iSamplesCalculated > (m_pSampleRate->get() / kiPlaypositionUpdateRate)) { + if (m_iSamplesCalculated > (kSamplesPerFrame * m_pSampleRate->get() / kiPlaypositionUpdateRate)) { const double samplePositionToSeconds = 1.0 / m_trackSampleRateOld / kSamplesPerFrame / m_tempo_ratio_old; m_timeElapsed->set(m_filepos_play * samplePositionToSeconds); @@ -1279,7 +1274,7 @@ void EngineBuffer::hintReader(const double dRate) { m_hintList.append(hint); } - for (const auto& pControl: m_engineControls) { + for (const auto& pControl: qAsConst(m_engineControls)) { pControl->hintReader(&m_hintList); } m_pReader->hintAndMaybeWake(m_hintList); @@ -1302,9 +1297,6 @@ void EngineBuffer::addControl(EngineControl* pControl) { // Connect to signals from EngineControl here... m_engineControls.push_back(pControl); pControl->setEngineBuffer(this); - connect(this, SIGNAL(trackLoaded(TrackPointer, TrackPointer)), - pControl, SLOT(trackLoaded(TrackPointer, TrackPointer)), - Qt::DirectConnection); } void EngineBuffer::bindWorkers(EngineWorkerScheduler* pWorkerScheduler) { @@ -1371,3 +1363,14 @@ void EngineBuffer::collectFeatures(GroupFeatureState* pGroupFeatures) const { m_pBpmControl->collectFeatures(pGroupFeatures); } } + +void EngineBuffer::notifyTrackLoaded( + TrackPointer pNewTrack, TrackPointer pOldTrack) { + // First inform engineControls directly + // Note: we are still in a worker thread. + for (const auto& pControl: qAsConst(m_engineControls)) { + pControl->trackLoaded(pNewTrack); + } + // Inform BaseTrackPlayer via a queued connection + emit(trackLoaded(pNewTrack, pOldTrack)); +} diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 6adf9ab24dc..cdb01276b4d 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -235,12 +235,14 @@ class EngineBuffer : public EngineObject { bool updateIndicatorsAndModifyPlay(bool newPlay); void verifyPlay(); + void notifyTrackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack); + void processTrackLocked(CSAMPLE* pOutput, const int iBufferSize, int sample_rate); // Holds the name of the control group QString m_group; UserSettingsPointer m_pConfig; - LoopingControl* m_pLoopingControl; + LoopingControl* m_pLoopingControl; // used for testes FRIEND_TEST(LoopingControlTest, LoopScale_HalvesLoop); FRIEND_TEST(LoopingControlTest, LoopMoveTest); FRIEND_TEST(LoopingControlTest, LoopResizeSeek); @@ -303,7 +305,7 @@ class EngineBuffer : public EngineObject { int m_trackSamplesOld; // Copy of file sample rate - int m_trackSampleRateOld; + double m_trackSampleRateOld; // Mutex controlling weather the process function is in pause mode. This happens // during seek and loading of a new track diff --git a/src/engine/enginecontrol.cpp b/src/engine/enginecontrol.cpp index 632553ce5c0..14fe7dc659f 100644 --- a/src/engine/enginecontrol.cpp +++ b/src/engine/enginecontrol.cpp @@ -11,9 +11,9 @@ EngineControl::EngineControl(QString group, UserSettingsPointer pConfig) : m_group(group), m_pConfig(pConfig), - m_pEngineMaster(NULL), - m_pEngineBuffer(NULL) { - setCurrentSample(0.0, 0.0); + m_pEngineMaster(nullptr), + m_pEngineBuffer(nullptr) { + setCurrentSample(0.0, 0.0, 0.0); } EngineControl::~EngineControl() { @@ -21,17 +21,14 @@ EngineControl::~EngineControl() { void EngineControl::process(const double dRate, const double dCurrentSample, - const double dTotalSamples, const int iBufferSize) { Q_UNUSED(dRate); Q_UNUSED(dCurrentSample); - Q_UNUSED(dTotalSamples); Q_UNUSED(iBufferSize); } -void EngineControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { +void EngineControl::trackLoaded(TrackPointer pNewTrack) { Q_UNUSED(pNewTrack); - Q_UNUSED(pOldTrack); } void EngineControl::hintReader(HintVector*) { @@ -45,26 +42,15 @@ void EngineControl::setEngineBuffer(EngineBuffer* pEngineBuffer) { m_pEngineBuffer = pEngineBuffer; } -void EngineControl::setCurrentSample(const double dCurrentSample, const double dTotalSamples) { +void EngineControl::setCurrentSample( + const double dCurrentSample, const double dTotalSamples, const double dTrackSampleRate) { SampleOfTrack sot; sot.current = dCurrentSample; sot.total = dTotalSamples; + sot.rate = dTrackSampleRate; m_sampleOfTrack.setValue(sot); } -double EngineControl::getCurrentSample() const { - return m_sampleOfTrack.getValue().current; -} - -double EngineControl::getTotalSamples() const { - return m_sampleOfTrack.getValue().total; -} - -bool EngineControl::atEndPosition() const { - SampleOfTrack sot = m_sampleOfTrack.getValue(); - return (sot.current >= sot.total); -} - QString EngineControl::getGroup() const { return m_group; } diff --git a/src/engine/enginecontrol.h b/src/engine/enginecontrol.h index 1661a0b6821..3d85173a1db 100644 --- a/src/engine/enginecontrol.h +++ b/src/engine/enginecontrol.h @@ -7,6 +7,8 @@ #include #include +#include + #include "preferences/usersettings.h" #include "track/track.h" #include "control/controlvalue.h" @@ -43,9 +45,8 @@ class EngineControl : public QObject { // EngineControl can perform any upkeep operations that are necessary during // this call. virtual void process(const double dRate, - const double dCurrentSample, - const double dTotalSamples, - const int iBufferSize); + const double dCurrentSample, + const int iBufferSize); // hintReader allows the EngineControl to provide hints to the reader to // indicate that the given portion of a song is a potential imminent seek @@ -54,10 +55,8 @@ class EngineControl : public QObject { virtual void setEngineMaster(EngineMaster* pEngineMaster); void setEngineBuffer(EngineBuffer* pEngineBuffer); - virtual void setCurrentSample(const double dCurrentSample, const double dTotalSamples); - double getCurrentSample() const; - double getTotalSamples() const; - bool atEndPosition() const; + virtual void setCurrentSample(const double dCurrentSample, + const double dTotalSamples, const double dTrackSampleRate); QString getGroup() const; // Called to collect player features for effects processing. @@ -67,11 +66,18 @@ class EngineControl : public QObject { // Called whenever a seek occurs to allow the EngineControl to respond. virtual void notifySeek(double dNewPlaypo, bool adjustingPhase); - - public slots: - virtual void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack); + virtual void trackLoaded(TrackPointer pNewTrack); protected: + struct SampleOfTrack { + double current; + double total; + double rate; + }; + + SampleOfTrack getSampleOfTrack() const { + return m_sampleOfTrack.getValue(); + } void seek(double fractionalPosition); void seekAbs(double sample); // Seek to an exact sample and don't allow quantizing adjustment. @@ -86,14 +92,17 @@ class EngineControl : public QObject { UserSettingsPointer m_pConfig; private: - struct SampleOfTrack { - double current; - double total; - }; - ControlValueAtomic m_sampleOfTrack; EngineMaster* m_pEngineMaster; EngineBuffer* m_pEngineBuffer; + + + FRIEND_TEST(LoopingControlTest, ReloopToggleButton_DoesNotJumpAhead); + FRIEND_TEST(LoopingControlTest, ReloopAndStopButton); + FRIEND_TEST(LoopingControlTest, LoopScale_HalvesLoop); + FRIEND_TEST(LoopingControlTest, LoopMoveTest); + FRIEND_TEST(LoopingControlTest, LoopResizeSeek); + FRIEND_TEST(LoopingControlTest, Beatjump_JumpsByBeats); }; #endif /* ENGINECONTROL_H */ diff --git a/src/engine/keycontrol.h b/src/engine/keycontrol.h index b97f3567c24..7572e950e27 100644 --- a/src/engine/keycontrol.h +++ b/src/engine/keycontrol.h @@ -66,14 +66,13 @@ class KeyControl : public EngineControl { ControlPushButton* m_keylockMode; ControlPushButton* m_keyunlockMode; - /** The current loaded file's detected key */ + // The current loaded file's detected key ControlObject* m_pFileKey; - /** The current effective key of the engine */ + // The current effective key of the engine ControlObject* m_pEngineKey; ControlPotmeter* m_pEngineKeyDistance; - TrackPointer m_pTrack; struct PitchTempoRatio m_pitchRateInfo; QAtomicInt m_updatePitchRequest; QAtomicInt m_updatePitchAdjustRequest; diff --git a/src/engine/loopingcontrol.cpp b/src/engine/loopingcontrol.cpp index d8a92fc536e..6b5e06cd56f 100644 --- a/src/engine/loopingcontrol.cpp +++ b/src/engine/loopingcontrol.cpp @@ -304,10 +304,8 @@ void LoopingControl::slotLoopDouble(double pressed) { } void LoopingControl::process(const double dRate, - const double currentSample, - const double totalSamples, - const int iBufferSize) { - Q_UNUSED(totalSamples); + const double currentSample, + const int iBufferSize) { Q_UNUSED(iBufferSize); Q_UNUSED(dRate); @@ -456,13 +454,14 @@ void LoopingControl::hintReader(HintVector* pHintList) { void LoopingControl::setLoopInToCurrentPosition() { // set loop-in position + BeatsPointer pBeats = m_pBeats; LoopSamples loopSamples = m_loopSamples.getValue(); double quantizedBeat = -1; double pos = m_currentSample.getValue(); - if (m_pQuantizeEnabled->toBool() && m_pBeats != nullptr) { + if (m_pQuantizeEnabled->toBool() && pBeats) { if (m_bAdjustingLoopIn) { double closestBeat = m_pClosestBeat->get(); - if (closestBeat == getCurrentSample()) { + if (closestBeat == m_currentSample.getValue()) { quantizedBeat = closestBeat; } else { quantizedBeat = m_pPreviousBeat->get(); @@ -488,8 +487,8 @@ void LoopingControl::setLoopInToCurrentPosition() { // pre-defined beatloop size instead (when possible) if (loopSamples.end != kNoTrigger && (loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) { - if (quantizedBeat != -1 && m_pBeats) { - pos = m_pBeats->findNthBeat(quantizedBeat, -2); + if (quantizedBeat != -1 && pBeats) { + pos = pBeats->findNthBeat(quantizedBeat, -2); if (pos == -1 || (loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) { pos = loopSamples.end - MINIMUM_AUDIBLE_LOOP_SIZE; } @@ -513,9 +512,9 @@ void LoopingControl::setLoopInToCurrentPosition() { if (m_pQuantizeEnabled->toBool() && loopSamples.start < loopSamples.end - && m_pBeats != nullptr) { + && pBeats) { m_pCOBeatLoopSize->setAndConfirm( - m_pBeats->numBeatsInRange(loopSamples.start, loopSamples.end)); + pBeats->numBeatsInRange(loopSamples.start, loopSamples.end)); updateBeatLoopingControls(); } else { clearActiveBeatLoop(); @@ -526,7 +525,7 @@ void LoopingControl::setLoopInToCurrentPosition() { } void LoopingControl::slotLoopIn(double pressed) { - if (m_pTrack == nullptr) { + if (!m_pTrack) { return; } @@ -557,13 +556,14 @@ void LoopingControl::slotLoopInGoto(double pressed) { } void LoopingControl::setLoopOutToCurrentPosition() { + BeatsPointer pBeats = m_pBeats; LoopSamples loopSamples = m_loopSamples.getValue(); double quantizedBeat = -1; int pos = m_currentSample.getValue(); - if (m_pQuantizeEnabled->toBool() && m_pBeats != nullptr) { + if (m_pQuantizeEnabled->toBool() && pBeats) { if (m_bAdjustingLoopOut) { double closestBeat = m_pClosestBeat->get(); - if (closestBeat == getCurrentSample()) { + if (closestBeat == m_currentSample.getValue()) { quantizedBeat = closestBeat; } else { quantizedBeat = m_pNextBeat->get(); @@ -586,8 +586,8 @@ void LoopingControl::setLoopOutToCurrentPosition() { // inaudible (which can happen easily with quantize-to-beat enabled,) // use the smallest pre-defined beatloop instead (when possible) if ((pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) { - if (quantizedBeat != -1 && m_pBeats) { - pos = static_cast(floor(m_pBeats->findNthBeat(quantizedBeat, 2))); + if (quantizedBeat != -1 && pBeats) { + pos = static_cast(floor(pBeats->findNthBeat(quantizedBeat, 2))); if (pos == -1 || (pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) { pos = loopSamples.start + MINIMUM_AUDIBLE_LOOP_SIZE; } @@ -610,9 +610,9 @@ void LoopingControl::setLoopOutToCurrentPosition() { loopSamples.seek = false; } - if (m_pQuantizeEnabled->toBool() && m_pBeats != nullptr) { + if (m_pQuantizeEnabled->toBool() && pBeats) { m_pCOBeatLoopSize->setAndConfirm( - m_pBeats->numBeatsInRange(loopSamples.start, loopSamples.end)); + pBeats->numBeatsInRange(loopSamples.start, loopSamples.end)); updateBeatLoopingControls(); } else { clearActiveBeatLoop(); @@ -693,7 +693,7 @@ void LoopingControl::slotReloopToggle(double val) { if (loopSamples.start != kNoTrigger && loopSamples.end != kNoTrigger && loopSamples.start <= loopSamples.end) { setLoopingEnabled(true); - if (getCurrentSample() > loopSamples.end) { + if (m_currentSample.getValue() > loopSamples.end) { slotLoopInGoto(1); } } @@ -801,9 +801,7 @@ void LoopingControl::setLoopingEnabled(bool enabled) { } } -void LoopingControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); - +void LoopingControl::trackLoaded(TrackPointer pNewTrack) { if (m_pTrack) { disconnect(m_pTrack.get(), SIGNAL(beatsUpdated()), this, SLOT(slotUpdatedTrackBeats())); @@ -822,10 +820,10 @@ void LoopingControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) } } -void LoopingControl::slotUpdatedTrackBeats() -{ - if (m_pTrack) { - m_pBeats = m_pTrack->getBeats(); +void LoopingControl::slotUpdatedTrackBeats() { + TrackPointer pTrack = m_pTrack; + if (pTrack) { + m_pBeats = pTrack->getBeats(); } } @@ -876,7 +874,8 @@ void LoopingControl::clearActiveBeatLoop() { } bool LoopingControl::currentLoopMatchesBeatloopSize() { - if (m_pBeats == nullptr) { + BeatsPointer pBeats = m_pBeats; + if (!pBeats) { return false; } @@ -884,7 +883,7 @@ bool LoopingControl::currentLoopMatchesBeatloopSize() { // Calculate where the loop out point would be if it is a beatloop double beatLoopOutPoint = - m_pBeats->findNBeatsFromSample(loopSamples.start, m_pCOBeatLoopSize->get()); + pBeats->findNBeatsFromSample(loopSamples.start, m_pCOBeatLoopSize->get()); return loopSamples.end > beatLoopOutPoint - 2 && loopSamples.end < beatLoopOutPoint + 2; @@ -926,8 +925,8 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable } int samples = m_pTrackSamples->get(); - if (!m_pTrack || samples == 0 - || !m_pBeats) { + BeatsPointer pBeats = m_pBeats; + if (samples == 0 || !pBeats) { clearActiveBeatLoop(); m_pCOBeatLoopSize->setAndConfirm(beats); return; @@ -937,6 +936,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // give start and end defaults so we can detect problems LoopSamples newloopSamples = {kNoTrigger, kNoTrigger, false}; LoopSamples loopSamples = m_loopSamples.getValue(); + double currentSample = m_currentSample.getValue(); // Start from the current position/closest beat and // create the loop around X beats from there. @@ -944,16 +944,15 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable if (loopSamples.start != kNoTrigger) { newloopSamples.start = loopSamples.start; } else { - newloopSamples.start = getCurrentSample(); + newloopSamples.start = currentSample; } } else { // loop_in is set to the previous beat if quantize is on. The // closest beat might be ahead of play position which would cause a seek. // TODO: If in reverse, should probably choose nextBeat. - double cur_pos = getCurrentSample(); double prevBeat; double nextBeat; - m_pBeats->findPrevNextBeats(cur_pos, &prevBeat, &nextBeat); + pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat); if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { if (beats >= 1.0) { @@ -968,7 +967,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // If I press 1/4 beatloop, we want loop from 50% to 75% etc double beat_len = nextBeat - prevBeat; double loops_per_beat = 1.0 / beats; - double beat_pos = cur_pos - prevBeat; + double beat_pos = currentSample - prevBeat; int beat_frac = static_cast(floor((beat_pos / beat_len) * loops_per_beat)); @@ -976,11 +975,11 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable } } else { - newloopSamples.start = cur_pos; + newloopSamples.start = currentSample; } } - newloopSamples.end = m_pBeats->findNBeatsFromSample(newloopSamples.start, beats); + newloopSamples.end = pBeats->findNBeatsFromSample(newloopSamples.start, beats); if (newloopSamples.start >= newloopSamples.end // happens when the call above fails || newloopSamples.end > samples) { // Do not allow beat loops to go beyond the end of the track // If a track is loaded with beatloop_size larger than @@ -988,7 +987,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // the end of the track, let beatloop_size be set to // a smaller size, but not get larger. double previousBeatloopSize = m_pCOBeatLoopSize->get(); - double previousBeatloopOutPoint = m_pBeats->findNBeatsFromSample( + double previousBeatloopOutPoint = pBeats->findNBeatsFromSample( newloopSamples.start, previousBeatloopSize); if (previousBeatloopOutPoint < newloopSamples.start && beats < previousBeatloopSize) { @@ -1073,7 +1072,8 @@ void LoopingControl::slotBeatLoopRollActivate(double pressed) { } void LoopingControl::slotBeatJump(double beats) { - if (!m_pTrack || !m_pBeats) { + BeatsPointer pBeats = m_pBeats; + if (!pBeats) { return; } @@ -1086,7 +1086,7 @@ void LoopingControl::slotBeatJump(double beats) { // If inside an active loop, move loop slotLoopMove(beats); } else { - seekAbs(m_pBeats->findNBeatsFromSample(getCurrentSample(), beats)); + seekAbs(pBeats->findNBeatsFromSample(currentSample, beats)); } } @@ -1103,7 +1103,8 @@ void LoopingControl::slotBeatJumpBackward(double pressed) { } void LoopingControl::slotLoopMove(double beats) { - if (m_pTrack == nullptr || m_pBeats == nullptr || beats == 0) { + BeatsPointer pBeats = m_pBeats; + if (!pBeats || beats == 0) { return; } LoopSamples loopSamples = m_loopSamples.getValue(); @@ -1111,12 +1112,12 @@ void LoopingControl::slotLoopMove(double beats) { return; } - if (BpmControl::getBeatContext(m_pBeats, getCurrentSample(), + if (BpmControl::getBeatContext(pBeats, m_currentSample.getValue(), nullptr, nullptr, nullptr, nullptr)) { - double new_loop_in = m_pBeats->findNBeatsFromSample(loopSamples.start, beats); + double new_loop_in = pBeats->findNBeatsFromSample(loopSamples.start, beats); double new_loop_out = currentLoopMatchesBeatloopSize() ? - m_pBeats->findNBeatsFromSample(new_loop_in, m_pCOBeatLoopSize->get()) : - m_pBeats->findNBeatsFromSample(loopSamples.end, beats); + pBeats->findNBeatsFromSample(new_loop_in, m_pCOBeatLoopSize->get()) : + pBeats->findNBeatsFromSample(loopSamples.end, beats); // If we are looping make sure that the play head does not leave the // loop as a result of our adjustment. diff --git a/src/engine/loopingcontrol.h b/src/engine/loopingcontrol.h index 01d3644b121..a5ee490a5ca 100644 --- a/src/engine/loopingcontrol.h +++ b/src/engine/loopingcontrol.h @@ -35,7 +35,6 @@ class LoopingControl : public EngineControl { // the sample that should be seeked to. Otherwise it returns currentSample. void process(const double dRate, const double currentSample, - const double totalSamples, const int iBufferSize) override; // nextTrigger returns the sample at which the engine will be triggered to @@ -60,7 +59,7 @@ class LoopingControl : public EngineControl { void slotReloopAndStop(double); void slotLoopStartPos(double); void slotLoopEndPos(double); - virtual void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; + void trackLoaded(TrackPointer pNewTrack) override; void slotUpdatedTrackBeats(); // Generate a loop of 'beats' length. It can also do fractions for a @@ -158,6 +157,7 @@ class LoopingControl : public EngineControl { ControlObject* m_pCOLoopMove; QList m_loopMoves; + // objects below are written from an engine worker thread TrackPointer m_pTrack; BeatsPointer m_pBeats; }; diff --git a/src/engine/quantizecontrol.cpp b/src/engine/quantizecontrol.cpp index faebf241e17..cd638e9e711 100644 --- a/src/engine/quantizecontrol.cpp +++ b/src/engine/quantizecontrol.cpp @@ -32,8 +32,7 @@ QuantizeControl::~QuantizeControl() { delete m_pCOClosestBeat; } -void QuantizeControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); +void QuantizeControl::trackLoaded(TrackPointer pNewTrack) { if (m_pTrack) { disconnect(m_pTrack.get(), SIGNAL(beatsUpdated()), this, SLOT(slotBeatsUpdated())); @@ -58,21 +57,24 @@ void QuantizeControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack } void QuantizeControl::slotBeatsUpdated() { - if (m_pTrack) { - m_pBeats = m_pTrack->getBeats(); - lookupBeatPositions(getCurrentSample()); - updateClosestBeat(getCurrentSample()); + TrackPointer pTrack = m_pTrack; + if (pTrack) { + m_pBeats = pTrack->getBeats(); + double current = getSampleOfTrack().current; + lookupBeatPositions(current); + updateClosestBeat(current); } } void QuantizeControl::setCurrentSample(const double dCurrentSample, - const double dTotalSamples) { - if (dCurrentSample == getCurrentSample()) { + const double dTotalSamples, + const double dTrackSampleRate) { + if (dCurrentSample == getSampleOfTrack().current) { // No need to recalculate. return; } - EngineControl::setCurrentSample(dCurrentSample, dTotalSamples); + EngineControl::setCurrentSample(dCurrentSample, dTotalSamples, dTrackSampleRate); // We only need to update the prev or next if the current sample is // out of range of the existing beat positions or if we've been forced to // do so. @@ -85,9 +87,10 @@ void QuantizeControl::setCurrentSample(const double dCurrentSample, } void QuantizeControl::lookupBeatPositions(double dCurrentSample) { - if (m_pBeats) { + BeatsPointer pBeats = m_pBeats; + if (pBeats) { double prevBeat, nextBeat; - m_pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat); + pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat); m_pCOPrevBeat->set(prevBeat); m_pCONextBeat->set(nextBeat); } diff --git a/src/engine/quantizecontrol.h b/src/engine/quantizecontrol.h index 76baef63ebf..d2f828dcf67 100644 --- a/src/engine/quantizecontrol.h +++ b/src/engine/quantizecontrol.h @@ -19,10 +19,8 @@ class QuantizeControl : public EngineControl { ~QuantizeControl() override; void setCurrentSample(const double dCurrentSample, - const double dTotalSamples) override; - - public slots: - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; + const double dTotalSamples, const double dTrackSampleRate) override; + void trackLoaded(TrackPointer pNewTrack) override; private slots: void slotBeatsUpdated(); @@ -39,6 +37,7 @@ class QuantizeControl : public EngineControl { ControlObject* m_pCOPrevBeat; ControlObject* m_pCOClosestBeat; + // objects below are written from an engine worker thread TrackPointer m_pTrack; BeatsPointer m_pBeats; }; diff --git a/src/engine/ratecontrol.cpp b/src/engine/ratecontrol.cpp index 644b5e385ff..109ecce8c9b 100644 --- a/src/engine/ratecontrol.cpp +++ b/src/engine/ratecontrol.cpp @@ -18,10 +18,10 @@ #include // Static default values for rate buttons (percents) -double RateControl::m_dTemporaryRateChangeCoarse; -double RateControl::m_dTemporaryRateChangeFine; -double RateControl::m_dPermanentRateChangeCoarse; -double RateControl::m_dPermanentRateChangeFine; +ControlValueAtomic RateControl::m_dTemporaryRateChangeCoarse; +ControlValueAtomic RateControl::m_dTemporaryRateChangeFine; +ControlValueAtomic RateControl::m_dPermanentRateChangeCoarse; +ControlValueAtomic RateControl::m_dPermanentRateChangeFine; int RateControl::m_iRateRampSensitivity; RateControl::RampMode RateControl::m_eRateRampMode; @@ -35,7 +35,6 @@ RateControl::RateControl(QString group, m_ePbCurrent(0), m_ePbPressed(0), m_bTempStarted(false), - m_dTempRateChange(0.0), m_dRateTemp(0.0), m_eRampBackMode(RATERAMP_RAMPBACK_NONE), m_dRateTempRampbackChange(0.0) { @@ -233,42 +232,42 @@ void RateControl::setRateRampSensitivity(int sense) { //static void RateControl::setTemporaryRateChangeCoarseAmount(double v) { - m_dTemporaryRateChangeCoarse = v; + m_dTemporaryRateChangeCoarse.setValue(v); } //static void RateControl::setTemporaryRateChangeFineAmount(double v) { - m_dTemporaryRateChangeFine = v; + m_dTemporaryRateChangeFine.setValue(v); } //static void RateControl::setPermanentRateChangeCoarseAmount(double v) { - m_dPermanentRateChangeCoarse = v; + m_dPermanentRateChangeCoarse.setValue(v); } //static void RateControl::setPermanentRateChangeFineAmount(double v) { - m_dPermanentRateChangeFine = v; + m_dPermanentRateChangeFine.setValue(v); } //static double RateControl::getTemporaryRateChangeCoarseAmount() { - return m_dTemporaryRateChangeCoarse; + return m_dTemporaryRateChangeCoarse.getValue(); } //static double RateControl::getTemporaryRateChangeFineAmount() { - return m_dTemporaryRateChangeFine; + return m_dTemporaryRateChangeFine.getValue(); } //static double RateControl::getPermanentRateChangeCoarseAmount() { - return m_dPermanentRateChangeCoarse; + return m_dPermanentRateChangeCoarse.getValue(); } //static double RateControl::getPermanentRateChangeFineAmount() { - return m_dPermanentRateChangeFine; + return m_dPermanentRateChangeFine.getValue(); } void RateControl::slotReverseRollActivate(double v) { @@ -304,7 +303,7 @@ void RateControl::slotControlRatePermDown(double) // Adjusts temp rate down if button pressed if (buttonRatePermDown->get()) { m_pRateSlider->set(m_pRateSlider->get() - - m_pRateDir->get() * m_dPermanentRateChangeCoarse / (100 * m_pRateRange->get())); + m_pRateDir->get() * m_dPermanentRateChangeCoarse.getValue() / (100 * m_pRateRange->get())); } } @@ -313,7 +312,7 @@ void RateControl::slotControlRatePermDownSmall(double) // Adjusts temp rate down if button pressed if (buttonRatePermDownSmall->get()) m_pRateSlider->set(m_pRateSlider->get() - - m_pRateDir->get() * m_dPermanentRateChangeFine / (100. * m_pRateRange->get())); + m_pRateDir->get() * m_dPermanentRateChangeFine.getValue() / (100. * m_pRateRange->get())); } void RateControl::slotControlRatePermUp(double) @@ -321,7 +320,7 @@ void RateControl::slotControlRatePermUp(double) // Adjusts temp rate up if button pressed if (buttonRatePermUp->get()) { m_pRateSlider->set(m_pRateSlider->get() + - m_pRateDir->get() * m_dPermanentRateChangeCoarse / (100. * m_pRateRange->get())); + m_pRateDir->get() * m_dPermanentRateChangeCoarse.getValue() / (100. * m_pRateRange->get())); } } @@ -330,7 +329,7 @@ void RateControl::slotControlRatePermUpSmall(double) // Adjusts temp rate up if button pressed if (buttonRatePermUpSmall->get()) m_pRateSlider->set(m_pRateSlider->get() + - m_pRateDir->get() * m_dPermanentRateChangeFine / (100. * m_pRateRange->get())); + m_pRateDir->get() * m_dPermanentRateChangeFine.getValue() / (100. * m_pRateRange->get())); } void RateControl::slotControlRateTempDown(double) @@ -378,26 +377,17 @@ void RateControl::slotControlRateTempUp(double) } } -void RateControl::slotControlRateTempUpSmall(double) -{ +void RateControl::slotControlRateTempUpSmall(double) { // Set the state of the Temporary button. Logic is handled in ::process() - if (buttonRateTempUpSmall->get() && !(m_ePbPressed & RateControl::RATERAMP_UP)) - { + if (buttonRateTempUpSmall->get() && !(m_ePbPressed & RateControl::RATERAMP_UP)){ m_ePbPressed |= RateControl::RATERAMP_UP; m_ePbCurrent = RateControl::RATERAMP_UP; - } - else if (!buttonRateTempUpSmall->get()) - { + } else if (!buttonRateTempUpSmall->get()) { m_ePbPressed &= ~RateControl::RATERAMP_UP; m_ePbCurrent = m_ePbPressed; } } -void RateControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); - m_pTrack = pNewTrack; -} - double RateControl::calcRateRatio() const { double rateRatio = 1.0 + m_pRateDir->get() * m_pRateRange->get() * m_pRateSlider->get(); @@ -494,7 +484,7 @@ double RateControl::calculateSpeed(double baserate, double speed, bool paused, } } - double currentSample = getCurrentSample(); + double currentSample = getSampleOfTrack().current; m_pScratchController->process(currentSample, rate, iSamplesPerBuffer, baserate); // If waveform scratch is enabled, override all other controls @@ -534,13 +524,11 @@ double RateControl::calculateSpeed(double baserate, double speed, bool paused, } void RateControl::process(const double rate, - const double currentSample, - const double totalSamples, - const int bufferSamples) + const double currentSample, + const int bufferSamples) { Q_UNUSED(rate); Q_UNUSED(currentSample); - Q_UNUSED(totalSamples); /* * Code to handle temporary rate change buttons. * @@ -571,9 +559,9 @@ void RateControl::process(const double rate, return; } - double change = m_pRateDir->get() * m_dTemporaryRateChangeCoarse / + double change = m_pRateDir->get() * m_dTemporaryRateChangeCoarse.getValue() / (100. * range); - double csmall = m_pRateDir->get() * m_dTemporaryRateChangeFine / + double csmall = m_pRateDir->get() * m_dTemporaryRateChangeFine.getValue() / (100. * range); if (buttonRateTempUp->get()) @@ -585,7 +573,8 @@ void RateControl::process(const double rate, else if (buttonRateTempDownSmall->get()) subRateTemp(csmall); } else if (m_eRateRampMode == RampMode::Linear) { - m_dTemporaryRateChangeCoarse = ((double)latrate / ((double)m_iRateRampSensitivity / 100.)); + m_dTemporaryRateChangeCoarse.setValue( + ((double)latrate / ((double)m_iRateRampSensitivity / 100.))); if (m_eRampBackMode == RATERAMP_RAMPBACK_PERIOD) m_dRateTempRampbackChange = 0.0; @@ -597,9 +586,9 @@ void RateControl::process(const double rate, if (m_ePbCurrent) { // apply ramped pitchbending if (m_ePbCurrent == RateControl::RATERAMP_UP) { - addRateTemp(m_dTemporaryRateChangeCoarse); + addRateTemp(m_dTemporaryRateChangeCoarse.getValue()); } else if (m_ePbCurrent == RateControl::RATERAMP_DOWN) { - subRateTemp(m_dTemporaryRateChangeCoarse); + subRateTemp(m_dTemporaryRateChangeCoarse.getValue()); } } else if ((m_bTempStarted) || ((m_eRampBackMode != RATERAMP_RAMPBACK_NONE) diff --git a/src/engine/ratecontrol.h b/src/engine/ratecontrol.h index bd8b407fe56..309e13a8c42 100644 --- a/src/engine/ratecontrol.h +++ b/src/engine/ratecontrol.h @@ -63,9 +63,8 @@ class RateControl : public EngineControl { // Must be called during each callback of the audio thread so that // RateControl has a chance to update itself. void process(const double dRate, - const double currentSample, - const double totalSamples, - const int bufferSamples) override; + const double currentSample, + const int bufferSamples) override; // Returns the current engine rate. "reportScratching" is used to tell // the caller that the user is currently scratching, and this is used to // disable keylock. @@ -106,7 +105,6 @@ class RateControl : public EngineControl { void slotControlRateTempUpSmall(double); void slotControlFastForward(double); void slotControlFastBack(double); - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; private: double getJogFactor() const; @@ -125,10 +123,10 @@ class RateControl : public EngineControl { double getTempRate(void); // Values used when temp and perm rate buttons are pressed - static double m_dTemporaryRateChangeCoarse; - static double m_dTemporaryRateChangeFine; - static double m_dPermanentRateChangeCoarse; - static double m_dPermanentRateChangeFine; + static ControlValueAtomic m_dTemporaryRateChangeCoarse; + static ControlValueAtomic m_dTemporaryRateChangeFine; + static ControlValueAtomic m_dPermanentRateChangeCoarse; + static ControlValueAtomic m_dPermanentRateChangeFine; ControlPushButton *buttonRateTempDown; ControlPushButton *buttonRateTempDownSmall; @@ -163,8 +161,6 @@ class RateControl : public EngineControl { ControlObject* m_pSampleRate; - TrackPointer m_pTrack; - // For Master Sync BpmControl* m_pBpmControl; @@ -178,8 +174,6 @@ class RateControl : public EngineControl { // This is true if we've already started to ramp the rate bool m_bTempStarted; - // Set to the rate change used for rate temp - double m_dTempRateChange; // Set the Temporary Rate Change Mode static RampMode m_eRateRampMode; // The Rate Temp Sensitivity, the higher it is the slower it gets diff --git a/src/engine/sync/synccontrol.cpp b/src/engine/sync/synccontrol.cpp index fdab298be46..42c535a1095 100644 --- a/src/engine/sync/synccontrol.cpp +++ b/src/engine/sync/synccontrol.cpp @@ -317,8 +317,7 @@ void SyncControl::reportTrackPosition(double fractionalPlaypos) { } } -void SyncControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); +void SyncControl::trackLoaded(TrackPointer pNewTrack) { //qDebug() << getGroup() << "SyncControl::trackLoaded"; if (getSyncMode() == SYNC_MASTER) { // If we change or remove a new track while master, hand off. diff --git a/src/engine/sync/synccontrol.h b/src/engine/sync/synccontrol.h index d5020cde648..d2d127bd0d9 100644 --- a/src/engine/sync/synccontrol.h +++ b/src/engine/sync/synccontrol.h @@ -57,9 +57,7 @@ class SyncControl : public EngineControl, public Syncable { void reportTrackPosition(double fractionalPlaypos); void reportPlayerSpeed(double speed, bool scratching); - - public slots: - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; + void trackLoaded(TrackPointer pNewTrack) override; private slots: // Fired by changes in play. diff --git a/src/engine/vinylcontrolcontrol.cpp b/src/engine/vinylcontrolcontrol.cpp index 9ed43b6e54b..31248dd494a 100644 --- a/src/engine/vinylcontrolcontrol.cpp +++ b/src/engine/vinylcontrolcontrol.cpp @@ -62,9 +62,8 @@ VinylControlControl::~VinylControlControl() { delete m_pControlVinylStatus; } -void VinylControlControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { - Q_UNUSED(pOldTrack); - m_pCurrentTrack = pNewTrack; +void VinylControlControl::trackLoaded(TrackPointer pNewTrack) { + m_pTrack = pNewTrack; } void VinylControlControl::notifySeekQueued() { @@ -85,12 +84,12 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { } // Do nothing if no track is loaded. - if (!m_pCurrentTrack) { + TrackPointer pTrack = m_pTrack; + if (!pTrack) { return; } - - double total_samples = getTotalSamples(); + double total_samples = getSampleOfTrack().total; double new_playpos = round(fractionalPos * total_samples); if (m_pControlVinylEnabled->get() > 0.0 && m_pControlVinylMode->get() == MIXXX_VCMODE_RELATIVE) { @@ -107,7 +106,7 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { return; // If off, do nothing. case MIXXX_RELATIVE_CUE_ONECUE: //if onecue, just seek to the regular cue - seekExact(m_pCurrentTrack->getCuePoint()); + seekExact(pTrack->getCuePoint()); return; case MIXXX_RELATIVE_CUE_HOTCUE: // Continue processing in this function. @@ -120,7 +119,7 @@ void VinylControlControl::slotControlVinylSeek(double fractionalPos) { double shortest_distance = 0; int nearest_playpos = -1; - const QList cuePoints(m_pCurrentTrack->getCuePoints()); + const QList cuePoints(pTrack->getCuePoints()); QListIterator it(cuePoints); while (it.hasNext()) { CuePointer pCue(it.next()); diff --git a/src/engine/vinylcontrolcontrol.h b/src/engine/vinylcontrolcontrol.h index cb7e033c435..0c9783475ea 100644 --- a/src/engine/vinylcontrolcontrol.h +++ b/src/engine/vinylcontrolcontrol.h @@ -18,10 +18,10 @@ class VinylControlControl : public EngineControl { void notifySeekQueued(); bool isEnabled(); bool isScratching(); + void trackLoaded(TrackPointer pNewTrack) override; private slots: void slotControlVinylSeek(double fractionalPos); - void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) override; private: ControlObject* m_pControlVinylRate; @@ -35,7 +35,9 @@ class VinylControlControl : public EngineControl { ControlPushButton* m_pControlVinylCueing; ControlPushButton* m_pControlVinylSignalEnabled; ControlProxy* m_pPlayEnabled; - TrackPointer m_pCurrentTrack; + + TrackPointer m_pTrack; // is written from an engine worker thread + bool m_bSeekRequested; }; diff --git a/src/test/enginesynctest.cpp b/src/test/enginesynctest.cpp index 9a2d8923165..fd52ae1f47c 100644 --- a/src/test/enginesynctest.cpp +++ b/src/test/enginesynctest.cpp @@ -1395,7 +1395,7 @@ TEST_F(EngineSyncTest, UserTweakBeatDistance) { "beat_distance"))->get()); EXPECT_LT(difference, .00001); - EXPECT_FLOAT_EQ(0.0, m_pChannel1->getEngineBuffer()->m_pBpmControl->m_dUserOffset); + EXPECT_FLOAT_EQ(0.0, m_pChannel1->getEngineBuffer()->m_pBpmControl->m_dUserOffset.getValue()); } TEST_F(EngineSyncTest, MasterBpmNeverZero) { diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index 42ee3ad5dc0..cd6ba998b67 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -336,7 +336,7 @@ TEST_F(LoopingControlTest, ReloopToggleButton_DoesNotJumpAhead) { m_pButtonReloopToggle->slotSet(1); m_pButtonReloopToggle->slotSet(0); seekToSampleAndProcess(50); - EXPECT_LE(m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample(), m_pLoopStartPoint->get()); + EXPECT_LE(m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, m_pLoopStartPoint->get()); } TEST_F(LoopingControlTest, ReloopAndStopButton) { @@ -348,7 +348,7 @@ TEST_F(LoopingControlTest, ReloopAndStopButton) { m_pButtonReloopAndStop->slotSet(1); m_pButtonReloopAndStop->slotSet(0); ProcessBuffer(); - EXPECT_EQ(m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample(), m_pLoopStartPoint->get()); + EXPECT_EQ(m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, m_pLoopStartPoint->get()); EXPECT_TRUE(m_pLoopEnabled->toBool()); } @@ -375,7 +375,7 @@ TEST_F(LoopingControlTest, LoopScale_HalvesLoop) { seekToSampleAndProcess(1800); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(2000, m_pLoopEndPoint->get()); - EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); EXPECT_FALSE(isLoopEnabled()); m_pLoopScale->set(0.5); ProcessBuffer(); @@ -384,7 +384,7 @@ TEST_F(LoopingControlTest, LoopScale_HalvesLoop) { // The loop was not enabled so halving the loop should not move the playhead // even though it is outside the loop. - EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(1800, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); m_pButtonReloopToggle->slotSet(1); EXPECT_TRUE(isLoopEnabled()); @@ -512,7 +512,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_TRUE(isLoopEnabled()); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(300, m_pLoopEndPoint->get()); - EXPECT_EQ(10, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(10, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); // Move the loop out from under the playposition. m_pButtonBeatMoveForward->set(1.0); @@ -522,7 +522,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_EQ(44400, m_pLoopEndPoint->get()); ProcessBuffer(); // Should seek to the corresponding offset within the moved loop - EXPECT_EQ(44110, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(44110, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); // Move backward so that the current position is outside the new location of the loop m_pChannel1->getEngineBuffer()->queueNewPlaypos(44300, EngineBuffer::SEEK_STANDARD); @@ -534,7 +534,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_NEAR(300, m_pLoopEndPoint->get(), kLoopPositionMaxAbsError); ProcessBuffer(); EXPECT_NEAR(200, - m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample(), + m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, kLoopPositionMaxAbsError); // Now repeat the test with looping disabled (should not affect the @@ -550,7 +550,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_EQ(44400, m_pLoopEndPoint->get()); // Should not seek inside the moved loop when the loop is disabled EXPECT_NEAR(200, - m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample(), + m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, kLoopPositionMaxAbsError); // Move backward so that the current position is outside the new location of the loop @@ -562,7 +562,7 @@ TEST_F(LoopingControlTest, LoopMoveTest) { EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_NEAR(300, m_pLoopEndPoint->get(), kLoopPositionMaxAbsError); EXPECT_NEAR(500, - m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample(), + m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current, kLoopPositionMaxAbsError); } @@ -582,7 +582,7 @@ TEST_F(LoopingControlTest, LoopResizeSeek) { EXPECT_TRUE(isLoopEnabled()); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(600, m_pLoopEndPoint->get()); - EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); // Activate a shorter loop m_pButtonBeatLoop2Activate->set(1.0); @@ -594,7 +594,7 @@ TEST_F(LoopingControlTest, LoopResizeSeek) { EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(450, m_pLoopEndPoint->get()); ProcessBuffer(); - EXPECT_EQ(50, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(50, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); // But if looping is not enabled, no warping occurs. m_pLoopStartPoint->slotSet(0); @@ -604,14 +604,14 @@ TEST_F(LoopingControlTest, LoopResizeSeek) { EXPECT_FALSE(isLoopEnabled()); EXPECT_EQ(0, m_pLoopStartPoint->get()); EXPECT_EQ(600, m_pLoopEndPoint->get()); - EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); m_pButtonBeatLoop2Activate->set(1.0); ProcessBuffer(); EXPECT_EQ(500, m_pLoopStartPoint->get()); EXPECT_EQ(950, m_pLoopEndPoint->get()); - EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(500, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); } TEST_F(LoopingControlTest, BeatLoopSize_SetAndToggle) { @@ -789,11 +789,11 @@ TEST_F(LoopingControlTest, Beatjump_JumpsByBeats) { m_pButtonBeatJumpForward->set(1.0); m_pButtonBeatJumpForward->set(0.0); ProcessBuffer(); - EXPECT_EQ(beatLength * 4, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(beatLength * 4, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); m_pButtonBeatJumpBackward->set(1.0); m_pButtonBeatJumpBackward->set(0.0); ProcessBuffer(); - EXPECT_EQ(0, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getCurrentSample()); + EXPECT_EQ(0, m_pChannel1->getEngineBuffer()->m_pLoopingControl->getSampleOfTrack().current); } TEST_F(LoopingControlTest, Beatjump_MovesActiveLoop) { diff --git a/src/test/readaheadmanager_test.cpp b/src/test/readaheadmanager_test.cpp index 1573663dbc7..69281f402c3 100644 --- a/src/test/readaheadmanager_test.cpp +++ b/src/test/readaheadmanager_test.cpp @@ -61,10 +61,8 @@ class StubLoopControl : public LoopingControl { Q_UNUSED(adjustingPhase); } - public slots: - void trackLoaded(TrackPointer pTrack, TrackPointer pOldTrack) override { + void trackLoaded(TrackPointer pTrack) override { Q_UNUSED(pTrack); - Q_UNUSED(pOldTrack); } protected: diff --git a/src/util/console.cpp b/src/util/console.cpp index e953606788b..bf05fc4b404 100644 --- a/src/util/console.cpp +++ b/src/util/console.cpp @@ -177,7 +177,7 @@ Console::~Console() { } } if (m_shouldFreeConsole) { - // Note: The console has already witten the command on top of the output + // Note: The console has already written the command on top of the output // because it was originally released due to the /subsystem:windows flag. // We may send a fake "Enter" key here, using SendInput() to get a new // command prompt, but this executes a user entry unconditionally or has other diff --git a/src/util/tapfilter.cpp b/src/util/tapfilter.cpp index d91b92e791f..299ddeb2b56 100644 --- a/src/util/tapfilter.cpp +++ b/src/util/tapfilter.cpp @@ -11,9 +11,13 @@ TapFilter::~TapFilter() { } void TapFilter::tap() { + QMutexLocker locker(&m_mutex); mixxx::Duration elapsed = m_timer.restart(); if (elapsed <= m_maxInterval) { - emit(tapped(m_mean.insert(elapsed.toDoubleMillis()), m_mean.size())); + double averageLength = m_mean.insert(elapsed.toDoubleMillis()); + int numSamples = m_mean.size(); + locker.unlock(); + emit(tapped(averageLength, numSamples)); } else { // Reset the filter m_mean.clear(); diff --git a/src/util/tapfilter.h b/src/util/tapfilter.h index d5c95707d0c..d6805dcd315 100644 --- a/src/util/tapfilter.h +++ b/src/util/tapfilter.h @@ -2,6 +2,7 @@ #define TAPFILTER_H #include +#include #include "util/duration.h" #include "util/movinginterquartilemean.h" @@ -24,6 +25,7 @@ class TapFilter : public QObject { PerformanceTimer m_timer; MovingInterquartileMean m_mean; mixxx::Duration m_maxInterval; + QMutex m_mutex; }; #endif /* TAPFILTER_H */