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.

Statistics Download as Zip
| Branch: | Tag: | Revision:

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( &notchFilter, freq / sampleRate, 0.5 );
657
        PaQa_FilterRecording( &original, &filtered, &notchFilter );
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( &notchFilter, 1.07 * freq / sampleRate, 2.0 );
666
        PaQa_FilterRecording( &original, &filtered, &notchFilter );
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
}