}
/*==========================================================================================*/
-// Scan until we get a correlation that goes over the tolerance level,
-// peaks then drops to half the peak.
-double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFrames, double tolerance )
+/** Scan until we get a correlation of a single that goes over the tolerance level,
+ * peaks then drops to half the peak.
+ * Look for inverse correlation as well.
+ */
+double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFrames, double threshold )
{
int ic,is;
// How many buffers will fit in the recording?
int maxCorrelations = recording->numFrames - numFrames;
double maxSum = 0.0;
int peakIndex = -1;
+ double inverseMaxSum = 0.0;
+ int inversePeakIndex = -1;
double location = -1.0;
QA_ASSERT_TRUE( "numFrames out of bounds", (numFrames < recording->numFrames) );
for( ic=0; ic<maxCorrelations; ic++ )
{
+ int pastPeak;
+ int inversePastPeak;
+
double sum = 0.0;
// Correlate buffer against the recording.
float *recorded = &recording->buffer[ ic ];
maxSum = sum;
peakIndex = ic;
}
- if( (maxSum > tolerance) && (sum < 0.5*maxSum) )
+ if( ((-sum) > inverseMaxSum) )
{
- location = peakIndex;
+ inverseMaxSum = -sum;
+ inversePeakIndex = ic;
+ }
+ pastPeak = (maxSum > threshold) && (sum < 0.5*maxSum);
+ inversePastPeak = (inverseMaxSum > threshold) && ((-sum) < 0.5*inverseMaxSum);
+ //printf("PaQa_FindFirstMatch: ic = %4d, sum = %8f, maxSum = %8f, inverseMaxSum = %8f\n", ic, sum, maxSum, inverseMaxSum );
+ if( pastPeak && inversePastPeak )
+ {
+ if( maxSum > inverseMaxSum )
+ {
+ location = peakIndex;
+ }
+ else
+ {
+ location = inversePeakIndex;
+ }
break;
}
}
+ //printf("PaQa_FindFirstMatch: location = %4d\n", (int)location );
return location;
error:
return -1.0;
/*==========================================================================================*/
int PaQa_MeasureLatency( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
{
+ double threshold;
PaQaSineGenerator generator;
#define MAX_BUFFER_SIZE 2048
float buffer[MAX_BUFFER_SIZE];
PaQa_EraseBuffer( buffer, cycleSize, testTone->samplesPerFrame );
PaQa_MixSine( &generator, buffer, cycleSize, testTone->samplesPerFrame );
- analysisResult->latency = PaQa_FindFirstMatch( recording, buffer, cycleSize, /* tolerance= */ 0.1 );
+ threshold = cycleSize * 0.01;
+ analysisResult->latency = PaQa_FindFirstMatch( recording, buffer, cycleSize, threshold );
QA_ASSERT_TRUE( "Could not find the start of the signal.", (analysisResult->latency >= 0) );
analysisResult->amplitudeRatio = PaQa_CompareAmplitudes( recording, analysisResult->latency, buffer, cycleSize );
return 0;
return -1;
}
-
-
/*==========================================================================================*/
// Apply cosine squared window.
void PaQa_FadeInRecording( PaQaRecording *recording, int startFrame, int count )
// Scan remaining signal looking for peak.
maxAmplitude = 0.0;
maxPosition = -1;
- for( i=0; i<hipassOutput.numFrames; i++ )
+ for( i=(int) analysisResult->latency; i<hipassOutput.numFrames; i++ )
{
float x = hipassOutput.buffer[i];
float mag = fabs( x );
/*==========================================================================================*/
/**
- * Generate a recording with pop.
+ * Generate a clean recording.
*/
-static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude )
+static void MakeCleanRecording( PaQaRecording *recording, PaQaTestTone *testTone )
{
- int i;
PaQaSineGenerator generator;
#define BUFFER_SIZE 512
float buffer[BUFFER_SIZE];
PaQa_MixSine( &generator, buffer, BUFFER_SIZE, stride );
done = PaQa_WriteRecording( recording, buffer, BUFFER_SIZE, testTone->samplesPerFrame );
}
+}
+
+/*==========================================================================================*/
+/**
+ * Generate a recording with pop.
+ */
+
+static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude )
+{
+ int i;
+
+ MakeCleanRecording( recording, testTone );
// Apply glitch to good recording.
if( (popPosition + popWidth) >= recording->numFrames )
{
int result;
+ result = TestDetectSinglePhaseError( 44100, 200, 477, -1, 0 );
+ if( result < 0 ) return result;
+/*
result = TestDetectSinglePhaseError( 44100, 200, 77, -1, 0 );
if( result < 0 ) return result;
// Note that if the frequency is too high then it is hard to detect single dropped frames.
result = TestDetectSinglePhaseError( 44100, 200, 500, 4251, -1 );
if( result < 0 ) return result;
-
+*/
return 0;
}
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
MakeRecordingWithPop( &recording, &testTone, popPosition, popWidth, popAmplitude );
-
+
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult );
#if PRINT_REPORTS
printf(" expected actual\n");
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency );
printf(" popPosition: %10.3f %10.3f\n", (double)popPosition, analysisResult.popPosition );
- printf(" popAmplitude: %10.3f %10.3f\n", (double)popAmplitude, analysisResult.popAmplitude );
+ printf(" popAmplitude: %10.3f %10.3f\n", popAmplitude, analysisResult.popAmplitude );
printf(" cycleSize: %6d\n", cycleSize );
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
return 1;
}
+/*==========================================================================================*/
+/**
+ * Analyse recording with a DC offset.
+ */
+static int TestSingleInitialSpike( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
+{
+ int i;
+ int result = 0;
+ // Account for highpass filter offset.
+ int expectedLatency = latencyFrames + 1;
+ PaQaRecording recording;
+
+ PaQaRecording hipassOutput = { 0 };
+ BiquadFilter hipassFilter;
+
+ PaQaTestTone testTone;
+ PaQaAnalysisResult analysisResult = { 0.0 };
+ int maxFrames = ((int)sampleRate) * 2;
+
+ testTone.samplesPerFrame = 1;
+ testTone.sampleRate = sampleRate;
+ testTone.frequency = sampleRate / cycleSize;
+ testTone.amplitude = -0.5;
+ testTone.startDelay = latencyFrames;
+
+ result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
+ QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
+
+ result = PaQa_InitializeRecording( &hipassOutput, maxFrames, (int) sampleRate );
+ QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
+
+ MakeCleanRecording( &recording, &testTone );
+
+ // Apply DC step.
+ for( i=stepPosition; i<recording.numFrames; i++ )
+ {
+ recording.buffer[i] += stepAmplitude;
+ }
+
+ // Use high pass as a DC blocker!
+ BiquadFilter_SetupHighPass( &hipassFilter, 10.0 / sampleRate, 0.5 );
+ PaQa_FilterRecording( &recording, &hipassOutput, &hipassFilter );
+
+ testTone.amplitude = 0.5;
+ PaQa_AnalyseRecording( &hipassOutput, &testTone, &analysisResult );
+
+#if PRINT_REPORTS
+ printf("\n=== InitialSpike Analysis ===================\n");
+ printf(" expected actual\n");
+ printf(" latency: %10.3f %10.3f\n", (double)expectedLatency, analysisResult.latency );
+ printf(" popPosition: %10.3f\n", analysisResult.popPosition );
+ printf(" popAmplitude: %10.3f\n", analysisResult.popAmplitude );
+ printf(" amplitudeRatio: %10.3f\n", analysisResult.amplitudeRatio );
+ printf(" cycleSize: %6d\n", cycleSize );
+ printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
+ printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
+ printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
+ printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
+#endif
+
+ QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", expectedLatency, analysisResult.latency, 4.0 );
+ QA_ASSERT_EQUALS( "PaQa_AnalyseRecording no pop from step", -1, (int) analysisResult.popPosition );
+ PaQa_TerminateRecording( &recording );
+ PaQa_TerminateRecording( &hipassOutput );
+ return 0;
+
+error:
+ PaQa_SaveRecordingToWaveFile( &recording, "bad_step_original.wav" );
+ PaQa_SaveRecordingToWaveFile( &hipassOutput, "bad_step_hipass.wav" );
+ PaQa_TerminateRecording( &recording);
+ PaQa_TerminateRecording( &hipassOutput );
+ return 1;
+}
+
/*==========================================================================================*/
/**
* Test various dropped sample scenarios.
return 0;
}
+/*==========================================================================================*/
+/**
+ * Test analysis when there is a DC offset step before the sine signal.
+ */
+static int TestInitialSpike( void )
+{
+ int result;
+
+ // No spike.
+ result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.0 );
+ if( result < 0 ) return result;
+
+ // Small spike.
+ result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.1 );
+ if( result < 0 ) return result;
+
+ // short pop like Ross's error.
+ result = TestSingleInitialSpike( 8000, 32, 42, 2000, 0.1 );
+ if( result < 0 ) return result;
+
+ // Medium spike.
+ result = TestSingleInitialSpike( 44100, 40, 190, 3000, 0.5 );
+ if( result < 0 ) return result;
+
+ // Spike near sine.
+ //result = TestSingleInitialSpike( 44100, 2900, 140, 3000, 0.1 );
+ if( result < 0 ) return result;
+
+
+ return 0;
+}
+
+
#if TEST_SAVED_WAVE
/*==========================================================================================*/
/**
// Detect pops that get back in phase.
if ((result = TestDetectPops()) != 0) return result;
+ // Test to see if the latency detector can be tricked like it was on Ross' Windows machine.
+ if ((result = TestInitialSpike()) != 0) return result;
+
+
return 0;
}