diff options
Diffstat (limited to 'src/engine/controls/clockcontrol.cpp')
-rw-r--r-- | src/engine/controls/clockcontrol.cpp | 172 |
1 files changed, 157 insertions, 15 deletions
diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp index 6dc574ea4b..8b6c8fdd77 100644 --- a/src/engine/controls/clockcontrol.cpp +++ b/src/engine/controls/clockcontrol.cpp @@ -7,16 +7,31 @@ #include "preferences/usersettings.h" #include "track/track.h" +namespace { +constexpr double kBlinkInterval = 0.20; // LED is on 20% of the beat period +constexpr double kStandStillTolerance = + 0.005; // (seconds) Minimum change, to he last evaluated position +constexpr double kSignificiantRateThreshold = + 0.1; // If rate is significiant, update indicator also inside standstill tolerance +} // namespace + 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_InternalState = StateMachine::outsideIndicationArea; + m_NextBeatSamples = 0; + m_blinkIntervalSamples = 0; + m_pLoopEnabled = new ControlProxy(group, "loop_enabled", this); + m_pLoopStartPosition = new ControlProxy(group, "loop_start_position", this); + m_pLoopEndPosition = new ControlProxy(group, "loop_end_position", this); } ClockControl::~ClockControl() { delete m_pCOBeatActive; - delete m_pCOSampleRate; } // called from an engine worker thread @@ -30,27 +45,154 @@ 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(); +} - // TODO(XXX) should this be customizable, or latency dependent? - const double blinkSeconds = 0.100; +void ClockControl::updateIndicators(const double dRate, + const double currentSample, + const double sampleRate) { + /* This method sets the control beat_active is set to the following values: + * 0.0 --> No beat indication (ouside 20% area or play direction changed while indication was on) + * 1.0 --> Forward playing, set at the beat and set back to 0.0 at 20% of beat distance + * 2.0 --> Reverse playing, set at the beat and set back to 0.0 at -20% of beat distance + */ - // 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; + // No position change since last indicator update (e.g. deck stopped) -> No indicator update needed + // The kSignificiantRateThreshold condition ensures an immidiate indicator update, when the play/cue button is pressed + if ((currentSample <= (m_lastEvaluatedSample + kStandStillTolerance * sampleRate)) && + (currentSample >= (m_lastEvaluatedSample - kStandStillTolerance * sampleRate)) && + (fabs(dRate) <= kSignificiantRateThreshold)) { + return; + } + + // Position change more significiantly, but rate is zero. Occurs when pressing a cue point + // The m_InternalState needs to be taken into account here to prevent uneccessary events (state 0 -> state 0) + if ((dRate == 0.0) && (m_InternalState != StateMachine::outsideIndicationArea)) { + m_InternalState = StateMachine::outsideIndicationArea; + m_pCOBeatActive->forceSet(0.0); + } + + double prevIndicatorSamples; + double nextIndicatorSamples; const 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)) { + pBeats->findPrevNextBeats(currentSample, + &m_PrevBeatSamples, + &m_NextBeatSamples, + false); // Precise compare without tolerance needed + } + } else { + m_PrevBeatSamples = -1; + m_NextBeatSamples = -1; + } + + // Loops need special handling + if (m_pLoopEnabled->toBool()) { + const double loop_start_position = m_pLoopStartPosition->get(); + const double loop_end_position = m_pLoopEndPosition->get(); + + if ((m_PrevBeatSamples < loop_start_position) && (m_NextBeatSamples >= loop_end_position)) { + // No beat inside loop -> show beat indication at loop_start_position + prevIndicatorSamples = loop_start_position; + nextIndicatorSamples = loop_end_position; + } else { + prevIndicatorSamples = m_PrevBeatSamples; + nextIndicatorSamples = m_NextBeatSamples; + } + + if ((m_PrevBeatSamples != -1) && (m_NextBeatSamples != -1)) { + // Don't overwrite interval at begin/end of track + if ((loop_end_position - loop_start_position) < + (m_NextBeatSamples - m_PrevBeatSamples)) { + // Loops smaller than beat distance -> Set m_blinkIntervalSamples based on loop period + m_blinkIntervalSamples = + (loop_end_position - loop_start_position) * + kBlinkInterval; + } else { + m_blinkIntervalSamples = + (nextIndicatorSamples - prevIndicatorSamples) * + kBlinkInterval; + } + } + } else { + prevIndicatorSamples = m_PrevBeatSamples; + nextIndicatorSamples = m_NextBeatSamples; + + if ((prevIndicatorSamples != -1) && (nextIndicatorSamples != -1)) { + // Don't overwrite interval at begin/end of track + m_blinkIntervalSamples = + (nextIndicatorSamples - prevIndicatorSamples) * + kBlinkInterval; + } + } + + // The m_InternalState needs to be taken into account, to show a reliable beat indication for loops + if (dRate > 0.0) { + if (m_lastPlayDirection == true) { + if ((currentSample > prevIndicatorSamples) && + (currentSample < + prevIndicatorSamples + m_blinkIntervalSamples) && + (m_InternalState != StateMachine::afterBeatActive) && + (m_InternalState != StateMachine::afterBeatDirectionChanged)) { + m_InternalState = StateMachine::afterBeatActive; + m_pCOBeatActive->forceSet(1.0); + } else if ((currentSample > prevIndicatorSamples + + m_blinkIntervalSamples) && + ((m_InternalState == StateMachine::afterBeatActive) || + (m_InternalState == StateMachine::afterBeatDirectionChanged))) { + m_InternalState = StateMachine::outsideIndicationArea; + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and forward playing + if ((currentSample < nextIndicatorSamples) && + (currentSample >= + nextIndicatorSamples - m_blinkIntervalSamples) && + (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { + m_InternalState = StateMachine::beforeBeatDirectionChanged; + m_pCOBeatActive->forceSet(0.0); + } + } + m_lastPlayDirection = true; // Forward + } else if (dRate < 0.0) { + if (m_lastPlayDirection == false) { + if ((currentSample < nextIndicatorSamples) && + (currentSample > + nextIndicatorSamples - m_blinkIntervalSamples) && + (m_InternalState != StateMachine::beforeBeatActive) && + (m_InternalState != StateMachine::beforeBeatDirectionChanged)) { + m_InternalState = StateMachine::beforeBeatActive; + m_pCOBeatActive->forceSet(2.0); + } else if ((currentSample < nextIndicatorSamples - + m_blinkIntervalSamples) && + ((m_InternalState == StateMachine::beforeBeatActive) || + (m_InternalState == StateMachine::beforeBeatDirectionChanged))) { + m_InternalState = StateMachine::outsideIndicationArea; + m_pCOBeatActive->forceSet(0.0); + } + } else { + // Play direction changed while beat indicator was on and reverse playing + if ((currentSample > prevIndicatorSamples) && + (currentSample <= + prevIndicatorSamples + m_blinkIntervalSamples) && + (m_InternalState != StateMachine::afterBeatDirectionChanged)) { + m_InternalState = StateMachine::afterBeatDirectionChanged; + m_pCOBeatActive->forceSet(0.0); + } + } + m_lastPlayDirection = false; // Reverse } + m_lastEvaluatedSample = currentSample; } |