Skip to content

Commit

Permalink
Reworked beat_active control:
Browse files Browse the repository at this point in the history
-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
  • Loading branch information
JoergAtGithub committed Jan 31, 2021
1 parent c833006 commit 1196d3f
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 19 deletions.
87 changes: 72 additions & 15 deletions src/engine/controls/clockcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,27 +33,81 @@ 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;
}
}
13 changes: 12 additions & 1 deletion src/engine/controls/clockcontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 4 additions & 3 deletions src/track/beatgrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 1196d3f

Please sign in to comment.