To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
The primary repository for this project is hosted at https://github.com/sonic-visualiser/sv-dependency-builds .
This repository is a read-only copy which is updated automatically every hour.
root / src / portaudio_20161030_catalina_patch / qa / loopback / src / test_audio_analyzer.c @ 162:d43aab368df9
History | View | Annotate | Download (22.7 KB)
| 1 |
|
|---|---|
| 2 |
/*
|
| 3 |
* PortAudio Portable Real-Time Audio Library
|
| 4 |
* Latest Version at: http://www.portaudio.com
|
| 5 |
*
|
| 6 |
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
| 7 |
*
|
| 8 |
* Permission is hereby granted, free of charge, to any person obtaining
|
| 9 |
* a copy of this software and associated documentation files
|
| 10 |
* (the "Software"), to deal in the Software without restriction,
|
| 11 |
* including without limitation the rights to use, copy, modify, merge,
|
| 12 |
* publish, distribute, sublicense, and/or sell copies of the Software,
|
| 13 |
* and to permit persons to whom the Software is furnished to do so,
|
| 14 |
* subject to the following conditions:
|
| 15 |
*
|
| 16 |
* The above copyright notice and this permission notice shall be
|
| 17 |
* included in all copies or substantial portions of the Software.
|
| 18 |
*
|
| 19 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 20 |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 21 |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 22 |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
| 23 |
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
| 24 |
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 25 |
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 26 |
*/
|
| 27 |
|
| 28 |
/*
|
| 29 |
* The text above constitutes the entire PortAudio license; however,
|
| 30 |
* the PortAudio community also makes the following non-binding requests:
|
| 31 |
*
|
| 32 |
* Any person wishing to distribute modifications to the Software is
|
| 33 |
* requested to send the modifications to the original developer so that
|
| 34 |
* they can be incorporated into the canonical version. It is also
|
| 35 |
* requested that these non-binding requests be included along with the
|
| 36 |
* license above.
|
| 37 |
*/
|
| 38 |
|
| 39 |
#include <stdio.h> |
| 40 |
#include <stdlib.h> |
| 41 |
#include <math.h> |
| 42 |
#include "qa_tools.h" |
| 43 |
#include "audio_analyzer.h" |
| 44 |
#include "test_audio_analyzer.h" |
| 45 |
#include "write_wav.h" |
| 46 |
#include "biquad_filter.h" |
| 47 |
|
| 48 |
#define FRAMES_PER_BLOCK (64) |
| 49 |
#define PRINT_REPORTS 0 |
| 50 |
|
| 51 |
#define TEST_SAVED_WAVE (0) |
| 52 |
|
| 53 |
/*==========================================================================================*/
|
| 54 |
/**
|
| 55 |
* Detect a single tone.
|
| 56 |
*/
|
| 57 |
static int TestSingleMonoTone( void ) |
| 58 |
{
|
| 59 |
int result = 0; |
| 60 |
PaQaSineGenerator generator; |
| 61 |
PaQaRecording recording; |
| 62 |
float buffer[FRAMES_PER_BLOCK];
|
| 63 |
double sampleRate = 44100.0; |
| 64 |
int maxFrames = ((int)sampleRate) * 1; |
| 65 |
int samplesPerFrame = 1; |
| 66 |
int stride = 1; |
| 67 |
int done = 0; |
| 68 |
|
| 69 |
double freq = 234.5; |
| 70 |
double amp = 0.5; |
| 71 |
|
| 72 |
double mag1, mag2;
|
| 73 |
|
| 74 |
// Setup a sine oscillator.
|
| 75 |
PaQa_SetupSineGenerator( &generator, freq, amp, sampleRate ); |
| 76 |
|
| 77 |
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
| 78 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 79 |
|
| 80 |
done = 0;
|
| 81 |
while (!done)
|
| 82 |
{
|
| 83 |
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame ); |
| 84 |
PaQa_MixSine( &generator, buffer, FRAMES_PER_BLOCK, stride ); |
| 85 |
done = PaQa_WriteRecording( &recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame ); |
| 86 |
} |
| 87 |
|
| 88 |
mag1 = PaQa_CorrelateSine( &recording, freq, sampleRate, 0, recording.numFrames, NULL ); |
| 89 |
QA_ASSERT_CLOSE( "exact frequency match", amp, mag1, 0.01 ); |
| 90 |
|
| 91 |
mag2 = PaQa_CorrelateSine( &recording, freq * 1.23, sampleRate, 0, recording.numFrames, NULL ); |
| 92 |
QA_ASSERT_CLOSE( "wrong frequency", 0.0, mag2, 0.01 ); |
| 93 |
|
| 94 |
PaQa_TerminateRecording( &recording ); |
| 95 |
return 0; |
| 96 |
|
| 97 |
error:
|
| 98 |
PaQa_TerminateRecording( &recording); |
| 99 |
return 1; |
| 100 |
|
| 101 |
} |
| 102 |
|
| 103 |
/*==========================================================================================*/
|
| 104 |
/**
|
| 105 |
* Mix multiple tones and then detect them.
|
| 106 |
*/
|
| 107 |
|
| 108 |
static int TestMixedMonoTones( void ) |
| 109 |
{
|
| 110 |
int i;
|
| 111 |
int result = 0; |
| 112 |
#define NUM_TONES (5) |
| 113 |
PaQaSineGenerator generators[NUM_TONES]; |
| 114 |
PaQaRecording recording; |
| 115 |
float buffer[FRAMES_PER_BLOCK];
|
| 116 |
double sampleRate = 44100.0; |
| 117 |
int maxFrames = ((int)sampleRate) * 1; |
| 118 |
int samplesPerFrame = 1; |
| 119 |
|
| 120 |
double baseFreq = 234.5; |
| 121 |
double amp = 0.1; |
| 122 |
|
| 123 |
double mag2;
|
| 124 |
|
| 125 |
int stride = samplesPerFrame;
|
| 126 |
int done = 0; |
| 127 |
|
| 128 |
// Setup a sine oscillator.
|
| 129 |
for( i=0; i<NUM_TONES; i++ ) |
| 130 |
{
|
| 131 |
PaQa_SetupSineGenerator( &generators[i], PaQa_GetNthFrequency( baseFreq, i ), amp, sampleRate ); |
| 132 |
} |
| 133 |
|
| 134 |
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
| 135 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 136 |
|
| 137 |
done = 0;
|
| 138 |
while (!done)
|
| 139 |
{
|
| 140 |
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame ); |
| 141 |
for( i=0; i<NUM_TONES; i++ ) |
| 142 |
{
|
| 143 |
PaQa_MixSine( &generators[i], buffer, FRAMES_PER_BLOCK, stride ); |
| 144 |
} |
| 145 |
done = PaQa_WriteRecording( &recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame ); |
| 146 |
} |
| 147 |
|
| 148 |
for( i=0; i<NUM_TONES; i++ ) |
| 149 |
{
|
| 150 |
double mag = PaQa_CorrelateSine( &recording, PaQa_GetNthFrequency( baseFreq, i), sampleRate, 0, recording.numFrames, NULL ); |
| 151 |
QA_ASSERT_CLOSE( "exact frequency match", amp, mag, 0.01 ); |
| 152 |
} |
| 153 |
|
| 154 |
mag2 = PaQa_CorrelateSine( &recording, baseFreq * 0.87, sampleRate, 0, recording.numFrames, NULL ); |
| 155 |
QA_ASSERT_CLOSE( "wrong frequency", 0.0, mag2, 0.01 ); |
| 156 |
|
| 157 |
PaQa_TerminateRecording( &recording ); |
| 158 |
return 0; |
| 159 |
|
| 160 |
error:
|
| 161 |
PaQa_TerminateRecording( &recording); |
| 162 |
return 1; |
| 163 |
|
| 164 |
} |
| 165 |
|
| 166 |
|
| 167 |
/*==========================================================================================*/
|
| 168 |
/**
|
| 169 |
* Generate a recording with added or dropped frames.
|
| 170 |
*/
|
| 171 |
|
| 172 |
static void MakeRecordingWithAddedFrames( PaQaRecording *recording, PaQaTestTone *testTone, int glitchPosition, int framesToAdd ) |
| 173 |
{
|
| 174 |
PaQaSineGenerator generator; |
| 175 |
#define BUFFER_SIZE 512 |
| 176 |
float buffer[BUFFER_SIZE];
|
| 177 |
|
| 178 |
int frameCounter = testTone->startDelay;
|
| 179 |
|
| 180 |
int stride = 1; |
| 181 |
// Record some initial silence.
|
| 182 |
int done = PaQa_WriteSilence( recording, testTone->startDelay );
|
| 183 |
|
| 184 |
// Setup a sine oscillator.
|
| 185 |
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate ); |
| 186 |
|
| 187 |
while (!done)
|
| 188 |
{
|
| 189 |
int framesThisLoop = BUFFER_SIZE;
|
| 190 |
|
| 191 |
if( frameCounter == glitchPosition )
|
| 192 |
{
|
| 193 |
if( framesToAdd > 0 ) |
| 194 |
{
|
| 195 |
// Record some frozen data without advancing the sine generator.
|
| 196 |
done = PaQa_RecordFreeze( recording, framesToAdd ); |
| 197 |
frameCounter += framesToAdd; |
| 198 |
} |
| 199 |
else if( framesToAdd < 0 ) |
| 200 |
{
|
| 201 |
// Advance sine generator a few frames.
|
| 202 |
PaQa_MixSine( &generator, buffer, 0 - framesToAdd, stride );
|
| 203 |
} |
| 204 |
|
| 205 |
} |
| 206 |
else if( (frameCounter < glitchPosition) && ((frameCounter + framesThisLoop) > glitchPosition) ) |
| 207 |
{
|
| 208 |
// Go right up to the glitchPosition.
|
| 209 |
framesThisLoop = glitchPosition - frameCounter; |
| 210 |
} |
| 211 |
|
| 212 |
if( framesThisLoop > 0 ) |
| 213 |
{
|
| 214 |
PaQa_EraseBuffer( buffer, framesThisLoop, testTone->samplesPerFrame ); |
| 215 |
PaQa_MixSine( &generator, buffer, framesThisLoop, stride ); |
| 216 |
done = PaQa_WriteRecording( recording, buffer, framesThisLoop, testTone->samplesPerFrame ); |
| 217 |
} |
| 218 |
frameCounter += framesThisLoop; |
| 219 |
} |
| 220 |
} |
| 221 |
|
| 222 |
|
| 223 |
/*==========================================================================================*/
|
| 224 |
/**
|
| 225 |
* Generate a clean recording.
|
| 226 |
*/
|
| 227 |
|
| 228 |
static void MakeCleanRecording( PaQaRecording *recording, PaQaTestTone *testTone ) |
| 229 |
{
|
| 230 |
PaQaSineGenerator generator; |
| 231 |
#define BUFFER_SIZE 512 |
| 232 |
float buffer[BUFFER_SIZE];
|
| 233 |
|
| 234 |
int stride = 1; |
| 235 |
// Record some initial silence.
|
| 236 |
int done = PaQa_WriteSilence( recording, testTone->startDelay );
|
| 237 |
|
| 238 |
// Setup a sine oscillator.
|
| 239 |
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate ); |
| 240 |
|
| 241 |
// Generate recording with good phase.
|
| 242 |
while (!done)
|
| 243 |
{
|
| 244 |
PaQa_EraseBuffer( buffer, BUFFER_SIZE, testTone->samplesPerFrame ); |
| 245 |
PaQa_MixSine( &generator, buffer, BUFFER_SIZE, stride ); |
| 246 |
done = PaQa_WriteRecording( recording, buffer, BUFFER_SIZE, testTone->samplesPerFrame ); |
| 247 |
} |
| 248 |
} |
| 249 |
|
| 250 |
/*==========================================================================================*/
|
| 251 |
/**
|
| 252 |
* Generate a recording with pop.
|
| 253 |
*/
|
| 254 |
|
| 255 |
static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude ) |
| 256 |
{
|
| 257 |
int i;
|
| 258 |
|
| 259 |
MakeCleanRecording( recording, testTone ); |
| 260 |
|
| 261 |
// Apply glitch to good recording.
|
| 262 |
if( (popPosition + popWidth) >= recording->numFrames )
|
| 263 |
{
|
| 264 |
popWidth = (recording->numFrames - popPosition) - 1;
|
| 265 |
} |
| 266 |
|
| 267 |
for( i=0; i<popWidth; i++ ) |
| 268 |
{
|
| 269 |
float good = recording->buffer[i+popPosition];
|
| 270 |
float bad = (good > 0.0) ? (good - popAmplitude) : (good + popAmplitude); |
| 271 |
recording->buffer[i+popPosition] = bad; |
| 272 |
} |
| 273 |
} |
| 274 |
|
| 275 |
/*==========================================================================================*/
|
| 276 |
/**
|
| 277 |
* Detect one phase error in a recording.
|
| 278 |
*/
|
| 279 |
static int TestDetectSinglePhaseError( double sampleRate, int cycleSize, int latencyFrames, int glitchPosition, int framesAdded ) |
| 280 |
{
|
| 281 |
int result = 0; |
| 282 |
PaQaRecording recording; |
| 283 |
PaQaTestTone testTone; |
| 284 |
PaQaAnalysisResult analysisResult = { 0.0 };
|
| 285 |
int framesDropped = 0; |
| 286 |
int maxFrames = ((int)sampleRate) * 2; |
| 287 |
|
| 288 |
testTone.samplesPerFrame = 1;
|
| 289 |
testTone.sampleRate = sampleRate; |
| 290 |
testTone.frequency = sampleRate / cycleSize; |
| 291 |
testTone.amplitude = 0.5; |
| 292 |
testTone.startDelay = latencyFrames; |
| 293 |
|
| 294 |
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
| 295 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 296 |
|
| 297 |
MakeRecordingWithAddedFrames( &recording, &testTone, glitchPosition, framesAdded ); |
| 298 |
|
| 299 |
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult ); |
| 300 |
|
| 301 |
if( framesAdded < 0 ) |
| 302 |
{
|
| 303 |
framesDropped = -framesAdded; |
| 304 |
framesAdded = 0;
|
| 305 |
} |
| 306 |
|
| 307 |
#if PRINT_REPORTS
|
| 308 |
printf("\n=== Dropped Frame Analysis ===================\n");
|
| 309 |
printf(" expected actual\n");
|
| 310 |
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency ); |
| 311 |
printf(" num added frames: %10.3f %10.3f\n", (double)framesAdded, analysisResult.numAddedFrames ); |
| 312 |
printf(" added frames at: %10.3f %10.3f\n", (double)glitchPosition, analysisResult.addedFramesPosition ); |
| 313 |
printf(" num dropped frames: %10.3f %10.3f\n", (double)framesDropped, analysisResult.numDroppedFrames ); |
| 314 |
printf(" dropped frames at: %10.3f %10.3f\n", (double)glitchPosition, analysisResult.droppedFramesPosition ); |
| 315 |
#endif
|
| 316 |
|
| 317 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", latencyFrames, analysisResult.latency, 0.5 ); |
| 318 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording framesAdded", framesAdded, analysisResult.numAddedFrames, 1.0 ); |
| 319 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording framesDropped", framesDropped, analysisResult.numDroppedFrames, 1.0 ); |
| 320 |
// QA_ASSERT_CLOSE( "PaQa_AnalyseRecording glitchPosition", glitchPosition, analysisResult.glitchPosition, cycleSize );
|
| 321 |
|
| 322 |
PaQa_TerminateRecording( &recording ); |
| 323 |
return 0; |
| 324 |
|
| 325 |
error:
|
| 326 |
PaQa_TerminateRecording( &recording); |
| 327 |
return 1; |
| 328 |
} |
| 329 |
|
| 330 |
/*==========================================================================================*/
|
| 331 |
/**
|
| 332 |
* Test various dropped sample scenarios.
|
| 333 |
*/
|
| 334 |
static int TestDetectPhaseErrors( void ) |
| 335 |
{
|
| 336 |
int result;
|
| 337 |
|
| 338 |
result = TestDetectSinglePhaseError( 44100, 200, 477, -1, 0 ); |
| 339 |
if( result < 0 ) return result; |
| 340 |
/*
|
| 341 |
result = TestDetectSinglePhaseError( 44100, 200, 77, -1, 0 );
|
| 342 |
if( result < 0 ) return result;
|
| 343 |
|
| 344 |
result = TestDetectSinglePhaseError( 44100, 200, 83, 3712, 9 );
|
| 345 |
if( result < 0 ) return result;
|
| 346 |
|
| 347 |
result = TestDetectSinglePhaseError( 44100, 280, 83, 3712, 27 );
|
| 348 |
if( result < 0 ) return result;
|
| 349 |
|
| 350 |
result = TestDetectSinglePhaseError( 44100, 200, 234, 3712, -9 );
|
| 351 |
if( result < 0 ) return result;
|
| 352 |
|
| 353 |
result = TestDetectSinglePhaseError( 44100, 200, 2091, 8923, -2 );
|
| 354 |
if( result < 0 ) return result;
|
| 355 |
|
| 356 |
result = TestDetectSinglePhaseError( 44100, 120, 1782, 5772, -18 );
|
| 357 |
if( result < 0 ) return result;
|
| 358 |
|
| 359 |
// Note that if the frequency is too high then it is hard to detect single dropped frames.
|
| 360 |
result = TestDetectSinglePhaseError( 44100, 200, 500, 4251, -1 );
|
| 361 |
if( result < 0 ) return result;
|
| 362 |
*/
|
| 363 |
return 0; |
| 364 |
} |
| 365 |
|
| 366 |
/*==========================================================================================*/
|
| 367 |
/**
|
| 368 |
* Detect one pop in a recording.
|
| 369 |
*/
|
| 370 |
static int TestDetectSinglePop( double sampleRate, int cycleSize, int latencyFrames, int popPosition, int popWidth, double popAmplitude ) |
| 371 |
{
|
| 372 |
int result = 0; |
| 373 |
PaQaRecording recording; |
| 374 |
PaQaTestTone testTone; |
| 375 |
PaQaAnalysisResult analysisResult = { 0.0 };
|
| 376 |
int maxFrames = ((int)sampleRate) * 2; |
| 377 |
|
| 378 |
testTone.samplesPerFrame = 1;
|
| 379 |
testTone.sampleRate = sampleRate; |
| 380 |
testTone.frequency = sampleRate / cycleSize; |
| 381 |
testTone.amplitude = 0.5; |
| 382 |
testTone.startDelay = latencyFrames; |
| 383 |
|
| 384 |
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
| 385 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 386 |
|
| 387 |
MakeRecordingWithPop( &recording, &testTone, popPosition, popWidth, popAmplitude ); |
| 388 |
|
| 389 |
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult ); |
| 390 |
|
| 391 |
#if PRINT_REPORTS
|
| 392 |
printf("\n=== Pop Analysis ===================\n");
|
| 393 |
printf(" expected actual\n");
|
| 394 |
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency ); |
| 395 |
printf(" popPosition: %10.3f %10.3f\n", (double)popPosition, analysisResult.popPosition ); |
| 396 |
printf(" popAmplitude: %10.3f %10.3f\n", popAmplitude, analysisResult.popAmplitude );
|
| 397 |
printf(" cycleSize: %6d\n", cycleSize );
|
| 398 |
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
|
| 399 |
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
|
| 400 |
printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
|
| 401 |
printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
|
| 402 |
#endif
|
| 403 |
|
| 404 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", latencyFrames, analysisResult.latency, 0.5 ); |
| 405 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording popPosition", popPosition, analysisResult.popPosition, 10 ); |
| 406 |
if( popWidth > 0 ) |
| 407 |
{
|
| 408 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording popAmplitude", popAmplitude, analysisResult.popAmplitude, 0.1 * popAmplitude ); |
| 409 |
} |
| 410 |
|
| 411 |
PaQa_TerminateRecording( &recording ); |
| 412 |
return 0; |
| 413 |
|
| 414 |
error:
|
| 415 |
PaQa_SaveRecordingToWaveFile( &recording, "bad_recording.wav" );
|
| 416 |
PaQa_TerminateRecording( &recording); |
| 417 |
return 1; |
| 418 |
} |
| 419 |
|
| 420 |
/*==========================================================================================*/
|
| 421 |
/**
|
| 422 |
* Analyse recording with a DC offset.
|
| 423 |
*/
|
| 424 |
static int TestSingleInitialSpike( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude ) |
| 425 |
{
|
| 426 |
int i;
|
| 427 |
int result = 0; |
| 428 |
// Account for highpass filter offset.
|
| 429 |
int expectedLatency = latencyFrames + 1; |
| 430 |
PaQaRecording recording; |
| 431 |
|
| 432 |
PaQaRecording hipassOutput = { 0 };
|
| 433 |
BiquadFilter hipassFilter; |
| 434 |
|
| 435 |
PaQaTestTone testTone; |
| 436 |
PaQaAnalysisResult analysisResult = { 0.0 };
|
| 437 |
int maxFrames = ((int)sampleRate) * 2; |
| 438 |
|
| 439 |
testTone.samplesPerFrame = 1;
|
| 440 |
testTone.sampleRate = sampleRate; |
| 441 |
testTone.frequency = sampleRate / cycleSize; |
| 442 |
testTone.amplitude = -0.5; |
| 443 |
testTone.startDelay = latencyFrames; |
| 444 |
|
| 445 |
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
| 446 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 447 |
|
| 448 |
result = PaQa_InitializeRecording( &hipassOutput, maxFrames, (int) sampleRate );
|
| 449 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 450 |
|
| 451 |
MakeCleanRecording( &recording, &testTone ); |
| 452 |
|
| 453 |
// Apply DC step.
|
| 454 |
for( i=stepPosition; i<recording.numFrames; i++ )
|
| 455 |
{
|
| 456 |
recording.buffer[i] += stepAmplitude; |
| 457 |
} |
| 458 |
|
| 459 |
// Use high pass as a DC blocker!
|
| 460 |
BiquadFilter_SetupHighPass( &hipassFilter, 10.0 / sampleRate, 0.5 ); |
| 461 |
PaQa_FilterRecording( &recording, &hipassOutput, &hipassFilter ); |
| 462 |
|
| 463 |
testTone.amplitude = 0.5; |
| 464 |
PaQa_AnalyseRecording( &hipassOutput, &testTone, &analysisResult ); |
| 465 |
|
| 466 |
#if PRINT_REPORTS
|
| 467 |
printf("\n=== InitialSpike Analysis ===================\n");
|
| 468 |
printf(" expected actual\n");
|
| 469 |
printf(" latency: %10.3f %10.3f\n", (double)expectedLatency, analysisResult.latency ); |
| 470 |
printf(" popPosition: %10.3f\n", analysisResult.popPosition );
|
| 471 |
printf(" popAmplitude: %10.3f\n", analysisResult.popAmplitude );
|
| 472 |
printf(" amplitudeRatio: %10.3f\n", analysisResult.amplitudeRatio );
|
| 473 |
printf(" cycleSize: %6d\n", cycleSize );
|
| 474 |
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
|
| 475 |
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
|
| 476 |
printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
|
| 477 |
printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
|
| 478 |
#endif
|
| 479 |
|
| 480 |
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", expectedLatency, analysisResult.latency, 4.0 ); |
| 481 |
QA_ASSERT_EQUALS( "PaQa_AnalyseRecording no pop from step", -1, (int) analysisResult.popPosition ); |
| 482 |
PaQa_TerminateRecording( &recording ); |
| 483 |
PaQa_TerminateRecording( &hipassOutput ); |
| 484 |
return 0; |
| 485 |
|
| 486 |
error:
|
| 487 |
PaQa_SaveRecordingToWaveFile( &recording, "bad_step_original.wav" );
|
| 488 |
PaQa_SaveRecordingToWaveFile( &hipassOutput, "bad_step_hipass.wav" );
|
| 489 |
PaQa_TerminateRecording( &recording); |
| 490 |
PaQa_TerminateRecording( &hipassOutput ); |
| 491 |
return 1; |
| 492 |
} |
| 493 |
|
| 494 |
/*==========================================================================================*/
|
| 495 |
/**
|
| 496 |
* Test various dropped sample scenarios.
|
| 497 |
*/
|
| 498 |
static int TestDetectPops( void ) |
| 499 |
{
|
| 500 |
int result;
|
| 501 |
|
| 502 |
// No pop.
|
| 503 |
result = TestDetectSinglePop( 44100, 200, 477, -1, 0, 0.0 ); |
| 504 |
if( result < 0 ) return result; |
| 505 |
|
| 506 |
// short pop
|
| 507 |
result = TestDetectSinglePop( 44100, 300, 810, 3987, 1, 0.5 ); |
| 508 |
if( result < 0 ) return result; |
| 509 |
|
| 510 |
// medium long pop
|
| 511 |
result = TestDetectSinglePop( 44100, 300, 810, 9876, 5, 0.5 ); |
| 512 |
if( result < 0 ) return result; |
| 513 |
|
| 514 |
// short tiny pop
|
| 515 |
result = TestDetectSinglePop( 44100, 250, 810, 5672, 1, 0.05 ); |
| 516 |
if( result < 0 ) return result; |
| 517 |
|
| 518 |
|
| 519 |
return 0; |
| 520 |
} |
| 521 |
|
| 522 |
/*==========================================================================================*/
|
| 523 |
/**
|
| 524 |
* Test analysis when there is a DC offset step before the sine signal.
|
| 525 |
*/
|
| 526 |
static int TestInitialSpike( void ) |
| 527 |
{
|
| 528 |
int result;
|
| 529 |
|
| 530 |
//( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
|
| 531 |
// No spike.
|
| 532 |
result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.0 ); |
| 533 |
if( result < 0 ) return result; |
| 534 |
|
| 535 |
// Small spike.
|
| 536 |
result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.1 ); |
| 537 |
if( result < 0 ) return result; |
| 538 |
|
| 539 |
// short pop like Ross's error.
|
| 540 |
result = TestSingleInitialSpike( 8000, 32, 42, 2000, 0.1 ); |
| 541 |
if( result < 0 ) return result; |
| 542 |
|
| 543 |
// Medium spike.
|
| 544 |
result = TestSingleInitialSpike( 44100, 40, 190, 3000, 0.5 ); |
| 545 |
if( result < 0 ) return result; |
| 546 |
|
| 547 |
// Spike near sine.
|
| 548 |
//result = TestSingleInitialSpike( 44100, 2900, 140, 3000, 0.1 );
|
| 549 |
if( result < 0 ) return result; |
| 550 |
|
| 551 |
|
| 552 |
return 0; |
| 553 |
} |
| 554 |
|
| 555 |
|
| 556 |
#if TEST_SAVED_WAVE
|
| 557 |
/*==========================================================================================*/
|
| 558 |
/**
|
| 559 |
* Simple test that writes a sawtooth waveform to a file.
|
| 560 |
*/
|
| 561 |
static int TestSavedWave() |
| 562 |
{
|
| 563 |
int i,j;
|
| 564 |
WAV_Writer writer; |
| 565 |
int result = 0; |
| 566 |
#define NUM_SAMPLES (200) |
| 567 |
short data[NUM_SAMPLES];
|
| 568 |
short saw = 0; |
| 569 |
|
| 570 |
|
| 571 |
result = Audio_WAV_OpenWriter( &writer, "test_sawtooth.wav", 44100, 1 ); |
| 572 |
if( result < 0 ) goto error; |
| 573 |
|
| 574 |
for( i=0; i<15; i++ ) |
| 575 |
{
|
| 576 |
for( j=0; j<NUM_SAMPLES; j++ ) |
| 577 |
{
|
| 578 |
data[j] = saw; |
| 579 |
saw += 293;
|
| 580 |
} |
| 581 |
result = Audio_WAV_WriteShorts( &writer, data, NUM_SAMPLES ); |
| 582 |
if( result < 0 ) goto error; |
| 583 |
} |
| 584 |
|
| 585 |
result = Audio_WAV_CloseWriter( &writer ); |
| 586 |
if( result < 0 ) goto error; |
| 587 |
|
| 588 |
|
| 589 |
return 0; |
| 590 |
|
| 591 |
error:
|
| 592 |
printf("ERROR: result = %d\n", result );
|
| 593 |
return result;
|
| 594 |
} |
| 595 |
#endif /* TEST_SAVED_WAVE */ |
| 596 |
|
| 597 |
/*==========================================================================================*/
|
| 598 |
/**
|
| 599 |
* Easy way to generate a sine tone recording.
|
| 600 |
*/
|
| 601 |
void PaQa_FillWithSine( PaQaRecording *recording, double sampleRate, double freq, double amp ) |
| 602 |
{
|
| 603 |
PaQaSineGenerator generator; |
| 604 |
float buffer[FRAMES_PER_BLOCK];
|
| 605 |
int samplesPerFrame = 1; |
| 606 |
int stride = 1; |
| 607 |
int done = 0; |
| 608 |
|
| 609 |
// Setup a sine oscillator.
|
| 610 |
PaQa_SetupSineGenerator( &generator, freq, amp, sampleRate ); |
| 611 |
|
| 612 |
done = 0;
|
| 613 |
while (!done)
|
| 614 |
{
|
| 615 |
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame ); |
| 616 |
PaQa_MixSine( &generator, buffer, FRAMES_PER_BLOCK, stride ); |
| 617 |
done = PaQa_WriteRecording( recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame ); |
| 618 |
} |
| 619 |
|
| 620 |
} |
| 621 |
|
| 622 |
/*==========================================================================================*/
|
| 623 |
/**
|
| 624 |
* Generate a tone then knock it out using a filter.
|
| 625 |
* Also check using filter slightly off tune to see if some energy gets through.
|
| 626 |
*/
|
| 627 |
static int TestNotchFilter( void ) |
| 628 |
{
|
| 629 |
int result = 0; |
| 630 |
PaQaRecording original = { 0 };
|
| 631 |
PaQaRecording filtered = { 0 };
|
| 632 |
BiquadFilter notchFilter; |
| 633 |
double sampleRate = 44100.0; |
| 634 |
int maxFrames = ((int)sampleRate) * 1; |
| 635 |
|
| 636 |
double freq = 234.5; |
| 637 |
double amp = 0.5; |
| 638 |
|
| 639 |
double mag1, mag2, mag3;
|
| 640 |
|
| 641 |
result = PaQa_InitializeRecording( &original, maxFrames, (int) sampleRate );
|
| 642 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 643 |
|
| 644 |
PaQa_FillWithSine( &original, sampleRate, freq, amp ); |
| 645 |
|
| 646 |
//result = PaQa_SaveRecordingToWaveFile( &original, "original.wav" );
|
| 647 |
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
| 648 |
|
| 649 |
mag1 = PaQa_CorrelateSine( &original, freq, sampleRate, 0, original.numFrames, NULL ); |
| 650 |
QA_ASSERT_CLOSE( "exact frequency match", amp, mag1, 0.01 ); |
| 651 |
|
| 652 |
// Filter with exact frequency.
|
| 653 |
result = PaQa_InitializeRecording( &filtered, maxFrames, (int) sampleRate );
|
| 654 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result ); |
| 655 |
|
| 656 |
BiquadFilter_SetupNotch( ¬chFilter, freq / sampleRate, 0.5 ); |
| 657 |
PaQa_FilterRecording( &original, &filtered, ¬chFilter ); |
| 658 |
result = PaQa_SaveRecordingToWaveFile( &filtered, "filtered1.wav" );
|
| 659 |
QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result ); |
| 660 |
|
| 661 |
mag2 = PaQa_CorrelateSine( &filtered, freq, sampleRate, 0, filtered.numFrames, NULL ); |
| 662 |
QA_ASSERT_CLOSE( "should eliminate tone", 0.0, mag2, 0.01 ); |
| 663 |
|
| 664 |
// Filter with mismatched frequency.
|
| 665 |
BiquadFilter_SetupNotch( ¬chFilter, 1.07 * freq / sampleRate, 2.0 ); |
| 666 |
PaQa_FilterRecording( &original, &filtered, ¬chFilter ); |
| 667 |
|
| 668 |
//result = PaQa_SaveRecordingToWaveFile( &filtered, "badfiltered.wav" );
|
| 669 |
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
| 670 |
|
| 671 |
mag3 = PaQa_CorrelateSine( &filtered, freq, sampleRate, 0, filtered.numFrames, NULL ); |
| 672 |
QA_ASSERT_CLOSE( "should eliminate tone", amp*0.26, mag3, 0.01 ); |
| 673 |
|
| 674 |
|
| 675 |
PaQa_TerminateRecording( &original ); |
| 676 |
PaQa_TerminateRecording( &filtered ); |
| 677 |
return 0; |
| 678 |
|
| 679 |
error:
|
| 680 |
PaQa_TerminateRecording( &original); |
| 681 |
PaQa_TerminateRecording( &filtered ); |
| 682 |
return 1; |
| 683 |
|
| 684 |
} |
| 685 |
|
| 686 |
/*==========================================================================================*/
|
| 687 |
/**
|
| 688 |
*/
|
| 689 |
int PaQa_TestAnalyzer( void ) |
| 690 |
{
|
| 691 |
int result;
|
| 692 |
|
| 693 |
#if TEST_SAVED_WAVE
|
| 694 |
// Write a simple wave file.
|
| 695 |
if ((result = TestSavedWave()) != 0) return result; |
| 696 |
#endif /* TEST_SAVED_WAVE */ |
| 697 |
|
| 698 |
// Generate single tone and verify presence.
|
| 699 |
if ((result = TestSingleMonoTone()) != 0) return result; |
| 700 |
|
| 701 |
// Generate prime series of tones and verify presence.
|
| 702 |
if ((result = TestMixedMonoTones()) != 0) return result; |
| 703 |
|
| 704 |
// Detect dropped or added samples in a sine wave recording.
|
| 705 |
if ((result = TestDetectPhaseErrors()) != 0) return result; |
| 706 |
|
| 707 |
// Test to see if notch filter can knock out the test tone.
|
| 708 |
if ((result = TestNotchFilter()) != 0) return result; |
| 709 |
|
| 710 |
// Detect pops that get back in phase.
|
| 711 |
if ((result = TestDetectPops()) != 0) return result; |
| 712 |
|
| 713 |
// Test to see if the latency detector can be tricked like it was on Ross' Windows machine.
|
| 714 |
if ((result = TestInitialSpike()) != 0) return result; |
| 715 |
|
| 716 |
|
| 717 |
return 0; |
| 718 |
} |