Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved VinylControlXwax::establishQuality #3279

Merged
merged 9 commits into from
Dec 15, 2021
95 changes: 75 additions & 20 deletions src/vinylcontrol/vinylcontrolxwax.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ VinylControlXwax::VinylControlXwax(UserSettingsPointer pConfig, const QString& g
m_dVinylPositionOld(0.0),
m_pWorkBuffer(MAX_BUFFER_LEN),
m_workBufferSize(MAX_BUFFER_LEN),
m_iQualPos(0),
m_iQualFilled(0),
m_iQualityRingIndex(0),
m_iQualityRingFilled(0),
m_iQualityLastPosition(-1),
m_dQualityLastPitch(0.0),
m_iPosition(-1),
m_bAtRecordEnd(false),
m_bForceResync(false),
Expand Down Expand Up @@ -230,20 +232,20 @@ void VinylControlXwax::analyzeSamples(CSAMPLE* pSamples, size_t nFrames) {
// Check if vinyl control is enabled...
m_bIsEnabled = enabled && checkEnabled(m_bIsEnabled, enabled->toBool());

double dVinylPitch = timecoder_get_pitch(&timecoder);

if(bHaveSignal) {
// Always analyze the input samples
m_iPosition = timecoder_get_position(&timecoder, nullptr);
//Notify the UI if the timecode quality is good
establishQuality(m_iPosition != -1);
establishQuality(dVinylPitch);
}

//are we even playing and enabled at all?
if (!m_bIsEnabled) {
return;
}

double dVinylPitch = timecoder_get_pitch(&timecoder);

// Has a new track been loaded? Currently we use track duration which is
// integer seconds in the song. However, for calculations we need the
// higher-accuracy duration found by dividing the track samples by the
Expand Down Expand Up @@ -626,10 +628,10 @@ void VinylControlXwax::analyzeSamples(CSAMPLE* pSamples, size_t nFrames) {
//resetSteadyPitch(dVinylPitch, filePosition);
// Notify the UI that the timecode quality is garbage/missing.
m_fTimecodeQuality = 0.0f;
m_iPitchRingPos = 0;
m_iPitchRingFilled = 0;
m_iQualPos = 0;
m_iQualFilled = 0;
m_dQualityLastPitch = 0.0;
m_iQualityLastPosition = -1;
m_iQualityRingIndex = 0;
m_iQualityRingFilled = 0;
m_bForceResync = true;
vinylStatus->set(VINYL_STATUS_OK);
}
Expand Down Expand Up @@ -789,22 +791,75 @@ bool VinylControlXwax::uiUpdateTime(double now) {
return false;
}

void VinylControlXwax::establishQuality(bool quality_sample) {
m_bQualityRing[m_iQualPos] = quality_sample;
if (m_iQualFilled < QUALITY_RING_SIZE) {
m_iQualFilled++;
int VinylControlXwax::getPositionQuality() {
int positionQualityPercent;

if (m_iPosition == -1) {
// No position code - happens when control vinyl is spinning slow (or during scratching) or the setup is incorrect
positionQualityPercent = 0;
} else if ((m_iPosition <= m_iQualityLastPosition - 5) ||
(m_iPosition >= m_iQualityLastPosition + 5)) {
// Position code changed not by more than 5. This indicates a normal spinning control vinyl.
positionQualityPercent = 100;
} else {
// Position code changed by more than 5. This indicates a fast spinning control vinyl, a jumping needle or a false signal.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if we use a simple calculation that gradually reduces the qualitypercent as the velocity increases? Rather than a three-step threshold.

Copy link
Member Author

@JoergAtGithub JoergAtGithub Feb 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see, how this can be expressed by a linear expression:

  • Position code -1 is a special case which needs to be handled
  • +-5 codes difference happen in normal operation due to the sample rate of the quality information. If the vinyl spins a bit faster than 100% nominal speed, it's still a good signal, but we will have missing position codes.
  • Higher code differences, are either a extreme fast spinning vinyls, a jumping needle or a weak signal.

// This covers also the case of m_iQualityLastPosition == -1
positionQualityPercent = 50;
}

m_iQualityLastPosition = m_iPosition;

return positionQualityPercent;
}

int VinylControlXwax::getPitchQuality(double& pitch) {
int pitchQualityPercent;

if (m_iQualityRingFilled == 0) {
m_dQualityLastPitch = pitch;
pitchQualityPercent = 0;
return pitchQualityPercent; // First value - but at least two values needed for calculation
}

int quality = 0;
for (int i = 0; i < m_iQualFilled; ++i) {
if (m_bQualityRing[i]) {
quality++;
double pitchDifference = pitch - m_dQualityLastPitch;
m_dQualityLastPitch = pitch;

if (pitchDifference != 0) {
double pitchStability = std::fabs(pitch / pitchDifference);

if (pitchStability < 3.0) {
// Unlikely that this pitch difference is from a proper set up control vinyl
pitchQualityPercent = 0;
} else if (pitchStability > 6.0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again can you represent this set of thresholds with a simple linear equation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(1 + tanh( pitchstability + 3 ) ) * 50 could work.
But this would require much more calculation effort, in this often executed real time code, than the two comparisations.
Do you think it's worth it?

// Typical pitch difference for normal spinning control vinyl
pitchQualityPercent = 100;
} else {
// Weak pitch difference for a control vinyl slow spinning ,or for scratching
pitchQualityPercent = 75;
}
} else {
// Unlikely case of two identical float values for pitch
pitchQualityPercent = 0;
}
return pitchQualityPercent;
}

void VinylControlXwax::establishQuality(double& pitch) {
m_iQualityRing[m_iQualityRingIndex] = getPositionQuality() + getPitchQuality(pitch);
m_fTimecodeQuality = std::max<float>(0.0,
std::min<float>(1.0,
static_cast<float>(std::accumulate(m_iQualityRing,
m_iQualityRing + m_iQualityRingFilled,
0)) /
2.0f / 100.0f /
static_cast<float>(
m_iQualityRingFilled))); // Two information in percent per datapoint

if (m_iQualityRingFilled < QUALITY_RING_SIZE) {
m_iQualityRingFilled++;
}

m_fTimecodeQuality = static_cast<float>(quality) /
static_cast<float>(m_iQualFilled);
m_iQualPos = (m_iQualPos + 1) % QUALITY_RING_SIZE;
m_iQualityRingIndex = (m_iQualityRingIndex + 1) % QUALITY_RING_SIZE;
}

float VinylControlXwax::getAngle() {
Expand Down
15 changes: 10 additions & 5 deletions src/vinylcontrol/vinylcontrolxwax.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extern "C" {

#define XWAX_DEVICE_FRAME 32
#define XWAX_SMOOTHING (128 / XWAX_DEVICE_FRAME) /* result value is in frames */
#define QUALITY_RING_SIZE 100
#define QUALITY_RING_SIZE 32

class VinylControlXwax : public VinylControl {
public:
Expand All @@ -45,7 +45,9 @@ class VinylControlXwax : public VinylControl {
void enableConstantMode();
void enableConstantMode(double rate);
bool uiUpdateTime(double time);
void establishQuality(bool quality_sample);
void establishQuality(double& pitch);
int getPositionQuality();
int getPitchQuality(double& pitch);

// Cache the position of the end of record
unsigned int m_uiSafeZone;
Expand All @@ -60,9 +62,12 @@ class VinylControlXwax : public VinylControl {
// Signal quality ring buffer.
// TODO(XXX): Replace with CircularBuffer instead of handling the ring logic
// in VinylControlXwax.
bool m_bQualityRing[QUALITY_RING_SIZE];
int m_iQualPos;
int m_iQualFilled;
int m_iQualityRing[QUALITY_RING_SIZE];
int m_iQualityRingIndex;
int m_iQualityRingFilled;

int m_iQualityLastPosition;
double m_dQualityLastPitch;

// Keeps track of the most recent position as reported by xwax.
int m_iPosition;
Expand Down