From 4719d5e9529e39b64e46e3cf46fe06a6106e0e66 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sat, 30 Jan 2021 19:03:20 +0100 Subject: [PATCH] Reworked beat_active control: -Made CO beat_active read-only -Update from engine::updateIndicators instead of process, which isn't updated enough for a reliable beat indicator -Fixed accuracy issue in beatgrid.cpp (compare to 1/100 of a beat period can be severalsamples off) -Don't calculate if deck is not playing -Set beat_active at the beat not +-50ms before/after -More states -Period of beat_active on is no longer hard coded 200ms, it's no 20% of the peat period --- src/engine/controls/clockcontrol.cpp | 96 ++++++++++++++++++++++++---- src/engine/controls/clockcontrol.h | 13 +++- src/engine/enginebuffer.cpp | 2 + src/track/beatgrid.cpp | 7 +- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 22cd4707bc9..ed1d27e7a28 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -10,13 +10,16 @@ ClockControl::ClockControl(const QString& group, UserSettingsPointer pConfig) : EngineControl(group, pConfig) { m_pCOBeatActive = new ControlObject(ConfigKey(group, "beat_active")); - m_pCOBeatActive->set(0.0); - m_pCOSampleRate = new ControlProxy("[Master]","samplerate"); + m_pCOBeatActive->setReadOnly(); + m_pCOBeatActive->forceSet(0.0); + m_lastEvaluatedSample = 0; + m_PrevBeatSamples = 0; + m_PrevBeatSamples = 0; + m_blinkIntervalSamples = 0; } ClockControl::~ClockControl() { delete m_pCOBeatActive; - delete m_pCOSampleRate; } // called from an engine worker thread @@ -30,27 +33,92 @@ void ClockControl::trackLoaded(TrackPointer pNewTrack) { void ClockControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { // Clear on-beat control - m_pCOBeatActive->set(0.0); + m_pCOBeatActive->forceSet(0.0); m_pBeats = pBeats; } void ClockControl::process(const double dRate, - const double currentSample, - const int iBuffersize) { + const double currentSample, + const int iBuffersize) { + Q_UNUSED(dRate); + Q_UNUSED(currentSample); Q_UNUSED(iBuffersize); - double samplerate = m_pCOSampleRate->get(); +} + +void ClockControl::updateIndicators(const double dRate, + const double currentSample) { + /* This method sets the control beat_active is set to the following values: + * +1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance + * +0.5 --> Direction changed to reverse playing while forward playing indication was on + * 0.0 --> No beat indication + * -0.5 --> Direction changed to forward playing while reverse playing indication was on + * -1.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance + */ // TODO(XXX) should this be customizable, or latency dependent? - const double blinkSeconds = 0.100; + const double kBlinkInterval = 0.20; // LED is on 20% of the beat period - // Multiply by two to get samples from frames. Interval is scaled linearly - // by the rate. - const double blinkIntervalSamples = 2.0 * samplerate * (1.0 * dRate) * blinkSeconds; + if ((currentSample == m_lastEvaluatedSample) || + (dRate == 0.0)) { + return; // No position change (e.g. deck stopped) -> No indicator update needed + } mixxx::BeatsPointer pBeats = m_pBeats; if (pBeats) { - double closestBeat = pBeats->findClosestBeat(currentSample); - double distanceToClosestBeat = fabs(currentSample - closestBeat); - m_pCOBeatActive->set(distanceToClosestBeat < blinkIntervalSamples / 2.0); + if ((currentSample >= m_NextBeatSamples) || + (currentSample <= m_PrevBeatSamples)) { + //qDebug() << "### findPrevNextBeats ### " << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples; + + pBeats->findPrevNextBeats( + currentSample, &m_PrevBeatSamples, &m_NextBeatSamples); + + m_blinkIntervalSamples = (m_NextBeatSamples - m_PrevBeatSamples) * kBlinkInterval; + } + //qDebug() << "dRate:" << dRate << " m_lastPlayDirection:" << m_lastPlayDirection << " m_pCOBeatActive->get(): " << m_pCOBeatActive->get() << " currentSample: " << currentSample << " m_lastEvaluatedSample: " << m_lastEvaluatedSample << " m_PrevBeatSamples: " << m_PrevBeatSamples << " m_NextBeatSamples: " << m_NextBeatSamples << " m_blinkIntervalSamples: " << m_blinkIntervalSamples; + + if (dRate >= 0.0) { + if (m_lastPlayDirection == true) { + if ((currentSample > m_PrevBeatSamples) && + (currentSample < + m_PrevBeatSamples + m_blinkIntervalSamples) && + (m_pCOBeatActive->get() < 0.5)) { + m_pCOBeatActive->forceSet(1.0); + } else if ((currentSample > m_PrevBeatSamples + + m_blinkIntervalSamples) && + (m_pCOBeatActive->get() > 0.0)) { + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and forward playing + if ((currentSample < m_NextBeatSamples) && + (currentSample >= + m_NextBeatSamples - m_blinkIntervalSamples)) { + m_pCOBeatActive->forceSet(-0.5); + } + } + m_lastPlayDirection = true; // Forward + } else { + if (m_lastPlayDirection == false) { + if ((currentSample < m_NextBeatSamples) && + (currentSample > + m_NextBeatSamples - m_blinkIntervalSamples) && + (m_pCOBeatActive->get() > -0.5)) { + m_pCOBeatActive->forceSet(-1.0); + } else if ((currentSample < m_NextBeatSamples - + m_blinkIntervalSamples) && + (m_pCOBeatActive->get() < 0.0)) { + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and reverse playing + if ((currentSample > m_PrevBeatSamples) && + (currentSample <= + m_PrevBeatSamples + m_blinkIntervalSamples)) { + m_pCOBeatActive->forceSet(0.5); + } + } + m_lastPlayDirection = false; // Reverse + } + m_lastEvaluatedSample = currentSample; } } diff --git a/src/engine/controls/clockcontrol.h b/src/engine/controls/clockcontrol.h index 87667db443a..a3600f5f2ec 100644 --- a/src/engine/controls/clockcontrol.h +++ b/src/engine/controls/clockcontrol.h @@ -19,12 +19,23 @@ class ClockControl: public EngineControl { void process(const double dRate, const double currentSample, const int iBufferSize) override; + void updateIndicators(const double dRate, + const double currentSample); + void trackLoaded(TrackPointer pNewTrack) override; void trackBeatsUpdated(mixxx::BeatsPointer pBeats) override; private: ControlObject* m_pCOBeatActive; - ControlProxy* m_pCOSampleRate; + + double m_lastEvaluatedSample; + + double m_PrevBeatSamples; + double m_NextBeatSamples; + double m_blinkIntervalSamples; + + // True is forward direction, False is reverse + bool m_lastPlayDirection; // m_pBeats is written from an engine worker thread mixxx::BeatsPointer m_pBeats; diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index da5cea027b5..0f3cc43a65c 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1350,6 +1350,8 @@ void EngineBuffer::updateIndicators(double speed, int iBufferSize) { (double)iBufferSize / m_trackSamplesOld, fractionalPlayposFromAbsolute(m_dSlipPosition), tempoTrackSeconds); + + m_pClockControl->updateIndicators(speed * m_baserate_old, m_filepos_play); } void EngineBuffer::hintReader(const double dRate) { diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 375c7f6dd3c..36bfad9863d 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -233,9 +233,10 @@ bool BeatGrid::findPrevNextBeats(double dSamples, double prevBeat = floor(beatFraction); double nextBeat = ceil(beatFraction); - // If the position is within 1/100th of the next or previous beat, treat it - // as if it is that beat. - const double kEpsilon = .01; + // If the position is within 1/1000,000,000th of the next or previous beat, treat it + // as if it is that beat. This value ensures safe float comparisation and that the + // accuracy is always better one sample. + const double kEpsilon = 1e-09; if (fabs(nextBeat - beatFraction) < kEpsilon) { beatFraction = nextBeat;