707 lines
22 KiB
C
707 lines
22 KiB
C
|
|
/*
|
|
* PortAudio Portable Real-Time Audio Library
|
|
* Latest Version at: http://www.portaudio.com
|
|
*
|
|
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files
|
|
* (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge,
|
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* The text above constitutes the entire PortAudio license; however,
|
|
* the PortAudio community also makes the following non-binding requests:
|
|
*
|
|
* Any person wishing to distribute modifications to the Software is
|
|
* requested to send the modifications to the original developer so that
|
|
* they can be incorporated into the canonical version. It is also
|
|
* requested that these non-binding requests be included along with the
|
|
* license above.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include "qa_tools.h"
|
|
#include "audio_analyzer.h"
|
|
#include "write_wav.h"
|
|
|
|
#define PAQA_POP_THRESHOLD (0.04)
|
|
|
|
/*==========================================================================================*/
|
|
double PaQa_GetNthFrequency( double baseFrequency, int index )
|
|
{
|
|
// Use 13 tone equal tempered scale because it does not generate harmonic ratios.
|
|
return baseFrequency * pow( 2.0, index / 13.0 );
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
void PaQa_EraseBuffer( float *buffer, int numFrames, int samplesPerFrame )
|
|
{
|
|
int i;
|
|
int numSamples = numFrames * samplesPerFrame;
|
|
for( i=0; i<numSamples; i++ )
|
|
{
|
|
*buffer++ = 0.0;
|
|
}
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
void PaQa_SetupSineGenerator( PaQaSineGenerator *generator, double frequency, double amplitude, double frameRate )
|
|
{
|
|
generator->phase = 0.0;
|
|
generator->amplitude = amplitude;
|
|
generator->frequency = frequency;
|
|
generator->phaseIncrement = 2.0 * frequency * MATH_PI / frameRate;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
void PaQa_MixSine( PaQaSineGenerator *generator, float *buffer, int numSamples, int stride )
|
|
{
|
|
int i;
|
|
for( i=0; i<numSamples; i++ )
|
|
{
|
|
float value = sinf( (float) generator->phase ) * generator->amplitude;
|
|
*buffer += value; // Mix with existing value.
|
|
buffer += stride;
|
|
// Advance phase and wrap around.
|
|
generator->phase += generator->phaseIncrement;
|
|
if (generator->phase > MATH_TWO_PI)
|
|
{
|
|
generator->phase -= MATH_TWO_PI;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
void PaQa_GenerateCrackDISABLED( float *buffer, int numSamples, int stride )
|
|
{
|
|
int i;
|
|
int offset = numSamples/2;
|
|
for( i=0; i<numSamples; i++ )
|
|
{
|
|
float phase = (MATH_TWO_PI * 0.5 * (i - offset)) / numSamples;
|
|
float cosp = cosf( phase );
|
|
float cos2 = cosp * cosp;
|
|
// invert second half of signal
|
|
float value = (i < offset) ? cos2 : (0-cos2);
|
|
*buffer = value;
|
|
buffer += stride;
|
|
}
|
|
}
|
|
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_InitializeRecording( PaQaRecording *recording, int maxFrames, int frameRate )
|
|
{
|
|
int numBytes = maxFrames * sizeof(float);
|
|
recording->buffer = (float*)malloc(numBytes);
|
|
QA_ASSERT_TRUE( "Allocate recording buffer.", (recording->buffer != NULL) );
|
|
recording->maxFrames = maxFrames; recording->sampleRate = frameRate;
|
|
recording->numFrames = 0;
|
|
return 0;
|
|
error:
|
|
return 1;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
void PaQa_TerminateRecording( PaQaRecording *recording )
|
|
{
|
|
if (recording->buffer != NULL)
|
|
{
|
|
free( recording->buffer );
|
|
recording->buffer = NULL;
|
|
}
|
|
recording->maxFrames = 0;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_WriteRecording( PaQaRecording *recording, float *buffer, int numFrames, int stride )
|
|
{
|
|
int i;
|
|
int framesToWrite;
|
|
float *data = &recording->buffer[recording->numFrames];
|
|
|
|
framesToWrite = numFrames;
|
|
if ((framesToWrite + recording->numFrames) > recording->maxFrames)
|
|
{
|
|
framesToWrite = recording->maxFrames - recording->numFrames;
|
|
}
|
|
|
|
for( i=0; i<framesToWrite; i++ )
|
|
{
|
|
*data++ = *buffer;
|
|
buffer += stride;
|
|
}
|
|
recording->numFrames += framesToWrite;
|
|
return (recording->numFrames >= recording->maxFrames);
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_WriteSilence( PaQaRecording *recording, int numFrames )
|
|
{
|
|
int i;
|
|
int framesToRecord;
|
|
float *data = &recording->buffer[recording->numFrames];
|
|
|
|
framesToRecord = numFrames;
|
|
if ((framesToRecord + recording->numFrames) > recording->maxFrames)
|
|
{
|
|
framesToRecord = recording->maxFrames - recording->numFrames;
|
|
}
|
|
|
|
for( i=0; i<framesToRecord; i++ )
|
|
{
|
|
*data++ = 0.0f;
|
|
}
|
|
recording->numFrames += framesToRecord;
|
|
return (recording->numFrames >= recording->maxFrames);
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_RecordFreeze( PaQaRecording *recording, int numFrames )
|
|
{
|
|
int i;
|
|
int framesToRecord;
|
|
float *data = &recording->buffer[recording->numFrames];
|
|
|
|
framesToRecord = numFrames;
|
|
if ((framesToRecord + recording->numFrames) > recording->maxFrames)
|
|
{
|
|
framesToRecord = recording->maxFrames - recording->numFrames;
|
|
}
|
|
|
|
for( i=0; i<framesToRecord; i++ )
|
|
{
|
|
// Copy old value forward as if the signal had frozen.
|
|
data[i] = data[i-1];
|
|
}
|
|
recording->numFrames += framesToRecord;
|
|
return (recording->numFrames >= recording->maxFrames);
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
/**
|
|
* Write recording to WAV file.
|
|
*/
|
|
int PaQa_SaveRecordingToWaveFile( PaQaRecording *recording, const char *filename )
|
|
{
|
|
WAV_Writer writer;
|
|
int result = 0;
|
|
#define NUM_SAMPLES (200)
|
|
short data[NUM_SAMPLES];
|
|
const int samplesPerFrame = 1;
|
|
int numLeft = recording->numFrames;
|
|
float *buffer = &recording->buffer[0];
|
|
|
|
result = Audio_WAV_OpenWriter( &writer, filename, recording->sampleRate, samplesPerFrame );
|
|
if( result < 0 ) goto error;
|
|
|
|
while( numLeft > 0 )
|
|
{
|
|
int i;
|
|
int numToSave = (numLeft > NUM_SAMPLES) ? NUM_SAMPLES : numLeft;
|
|
// Convert double samples to shorts.
|
|
for( i=0; i<numToSave; i++ )
|
|
{
|
|
double fval = *buffer++;
|
|
// Convert float to int and clip to short range.
|
|
int ival = fval * 32768.0;
|
|
if( ival > 32767 ) ival = 32767;
|
|
else if( ival < -32768 ) ival = -32768;
|
|
data[i] = ival;
|
|
}
|
|
result = Audio_WAV_WriteShorts( &writer, data, numToSave );
|
|
if( result < 0 ) goto error;
|
|
numLeft -= numToSave;
|
|
}
|
|
|
|
result = Audio_WAV_CloseWriter( &writer );
|
|
if( result < 0 ) goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
printf("ERROR: result = %d\n", result );
|
|
return result;
|
|
#undef NUM_SAMPLES
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
|
|
double PaQa_MeasureCrossingSlope( float *buffer, int numFrames )
|
|
{
|
|
int i;
|
|
double slopeTotal = 0.0;
|
|
int slopeCount = 0;
|
|
float previous;
|
|
double averageSlope = 0.0;
|
|
|
|
previous = buffer[0];
|
|
for( i=1; i<numFrames; i++ )
|
|
{
|
|
float current = buffer[i];
|
|
if( (current > 0.0) && (previous < 0.0) )
|
|
{
|
|
double delta = current - previous;
|
|
slopeTotal += delta;
|
|
slopeCount += 1;
|
|
}
|
|
previous = current;
|
|
}
|
|
if( slopeCount > 0 )
|
|
{
|
|
averageSlope = slopeTotal / slopeCount;
|
|
}
|
|
return averageSlope;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
/*
|
|
* We can't just measure the peaks cuz they may be clipped.
|
|
* But the zero crossing should be intact.
|
|
* The measured slope of a sine wave at zero should be:
|
|
*
|
|
* slope = sin( 2PI * frequency / sampleRate )
|
|
*
|
|
*/
|
|
double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
|
|
double frequency, double frameRate,
|
|
int startFrame, int numFrames )
|
|
{
|
|
float *buffer = &recording->buffer[startFrame];
|
|
double measuredSlope = PaQa_MeasureCrossingSlope( buffer, numFrames );
|
|
double unitySlope = sin( MATH_TWO_PI * frequency / frameRate );
|
|
double estimatedAmplitude = measuredSlope / unitySlope;
|
|
return estimatedAmplitude;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double frameRate,
|
|
int startFrame, int numFrames, double *phasePtr )
|
|
{
|
|
double magnitude = 0.0;
|
|
int numLeft = numFrames;
|
|
double phase = 0.0;
|
|
double phaseIncrement = 2.0 * MATH_PI * frequency / frameRate;
|
|
double sinAccumulator = 0.0;
|
|
double cosAccumulator = 0.0;
|
|
float *data = &recording->buffer[startFrame];
|
|
|
|
QA_ASSERT_TRUE( "startFrame out of bounds", (startFrame < recording->numFrames) );
|
|
QA_ASSERT_TRUE( "numFrames out of bounds", ((startFrame+numFrames) <= recording->numFrames) );
|
|
|
|
while( numLeft > 0 )
|
|
{
|
|
double sample = (double) *data++;
|
|
sinAccumulator += sample * sin( phase );
|
|
cosAccumulator += sample * cos( phase );
|
|
phase += phaseIncrement;
|
|
if (phase > MATH_TWO_PI)
|
|
{
|
|
phase -= MATH_TWO_PI;
|
|
}
|
|
numLeft -= 1;
|
|
}
|
|
sinAccumulator = sinAccumulator / numFrames;
|
|
cosAccumulator = cosAccumulator / numFrames;
|
|
// TODO Why do I have to multiply by 2.0? Need it to make result come out right.
|
|
magnitude = 2.0 * sqrt( (sinAccumulator * sinAccumulator) + (cosAccumulator * cosAccumulator ));
|
|
if( phasePtr != NULL )
|
|
{
|
|
double phase = atan2( cosAccumulator, sinAccumulator );
|
|
*phasePtr = phase;
|
|
}
|
|
return magnitude;
|
|
error:
|
|
return -1.0;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
void PaQa_FilterRecording( PaQaRecording *input, PaQaRecording *output, BiquadFilter *filter )
|
|
{
|
|
int numToFilter = (input->numFrames > output->maxFrames) ? output->maxFrames : input->numFrames;
|
|
BiquadFilter_Filter( filter, &input->buffer[0], &output->buffer[0], numToFilter );
|
|
output->numFrames = numToFilter;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
/** 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 ];
|
|
for( is=0; is<numFrames; is++ )
|
|
{
|
|
float s1 = buffer[is];
|
|
float s2 = *recorded++;
|
|
sum += s1 * s2;
|
|
}
|
|
if( (sum > maxSum) )
|
|
{
|
|
maxSum = sum;
|
|
peakIndex = ic;
|
|
}
|
|
if( ((-sum) > inverseMaxSum) )
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
// Measure the area under the curve by summing absolute value of each value.
|
|
double PaQa_MeasureArea( float *buffer, int numFrames, int stride )
|
|
{
|
|
int is;
|
|
double area = 0.0;
|
|
for( is=0; is<numFrames; is++ )
|
|
{
|
|
area += fabs( *buffer );
|
|
buffer += stride;
|
|
}
|
|
return area;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
// Measure the area under the curve by summing absolute value of each value.
|
|
double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames )
|
|
{
|
|
int is;
|
|
double area = 0.0;
|
|
double root;
|
|
for( is=0; is<numFrames; is++ )
|
|
{
|
|
float value = *buffer++;
|
|
area += value * value;
|
|
}
|
|
root = sqrt( area );
|
|
return root / numFrames;
|
|
}
|
|
|
|
|
|
/*==========================================================================================*/
|
|
// Compare the amplitudes of these two signals.
|
|
// Return ratio of recorded signal over buffer signal.
|
|
|
|
double PaQa_CompareAmplitudes( PaQaRecording *recording, int startAt, float *buffer, int numFrames )
|
|
{
|
|
QA_ASSERT_TRUE( "startAt+numFrames out of bounds", ((startAt+numFrames) < recording->numFrames) );
|
|
|
|
{
|
|
double recordedArea = PaQa_MeasureArea( &recording->buffer[startAt], numFrames, 1 );
|
|
double bufferArea = PaQa_MeasureArea( buffer, numFrames, 1 );
|
|
if( bufferArea == 0.0 ) return 100000000.0;
|
|
return recordedArea / bufferArea;
|
|
}
|
|
error:
|
|
return -1.0;
|
|
}
|
|
|
|
|
|
/*==========================================================================================*/
|
|
double PaQa_ComputePhaseDifference( double phase1, double phase2 )
|
|
{
|
|
double delta = phase1 - phase2;
|
|
while( delta > MATH_PI )
|
|
{
|
|
delta -= MATH_TWO_PI;
|
|
}
|
|
while( delta < -MATH_PI )
|
|
{
|
|
delta += MATH_TWO_PI;
|
|
}
|
|
return delta;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_MeasureLatency( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
|
{
|
|
double threshold;
|
|
PaQaSineGenerator generator;
|
|
#define MAX_BUFFER_SIZE 2048
|
|
float buffer[MAX_BUFFER_SIZE];
|
|
double period = testTone->sampleRate / testTone->frequency;
|
|
int cycleSize = (int) (period + 0.5);
|
|
//printf("PaQa_AnalyseRecording: frequency = %8f, frameRate = %8f, period = %8f, cycleSize = %8d\n",
|
|
// testTone->frequency, testTone->sampleRate, period, cycleSize );
|
|
analysisResult->latency = -1;
|
|
analysisResult->valid = (0);
|
|
|
|
// Set up generator to find matching first cycle.
|
|
QA_ASSERT_TRUE( "cycleSize out of bounds", (cycleSize < MAX_BUFFER_SIZE) );
|
|
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
|
|
PaQa_EraseBuffer( buffer, cycleSize, testTone->samplesPerFrame );
|
|
PaQa_MixSine( &generator, buffer, cycleSize, testTone->samplesPerFrame );
|
|
|
|
threshold = cycleSize * 0.02;
|
|
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;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
// Apply cosine squared window.
|
|
void PaQa_FadeInRecording( PaQaRecording *recording, int startFrame, int count )
|
|
{
|
|
int is;
|
|
double phase = 0.5 * MATH_PI;
|
|
// Advance a quarter wave
|
|
double phaseIncrement = 0.25 * 2.0 * MATH_PI / count;
|
|
|
|
assert( startFrame >= 0 );
|
|
assert( count > 0 );
|
|
|
|
/* Zero out initial part of the recording. */
|
|
for( is=0; is<startFrame; is++ )
|
|
{
|
|
recording->buffer[ is ] = 0.0f;
|
|
}
|
|
/* Fade in where signal begins. */
|
|
for( is=0; is<count; is++ )
|
|
{
|
|
double c = cos( phase );
|
|
double w = c * c;
|
|
float x = recording->buffer[ is + startFrame ];
|
|
float y = x * w;
|
|
//printf("FADE %d : w=%f, x=%f, y=%f\n", is, w, x, y );
|
|
recording->buffer[ is + startFrame ] = y;
|
|
|
|
phase += phaseIncrement;
|
|
}
|
|
}
|
|
|
|
|
|
/*==========================================================================================*/
|
|
/** Apply notch filter and high pass filter then detect remaining energy.
|
|
*/
|
|
int PaQa_DetectPop( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
|
{
|
|
int result = 0;
|
|
int i;
|
|
double maxAmplitude;
|
|
int maxPosition;
|
|
|
|
PaQaRecording notchOutput = { 0 };
|
|
BiquadFilter notchFilter;
|
|
|
|
PaQaRecording hipassOutput = { 0 };
|
|
BiquadFilter hipassFilter;
|
|
|
|
int frameRate = (int) recording->sampleRate;
|
|
|
|
analysisResult->popPosition = -1;
|
|
analysisResult->popAmplitude = 0.0;
|
|
|
|
result = PaQa_InitializeRecording( ¬chOutput, recording->numFrames, frameRate );
|
|
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
|
|
|
result = PaQa_InitializeRecording( &hipassOutput, recording->numFrames, frameRate );
|
|
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
|
|
|
// Use notch filter to remove test tone.
|
|
BiquadFilter_SetupNotch( ¬chFilter, testTone->frequency / frameRate, 0.5 );
|
|
PaQa_FilterRecording( recording, ¬chOutput, ¬chFilter );
|
|
//result = PaQa_SaveRecordingToWaveFile( ¬chOutput, "notch_output.wav" );
|
|
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
|
|
|
// Apply fade-in window.
|
|
PaQa_FadeInRecording( ¬chOutput, (int) analysisResult->latency, 500 );
|
|
|
|
// Use high pass to accentuate the edges of a pop. At higher frequency!
|
|
BiquadFilter_SetupHighPass( &hipassFilter, 2.0 * testTone->frequency / frameRate, 0.5 );
|
|
PaQa_FilterRecording( ¬chOutput, &hipassOutput, &hipassFilter );
|
|
//result = PaQa_SaveRecordingToWaveFile( &hipassOutput, "hipass_output.wav" );
|
|
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
|
|
|
// Scan remaining signal looking for peak.
|
|
maxAmplitude = 0.0;
|
|
maxPosition = -1;
|
|
for( i=(int) analysisResult->latency; i<hipassOutput.numFrames; i++ )
|
|
{
|
|
float x = hipassOutput.buffer[i];
|
|
float mag = fabs( x );
|
|
if( mag > maxAmplitude )
|
|
{
|
|
maxAmplitude = mag;
|
|
maxPosition = i;
|
|
}
|
|
}
|
|
|
|
if( maxAmplitude > PAQA_POP_THRESHOLD )
|
|
{
|
|
analysisResult->popPosition = maxPosition;
|
|
analysisResult->popAmplitude = maxAmplitude;
|
|
}
|
|
|
|
PaQa_TerminateRecording( ¬chOutput );
|
|
PaQa_TerminateRecording( &hipassOutput );
|
|
return 0;
|
|
|
|
error:
|
|
PaQa_TerminateRecording( ¬chOutput );
|
|
PaQa_TerminateRecording( &hipassOutput );
|
|
return -1;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_DetectPhaseError( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
|
{
|
|
int i;
|
|
double period = testTone->sampleRate / testTone->frequency;
|
|
int cycleSize = (int) (period + 0.5);
|
|
|
|
double maxAddedFrames = 0.0;
|
|
double maxDroppedFrames = 0.0;
|
|
|
|
double previousPhase = 0.0;
|
|
double previousFrameError = 0;
|
|
int loopCount = 0;
|
|
int skip = cycleSize;
|
|
int windowSize = cycleSize;
|
|
|
|
// Scan recording starting with first cycle, looking for phase errors.
|
|
analysisResult->numDroppedFrames = 0.0;
|
|
analysisResult->numAddedFrames = 0.0;
|
|
analysisResult->droppedFramesPosition = -1.0;
|
|
analysisResult->addedFramesPosition = -1.0;
|
|
|
|
for( i=analysisResult->latency; i<(recording->numFrames - windowSize); i += skip )
|
|
{
|
|
double expectedPhase = previousPhase + (skip * MATH_TWO_PI / period);
|
|
double expectedPhaseIncrement = PaQa_ComputePhaseDifference( expectedPhase, previousPhase );
|
|
|
|
double phase = 666.0;
|
|
double mag = PaQa_CorrelateSine( recording, testTone->frequency, testTone->sampleRate, i, windowSize, &phase );
|
|
if( (loopCount > 1) && (mag > 0.0) )
|
|
{
|
|
double phaseDelta = PaQa_ComputePhaseDifference( phase, previousPhase );
|
|
double phaseError = PaQa_ComputePhaseDifference( phaseDelta, expectedPhaseIncrement );
|
|
// Convert phaseError to equivalent number of frames.
|
|
double frameError = period * phaseError / MATH_TWO_PI;
|
|
double consecutiveFrameError = frameError + previousFrameError;
|
|
// if( fabs(frameError) > 0.01 )
|
|
// {
|
|
// printf("FFFFFFFFFFFFF frameError = %f, at %d\n", frameError, i );
|
|
// }
|
|
if( consecutiveFrameError > 0.8 )
|
|
{
|
|
double droppedFrames = consecutiveFrameError;
|
|
if (droppedFrames > (maxDroppedFrames * 1.001))
|
|
{
|
|
analysisResult->numDroppedFrames = droppedFrames;
|
|
analysisResult->droppedFramesPosition = i + (windowSize/2);
|
|
maxDroppedFrames = droppedFrames;
|
|
}
|
|
}
|
|
else if( consecutiveFrameError < -0.8 )
|
|
{
|
|
double addedFrames = 0 - consecutiveFrameError;
|
|
if (addedFrames > (maxAddedFrames * 1.001))
|
|
{
|
|
analysisResult->numAddedFrames = addedFrames;
|
|
analysisResult->addedFramesPosition = i + (windowSize/2);
|
|
maxAddedFrames = addedFrames;
|
|
}
|
|
}
|
|
previousFrameError = frameError;
|
|
|
|
|
|
//if( i<8000 )
|
|
//{
|
|
// printf("%d: phase = %8f, expected = %8f, delta = %8f, frameError = %8f\n", i, phase, expectedPhaseIncrement, phaseDelta, frameError );
|
|
//}
|
|
}
|
|
previousPhase = phase;
|
|
loopCount += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================================================*/
|
|
int PaQa_AnalyseRecording( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
|
{
|
|
int result = 0;
|
|
|
|
memset( analysisResult, 0, sizeof(PaQaAnalysisResult) );
|
|
result = PaQa_MeasureLatency( recording, testTone, analysisResult );
|
|
QA_ASSERT_EQUALS( "latency measurement", 0, result );
|
|
|
|
if( (analysisResult->latency >= 0) && (analysisResult->amplitudeRatio > 0.1) )
|
|
{
|
|
analysisResult->valid = (1);
|
|
|
|
result = PaQa_DetectPop( recording, testTone, analysisResult );
|
|
QA_ASSERT_EQUALS( "detect pop", 0, result );
|
|
|
|
result = PaQa_DetectPhaseError( recording, testTone, analysisResult );
|
|
QA_ASSERT_EQUALS( "detect phase error", 0, result );
|
|
}
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|