gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

paqa.c (49814B)


      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 <memory.h>
     42 #include <math.h>
     43 #include <string.h>
     44 
     45 #include "portaudio.h"
     46 
     47 #include "qa_tools.h"
     48 
     49 #include "paqa_tools.h"
     50 #include "audio_analyzer.h"
     51 #include "test_audio_analyzer.h"
     52 
     53 /** Accumulate counts for how many tests pass or fail. */
     54 int g_testsPassed = 0;
     55 int g_testsFailed = 0;
     56 
     57 #define MAX_NUM_GENERATORS                   (8)
     58 #define MAX_NUM_RECORDINGS                   (8)
     59 #define MAX_BACKGROUND_NOISE_RMS             (0.0004)
     60 #define LOOPBACK_DETECTION_DURATION_SECONDS  (0.8)
     61 #define DEFAULT_FRAMES_PER_BUFFER            (0)
     62 #define PAQA_WAIT_STREAM_MSEC                (100)
     63 #define PAQA_TEST_DURATION                   (1.2)
     64 
     65 // Use two separate streams instead of one full duplex stream.
     66 #define PAQA_FLAG_TWO_STREAMS       (1<<0)
     67 // Use bloching read/write for loopback.
     68 #define PAQA_FLAG_USE_BLOCKING_IO   (1<<1)
     69 
     70 const char * s_FlagOnNames[] =
     71 {
     72 	"Two Streams (Half Duplex)",
     73 	"Blocking Read/Write"
     74 };
     75 
     76 const char * s_FlagOffNames[] =
     77 {
     78 	"One Stream (Full Duplex)",
     79 	"Callback"
     80 };
     81 
     82 
     83 /** Parameters that describe a single test run. */
     84 typedef struct TestParameters_s
     85 {
     86 	PaStreamParameters inputParameters;
     87 	PaStreamParameters outputParameters;
     88 	double             sampleRate;
     89 	int                samplesPerFrame;
     90 	int                framesPerBuffer;
     91 	int                maxFrames;
     92 	double             baseFrequency;
     93 	double             amplitude;
     94     PaStreamFlags      streamFlags;  // paClipOff, etc
     95 	int                flags;        // PAQA_FLAG_TWO_STREAMS, PAQA_FLAG_USE_BLOCKING_IO
     96 } TestParameters;
     97 
     98 typedef struct LoopbackContext_s
     99 {
    100 	// Generate a unique signal on each channel.
    101 	PaQaSineGenerator  generators[MAX_NUM_GENERATORS];
    102 	// Record each channel individually.
    103 	PaQaRecording      recordings[MAX_NUM_RECORDINGS];
    104 	
    105 	// Reported by the stream after it's opened
    106 	PaTime             streamInfoInputLatency;
    107 	PaTime             streamInfoOutputLatency;
    108 
    109 	// Measured at runtime.
    110 	volatile int       callbackCount; // incremented for each callback
    111 	volatile int       inputBufferCount; // incremented if input buffer not NULL
    112 	int                inputUnderflowCount;
    113 	int                inputOverflowCount;
    114 	
    115 	volatile int       outputBufferCount; // incremented if output buffer not NULL
    116 	int                outputOverflowCount;
    117 	int                outputUnderflowCount;
    118 	
    119     // Measure whether input or output is lagging behind.
    120     volatile int       minInputOutputDelta;
    121     volatile int       maxInputOutputDelta;
    122     
    123 	int                minFramesPerBuffer;
    124 	int                maxFramesPerBuffer;
    125 	int                primingCount;
    126 	TestParameters    *test;
    127 	volatile int       done;
    128 } LoopbackContext;
    129 
    130 typedef struct UserOptions_s
    131 {
    132 	int           sampleRate;
    133 	int           framesPerBuffer;
    134 	int           inputLatency;
    135 	int           outputLatency;
    136 	int           saveBadWaves;
    137 	int           verbose;
    138 	int           waveFileCount;
    139 	const char   *waveFilePath;
    140 	PaDeviceIndex inputDevice;
    141 	PaDeviceIndex outputDevice;
    142 } UserOptions;
    143 
    144 #define BIG_BUFFER_SIZE  (sizeof(float) * 2 * 2 * 1024)
    145 static unsigned char g_ReadWriteBuffer[BIG_BUFFER_SIZE];
    146 
    147 #define MAX_CONVERSION_SAMPLES   (2 * 32 * 1024)
    148 #define CONVERSION_BUFFER_SIZE  (sizeof(float) * 2 * MAX_CONVERSION_SAMPLES)
    149 static unsigned char g_ConversionBuffer[CONVERSION_BUFFER_SIZE];
    150 
    151 /*******************************************************************/
    152 static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuffer,
    153 						unsigned long framesPerBuffer,
    154 						const PaStreamCallbackTimeInfo* timeInfo,
    155 						PaStreamCallbackFlags statusFlags,
    156 						void *userData )
    157 {
    158 	int i;
    159 	LoopbackContext *loopbackContext = (LoopbackContext *) userData;
    160 	
    161 	
    162 	loopbackContext->callbackCount += 1;
    163 	if( statusFlags & paInputUnderflow ) loopbackContext->inputUnderflowCount += 1;
    164 	if( statusFlags & paInputOverflow ) loopbackContext->inputOverflowCount += 1;
    165 	if( statusFlags & paOutputUnderflow ) loopbackContext->outputUnderflowCount += 1;
    166 	if( statusFlags & paOutputOverflow ) loopbackContext->outputOverflowCount += 1;
    167 	if( statusFlags & paPrimingOutput ) loopbackContext->primingCount += 1;
    168 	if( framesPerBuffer > loopbackContext->maxFramesPerBuffer )
    169 	{
    170 		loopbackContext->maxFramesPerBuffer = framesPerBuffer;
    171 	}
    172 	if( framesPerBuffer < loopbackContext->minFramesPerBuffer )
    173 	{
    174 		loopbackContext->minFramesPerBuffer = framesPerBuffer;
    175 	}
    176 	
    177     /* This may get called with NULL inputBuffer during initial setup.
    178 	 * We may also use the same callback with output only streams.
    179 	 */
    180 	if( inputBuffer != NULL)
    181 	{
    182 		int channelsPerFrame = loopbackContext->test->inputParameters.channelCount;
    183 		float *in = (float *)inputBuffer;
    184 		PaSampleFormat inFormat = loopbackContext->test->inputParameters.sampleFormat;
    185 		
    186 		loopbackContext->inputBufferCount += 1;
    187 		
    188 		if( inFormat != paFloat32 )
    189 		{
    190 			int samplesToConvert = framesPerBuffer * channelsPerFrame;
    191 			in = (float *) g_ConversionBuffer;
    192 			if( samplesToConvert > MAX_CONVERSION_SAMPLES )
    193 			{
    194 				// Hack to prevent buffer overflow.
    195 				// @todo Loop with small buffer instead of failing.
    196 				printf("Format conversion buffer too small!\n");
    197 				return paComplete;
    198 			}
    199 			PaQa_ConvertToFloat( inputBuffer, samplesToConvert, inFormat, (float *) g_ConversionBuffer );
    200 		}
    201 		
    202 		// Read each channel from the buffer.
    203 		for( i=0; i<channelsPerFrame; i++ )
    204 		{			
    205 			loopbackContext->done |= PaQa_WriteRecording( &loopbackContext->recordings[i],
    206 										in + i, 
    207 										framesPerBuffer,
    208 										channelsPerFrame );
    209 		}
    210 	}
    211 	
    212 	if( outputBuffer != NULL )
    213 	{
    214 		int channelsPerFrame = loopbackContext->test->outputParameters.channelCount;
    215 		float *out = (float *)outputBuffer;
    216 		PaSampleFormat outFormat = loopbackContext->test->outputParameters.sampleFormat;
    217 		
    218 		loopbackContext->outputBufferCount += 1;
    219 		
    220 		if( outFormat != paFloat32 )
    221 		{
    222 			// If we need to convert then mix to the g_ConversionBuffer and then convert into the PA outputBuffer.
    223 			out = (float *) g_ConversionBuffer;
    224 		}
    225 			
    226 		PaQa_EraseBuffer( out, framesPerBuffer, channelsPerFrame );
    227 		for( i=0; i<channelsPerFrame; i++ )
    228 		{
    229 			PaQa_MixSine( &loopbackContext->generators[i],
    230 						 out + i,
    231 						 framesPerBuffer,
    232 						 channelsPerFrame );
    233 		}
    234 		
    235 		if( outFormat != paFloat32 )
    236 		{
    237 			int samplesToConvert = framesPerBuffer * channelsPerFrame;
    238 			if( samplesToConvert > MAX_CONVERSION_SAMPLES )
    239 			{
    240 				printf("Format conversion buffer too small!\n");
    241 				return paComplete;
    242 			}			
    243 			PaQa_ConvertFromFloat( out, framesPerBuffer * channelsPerFrame, outFormat, outputBuffer );
    244 		}
    245 		
    246 	}
    247     
    248     // Measure whether the input or output are lagging behind.
    249     // Don't measure lag at end.
    250     if( !loopbackContext->done )
    251     {        
    252         int inputOutputDelta = loopbackContext->inputBufferCount - loopbackContext->outputBufferCount;
    253         if( loopbackContext->maxInputOutputDelta < inputOutputDelta )
    254         {
    255             loopbackContext->maxInputOutputDelta = inputOutputDelta;
    256         }
    257         if( loopbackContext->minInputOutputDelta > inputOutputDelta )
    258         {
    259             loopbackContext->minInputOutputDelta = inputOutputDelta;
    260         }
    261     }
    262     
    263 	return loopbackContext->done ? paComplete : paContinue;
    264 }
    265 
    266 static void CopyStreamInfoToLoopbackContext( LoopbackContext *loopbackContext, PaStream *inputStream, PaStream *outputStream )
    267 {
    268 	const PaStreamInfo *inputStreamInfo = Pa_GetStreamInfo( inputStream );
    269 	const PaStreamInfo *outputStreamInfo = Pa_GetStreamInfo( outputStream );
    270 
    271 	loopbackContext->streamInfoInputLatency = inputStreamInfo ? inputStreamInfo->inputLatency : -1;
    272 	loopbackContext->streamInfoOutputLatency = outputStreamInfo ? outputStreamInfo->outputLatency : -1;
    273 }
    274 
    275 /*******************************************************************/
    276 /** 
    277  * Open a full duplex audio stream.
    278  * Generate sine waves on the output channels and record the input channels.
    279  * Then close the stream.
    280  * @return 0 if OK or negative error.
    281  */
    282 int PaQa_RunLoopbackFullDuplex( LoopbackContext *loopbackContext )
    283 {
    284 	PaStream *stream = NULL;
    285 	PaError err = 0;
    286 	TestParameters *test = loopbackContext->test;
    287 	loopbackContext->done = 0;
    288 	// Use one full duplex stream.
    289 	err = Pa_OpenStream(
    290 					&stream,
    291 					&test->inputParameters,
    292 					&test->outputParameters,
    293 					test->sampleRate,
    294 					test->framesPerBuffer,
    295 					paClipOff, /* we won't output out of range samples so don't bother clipping them */
    296 					RecordAndPlaySinesCallback,
    297 					loopbackContext );
    298 	if( err != paNoError ) goto error;
    299 	
    300 	CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream );
    301 
    302 	err = Pa_StartStream( stream );
    303 	if( err != paNoError ) goto error;
    304 		
    305 	// Wait for stream to finish.
    306 	while( loopbackContext->done == 0 )
    307 	{
    308 		Pa_Sleep(PAQA_WAIT_STREAM_MSEC);
    309 	}
    310 	
    311 	err = Pa_StopStream( stream );
    312 	if( err != paNoError ) goto error;
    313 
    314 	err = Pa_CloseStream( stream );
    315 	if( err != paNoError ) goto error;
    316 		
    317 	return 0;
    318 	
    319 error:
    320 	return err;	
    321 }
    322 
    323 /*******************************************************************/
    324 /** 
    325  * Open two audio streams, one for input and one for output.
    326  * Generate sine waves on the output channels and record the input channels.
    327  * Then close the stream.
    328  * @return 0 if OK or paTimedOut.
    329  */
    330 
    331 int PaQa_WaitForStream( LoopbackContext *loopbackContext )
    332 {
    333 	int timeoutMSec = 1000 * PAQA_TEST_DURATION * 2;
    334 	
    335 	// Wait for stream to finish or timeout.
    336 	while( (loopbackContext->done == 0) && (timeoutMSec > 0) )
    337 	{
    338 		Pa_Sleep(PAQA_WAIT_STREAM_MSEC);
    339 		timeoutMSec -= PAQA_WAIT_STREAM_MSEC;
    340 	}
    341 	
    342 	if( loopbackContext->done == 0 )
    343 	{
    344 		printf("ERROR - stream completion timed out!");
    345 		return paTimedOut;
    346 	}
    347 	return 0;
    348 }
    349 
    350 /*******************************************************************/
    351 /** 
    352  * Open two audio streams, one for input and one for output.
    353  * Generate sine waves on the output channels and record the input channels.
    354  * Then close the stream.
    355  * @return 0 if OK or negative error.
    356  */
    357 int PaQa_RunLoopbackHalfDuplex( LoopbackContext *loopbackContext )
    358 {
    359 	PaStream *inStream = NULL;
    360 	PaStream *outStream = NULL;
    361 	PaError err = 0;
    362 	int timedOut = 0;
    363 	TestParameters *test = loopbackContext->test;
    364 	loopbackContext->done = 0;
    365 	
    366 	// Use two half duplex streams.
    367 	err = Pa_OpenStream(
    368 						&inStream,
    369 						&test->inputParameters,
    370 						NULL,
    371 						test->sampleRate,
    372 						test->framesPerBuffer,
    373 						test->streamFlags,
    374 						RecordAndPlaySinesCallback,
    375 						loopbackContext );
    376 	if( err != paNoError ) goto error;
    377 	err = Pa_OpenStream(
    378 						&outStream,
    379 						NULL,
    380 						&test->outputParameters,
    381 						test->sampleRate,
    382 						test->framesPerBuffer,
    383 						test->streamFlags,
    384 						RecordAndPlaySinesCallback,
    385 						loopbackContext );
    386 	if( err != paNoError ) goto error;
    387 
    388 	CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream );
    389 
    390 	err = Pa_StartStream( inStream );
    391 	if( err != paNoError ) goto error;
    392 	
    393 	// Start output later so we catch the beginning of the waveform.
    394 	err = Pa_StartStream( outStream );
    395 	if( err != paNoError ) goto error;
    396 	
    397 	timedOut = PaQa_WaitForStream( loopbackContext );
    398 	
    399 	err = Pa_StopStream( inStream );
    400 	if( err != paNoError ) goto error;
    401 		
    402 	err = Pa_StopStream( outStream );
    403 	if( err != paNoError ) goto error;
    404 	
    405 	err = Pa_CloseStream( inStream );
    406 	if( err != paNoError ) goto error;
    407 	
    408 	err = Pa_CloseStream( outStream );
    409 	if( err != paNoError ) goto error;
    410 	
    411 	return timedOut;
    412 	
    413 error:
    414 	return err;	
    415 }
    416 
    417 
    418 /*******************************************************************/
    419 /** 
    420  * Open one audio streams, just for input.
    421  * Record background level.
    422  * Then close the stream.
    423  * @return 0 if OK or negative error.
    424  */
    425 int PaQa_RunInputOnly( LoopbackContext *loopbackContext )
    426 {
    427 	PaStream *inStream = NULL;
    428 	PaError err = 0;
    429 	int timedOut = 0;
    430 	TestParameters *test = loopbackContext->test;
    431 	loopbackContext->done = 0;
    432 	
    433 	// Just open an input stream.
    434 	err = Pa_OpenStream(
    435 						&inStream,
    436 						&test->inputParameters,
    437 						NULL,
    438 						test->sampleRate,
    439 						test->framesPerBuffer,
    440 						paClipOff, /* We won't output out of range samples so don't bother clipping them. */
    441 						RecordAndPlaySinesCallback,
    442 						loopbackContext );
    443 	if( err != paNoError ) goto error;
    444 
    445 	err = Pa_StartStream( inStream );
    446 	if( err != paNoError ) goto error;
    447 	
    448 	timedOut = PaQa_WaitForStream( loopbackContext );
    449 	
    450 	err = Pa_StopStream( inStream );
    451 	if( err != paNoError ) goto error;
    452 	
    453 	err = Pa_CloseStream( inStream );
    454 	if( err != paNoError ) goto error;
    455 	
    456 	return timedOut;
    457 	
    458 error:
    459 	return err;	
    460 }
    461 
    462 /*******************************************************************/
    463 static int RecordAndPlayBlockingIO( PaStream *inStream,
    464 									  PaStream *outStream,
    465 									  LoopbackContext *loopbackContext
    466 									  )
    467 {	
    468 	int i;
    469 	float *in = (float *)g_ReadWriteBuffer;
    470 	float *out = (float *)g_ReadWriteBuffer;
    471 	PaError err;
    472 	int done = 0;
    473 	long available;
    474 	const long maxPerBuffer = 64;
    475 	TestParameters *test = loopbackContext->test;
    476 	long framesPerBuffer = test->framesPerBuffer;
    477 	if( framesPerBuffer <= 0 )
    478 	{
    479 		framesPerBuffer = maxPerBuffer; // bigger values might run past end of recording
    480 	}
    481 	
    482 	// Read in audio.
    483 	err = Pa_ReadStream( inStream, in, framesPerBuffer );
    484 	// Ignore an overflow on the first read.
    485 	//if( !((loopbackContext->callbackCount == 0) && (err == paInputOverflowed)) )
    486 	if( err != paInputOverflowed )
    487 	{
    488 		QA_ASSERT_EQUALS( "Pa_ReadStream failed", paNoError, err );
    489 	}
    490 	else
    491 	{
    492 		loopbackContext->inputOverflowCount += 1;
    493 	}
    494 
    495 	
    496 	// Save in a recording.
    497 	for( i=0; i<loopbackContext->test->inputParameters.channelCount; i++ )
    498 	{
    499 		done |= PaQa_WriteRecording( &loopbackContext->recordings[i],
    500 		         in + i,
    501 		         framesPerBuffer,
    502 		         loopbackContext->test->inputParameters.channelCount );
    503 	}
    504 	
    505 	// Synthesize audio.
    506 	available = Pa_GetStreamWriteAvailable( outStream );
    507 	if( available > (2*framesPerBuffer) ) available = (2*framesPerBuffer);
    508 	PaQa_EraseBuffer( out, available, loopbackContext->test->outputParameters.channelCount );
    509 	for( i=0; i<loopbackContext->test->outputParameters.channelCount; i++ )
    510 	{
    511 		PaQa_MixSine( &loopbackContext->generators[i],
    512 		          out + i,
    513 		          available,
    514 		          loopbackContext->test->outputParameters.channelCount );
    515 	}
    516 	
    517 	// Write out audio.
    518 	err = Pa_WriteStream( outStream, out, available );
    519 	// Ignore an underflow on the first write.
    520 	//if( !((loopbackContext->callbackCount == 0) && (err == paOutputUnderflowed)) )
    521 	if( err != paOutputUnderflowed )
    522 	{
    523 		QA_ASSERT_EQUALS( "Pa_WriteStream failed", paNoError, err );
    524 	}
    525 	else
    526 	{
    527 		loopbackContext->outputUnderflowCount += 1;
    528 	}
    529 	
    530 		
    531 	loopbackContext->callbackCount += 1;
    532 	
    533 	return done;
    534 error:
    535 	return err;
    536 }
    537 
    538 
    539 /*******************************************************************/
    540 /** 
    541  * Open two audio streams with non-blocking IO.
    542  * Generate sine waves on the output channels and record the input channels.
    543  * Then close the stream.
    544  * @return 0 if OK or negative error.
    545  */
    546 int PaQa_RunLoopbackHalfDuplexBlockingIO( LoopbackContext *loopbackContext )
    547 {
    548 	PaStream *inStream = NULL;
    549 	PaStream *outStream = NULL;
    550 	PaError err = 0;
    551 	TestParameters *test = loopbackContext->test;
    552 	
    553 	// Use two half duplex streams.
    554 	err = Pa_OpenStream(
    555 						&inStream,
    556 						&test->inputParameters,
    557 						NULL,
    558 						test->sampleRate,
    559 						test->framesPerBuffer,
    560 						paClipOff, /* we won't output out of range samples so don't bother clipping them */
    561 						NULL, // causes non-blocking IO
    562 						NULL );
    563 	if( err != paNoError ) goto error1;
    564 	err = Pa_OpenStream(
    565 						&outStream,
    566 						NULL,
    567 						&test->outputParameters,
    568 						test->sampleRate,
    569 						test->framesPerBuffer,
    570 						paClipOff, /* we won't output out of range samples so don't bother clipping them */
    571 						NULL, // causes non-blocking IO
    572 						NULL );
    573 	if( err != paNoError ) goto error2;
    574 	
    575 	CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream );
    576 
    577 	err = Pa_StartStream( outStream );
    578 	if( err != paNoError ) goto error3;
    579 	
    580 	err = Pa_StartStream( inStream );
    581 	if( err != paNoError ) goto error3;
    582 	
    583 	while( err == 0 )
    584 	{
    585 		err = RecordAndPlayBlockingIO( inStream, outStream, loopbackContext );
    586 		if( err < 0 ) goto error3;
    587 	}
    588 	
    589 	err = Pa_StopStream( inStream );
    590 	if( err != paNoError ) goto error3;
    591 	
    592 	err = Pa_StopStream( outStream );
    593 	if( err != paNoError ) goto error3;
    594 	
    595 	err = Pa_CloseStream( outStream );
    596 	if( err != paNoError ) goto error2;
    597 	
    598 	err = Pa_CloseStream( inStream );
    599 	if( err != paNoError ) goto error1;
    600 	
    601 	
    602 	return 0;
    603 	
    604 error3:
    605 	Pa_CloseStream( outStream );
    606 error2:
    607 	Pa_CloseStream( inStream );
    608 error1:
    609 	return err;	
    610 }
    611 
    612 
    613 /*******************************************************************/
    614 /** 
    615  * Open one audio stream with non-blocking IO.
    616  * Generate sine waves on the output channels and record the input channels.
    617  * Then close the stream.
    618  * @return 0 if OK or negative error.
    619  */
    620 int PaQa_RunLoopbackFullDuplexBlockingIO( LoopbackContext *loopbackContext )
    621 {
    622 	PaStream *stream = NULL;
    623 	PaError err = 0;
    624 	TestParameters *test = loopbackContext->test;
    625 	
    626 	// Use one full duplex stream.
    627 	err = Pa_OpenStream(
    628 						&stream,
    629 						&test->inputParameters,
    630 						&test->outputParameters,
    631 						test->sampleRate,
    632 						test->framesPerBuffer,
    633 						paClipOff, /* we won't output out of range samples so don't bother clipping them */
    634 						NULL, // causes non-blocking IO
    635 						NULL );
    636 	if( err != paNoError ) goto error1;
    637 		
    638 	CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream );
    639 
    640 	err = Pa_StartStream( stream );
    641 	if( err != paNoError ) goto error2;
    642 	
    643 	while( err == 0 )
    644 	{
    645 		err = RecordAndPlayBlockingIO( stream, stream, loopbackContext );
    646 		if( err < 0 ) goto error2;
    647 	}
    648 	
    649 	err = Pa_StopStream( stream );
    650 	if( err != paNoError ) goto error2;
    651 	
    652 	
    653 	err = Pa_CloseStream( stream );
    654 	if( err != paNoError ) goto error1;
    655 	
    656 	
    657 	return 0;
    658 	
    659 error2:
    660 	Pa_CloseStream( stream );
    661 error1:
    662 	return err;	
    663 }
    664 
    665 
    666 /*******************************************************************/
    667 /** 
    668  * Run some kind of loopback test.
    669  * @return 0 if OK or negative error.
    670  */
    671 int PaQa_RunLoopback( LoopbackContext *loopbackContext )
    672 {
    673 	PaError err = 0;
    674 	TestParameters *test = loopbackContext->test;
    675 	
    676 	
    677 	if( test->flags & PAQA_FLAG_TWO_STREAMS )
    678 	{
    679 		if( test->flags & PAQA_FLAG_USE_BLOCKING_IO )
    680 		{
    681 			err = PaQa_RunLoopbackHalfDuplexBlockingIO( loopbackContext );
    682 		}
    683 		else
    684 		{
    685 			err = PaQa_RunLoopbackHalfDuplex( loopbackContext );
    686 		}
    687 	}
    688 	else
    689 	{
    690 		if( test->flags & PAQA_FLAG_USE_BLOCKING_IO )
    691 		{
    692 			err = PaQa_RunLoopbackFullDuplexBlockingIO( loopbackContext );
    693 		}
    694 		else
    695 		{
    696 			err = PaQa_RunLoopbackFullDuplex( loopbackContext );
    697 		}
    698 	}
    699 	
    700 	if( err != paNoError )
    701 	{
    702 		printf("PortAudio error = %s\n", Pa_GetErrorText( err ) );
    703 	}
    704 	return err;	
    705 }
    706 
    707 /*******************************************************************/
    708 static int PaQa_SaveTestResultToWaveFile( UserOptions *userOptions, PaQaRecording *recording )
    709 {
    710 	if( userOptions->saveBadWaves )
    711 	{
    712 		char filename[256];
    713 #ifdef WIN32
    714         _snprintf( filename, sizeof(filename), "%s\\paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ );
    715 #else
    716 		snprintf( filename, sizeof(filename), "%s/paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ );
    717 #endif   
    718 		printf( "\"%s\", ", filename );
    719 		return PaQa_SaveRecordingToWaveFile( recording, filename );
    720 	}
    721 	return 0;
    722 }
    723 
    724 /*******************************************************************/
    725 static int PaQa_SetupLoopbackContext( LoopbackContext *loopbackContextPtr, TestParameters *testParams )
    726 {
    727 	int i;
    728 	// Setup loopback context.
    729 	memset( loopbackContextPtr, 0, sizeof(LoopbackContext) );	
    730 	loopbackContextPtr->test = testParams;
    731 	for( i=0; i<testParams->samplesPerFrame; i++ )
    732 	{
    733 		int err = PaQa_InitializeRecording( &loopbackContextPtr->recordings[i], testParams->maxFrames, testParams->sampleRate );
    734 		QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", paNoError, err );
    735 	}
    736 	for( i=0; i<testParams->samplesPerFrame; i++ )
    737 	{
    738 		PaQa_SetupSineGenerator( &loopbackContextPtr->generators[i], PaQa_GetNthFrequency( testParams->baseFrequency, i ),
    739 								testParams->amplitude, testParams->sampleRate );
    740 	}
    741 	loopbackContextPtr->minFramesPerBuffer = 0x0FFFFFFF;
    742 	return 0;
    743 error:
    744 	return -1;
    745 }
    746 
    747 /*******************************************************************/
    748 static void PaQa_TeardownLoopbackContext( LoopbackContext *loopbackContextPtr )
    749 {
    750 	int i;
    751 	if( loopbackContextPtr->test != NULL )
    752 	{
    753 		for( i=0; i<loopbackContextPtr->test->samplesPerFrame; i++ )
    754 		{
    755 			PaQa_TerminateRecording( &loopbackContextPtr->recordings[i] );
    756 		}
    757 	}
    758 }
    759 
    760 /*******************************************************************/
    761 static void PaQa_PrintShortErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel )
    762 {
    763 	printf("channel %d ", channel);
    764 	if( analysisResultPtr->popPosition > 0 )
    765 	{
    766 		printf("POP %0.3f at %d, ", (double)analysisResultPtr->popAmplitude, (int)analysisResultPtr->popPosition );	
    767 	}
    768 	else
    769 	{
    770 		if( analysisResultPtr->addedFramesPosition > 0 )
    771 		{
    772 			printf("ADD %d at %d ", (int)analysisResultPtr->numAddedFrames, (int)analysisResultPtr->addedFramesPosition );	
    773 		}
    774 		
    775 		if( analysisResultPtr->droppedFramesPosition > 0 )
    776 		{
    777 			printf("DROP %d at %d ", (int)analysisResultPtr->numDroppedFrames, (int)analysisResultPtr->droppedFramesPosition );	
    778 		}
    779 	}
    780 }
    781 
    782 /*******************************************************************/
    783 static void PaQa_PrintFullErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel )
    784 {
    785 	printf("\n=== Loopback Analysis ===================\n");
    786 	printf("             channel: %d\n", channel );
    787 	printf("             latency: %10.3f\n", analysisResultPtr->latency );
    788 	printf("      amplitudeRatio: %10.3f\n", (double)analysisResultPtr->amplitudeRatio );	
    789 	printf("         popPosition: %10.3f\n", (double)analysisResultPtr->popPosition );	
    790 	printf("        popAmplitude: %10.3f\n", (double)analysisResultPtr->popAmplitude );
    791 	printf("    num added frames: %10.3f\n", analysisResultPtr->numAddedFrames );
    792 	printf("     added frames at: %10.3f\n", analysisResultPtr->addedFramesPosition );
    793 	printf("  num dropped frames: %10.3f\n", analysisResultPtr->numDroppedFrames );
    794 	printf("   dropped frames at: %10.3f\n", analysisResultPtr->droppedFramesPosition );
    795 }
    796 
    797 /*******************************************************************/
    798 /** 
    799  * Test loopback connection using the given parameters.
    800  * @return number of channels with glitches, or negative error.
    801  */
    802 static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *testParams )
    803 {
    804 	int i;
    805 	LoopbackContext loopbackContext;
    806 	PaError err = paNoError;
    807 	PaQaTestTone testTone;
    808 	PaQaAnalysisResult analysisResult;
    809 	int numBadChannels = 0;
    810 	
    811 	printf("| %5d | %6d | ", ((int)(testParams->sampleRate+0.5)), testParams->framesPerBuffer );
    812 	fflush(stdout);
    813 	
    814 	testTone.samplesPerFrame = testParams->samplesPerFrame;
    815 	testTone.sampleRate = testParams->sampleRate;
    816 	testTone.amplitude = testParams->amplitude;
    817 	testTone.startDelay = 0;
    818 	
    819 	err = PaQa_SetupLoopbackContext( &loopbackContext, testParams );
    820 	if( err ) return err;
    821 	
    822 	err = PaQa_RunLoopback( &loopbackContext );
    823 	QA_ASSERT_TRUE("loopback did not run", (loopbackContext.callbackCount > 1) );
    824 
    825 	printf( "%7.2f %7.2f %7.2f | ",
    826 		   loopbackContext.streamInfoInputLatency * 1000.0,
    827 		   loopbackContext.streamInfoOutputLatency * 1000.0,
    828 		   (loopbackContext.streamInfoInputLatency + loopbackContext.streamInfoOutputLatency) * 1000.0
    829 		   );
    830 
    831 	printf( "%4d/%4d/%4d, %4d/%4d/%4d | ",
    832 		   loopbackContext.inputOverflowCount,
    833 		   loopbackContext.inputUnderflowCount,
    834 		   loopbackContext.inputBufferCount,
    835 		   loopbackContext.outputOverflowCount,
    836 		   loopbackContext.outputUnderflowCount,
    837 		   loopbackContext.outputBufferCount
    838 		   );
    839 	
    840 	// Analyse recording to detect glitches.
    841 	for( i=0; i<testParams->samplesPerFrame; i++ )
    842 	{
    843 		double freq = PaQa_GetNthFrequency( testParams->baseFrequency, i );
    844 		testTone.frequency = freq;
    845 		
    846 		PaQa_AnalyseRecording(  &loopbackContext.recordings[i], &testTone, &analysisResult );
    847 		
    848 		if( i==0 )
    849 		{
    850             double latencyMSec;
    851 
    852 			printf( "%4d-%4d | ",
    853 				   loopbackContext.minFramesPerBuffer,
    854 				   loopbackContext.maxFramesPerBuffer
    855 				   );
    856 			
    857 			latencyMSec = 1000.0 * analysisResult.latency / testParams->sampleRate;
    858 			printf("%7.2f | ", latencyMSec );
    859 						
    860 		}
    861 		
    862 		if( analysisResult.valid )
    863 		{
    864 			int badChannel = ( (analysisResult.popPosition > 0)
    865 					   || (analysisResult.addedFramesPosition > 0)
    866 					   || (analysisResult.droppedFramesPosition > 0) );
    867 			
    868 			if( badChannel )
    869 			{	
    870 				if( userOptions->verbose )
    871 				{
    872 					PaQa_PrintFullErrorReport( &analysisResult, i );
    873 				}
    874 				else
    875 				{
    876 					PaQa_PrintShortErrorReport( &analysisResult, i );
    877 				}
    878 				PaQa_SaveTestResultToWaveFile( userOptions, &loopbackContext.recordings[i] );
    879 			}
    880 			numBadChannels += badChannel;
    881 		}
    882 		else
    883 		{
    884 			printf( "[%d] No or low signal, ampRatio = %f", i, analysisResult.amplitudeRatio );
    885 			numBadChannels += 1;
    886 		}
    887 
    888 	}
    889 	if( numBadChannels == 0 )
    890 	{
    891 		printf( "OK" );
    892 	}
    893 
    894     // Print the # errors so far to make it easier to see where the error occured.
    895 	printf( " - #errs = %d\n", g_testsFailed );
    896 
    897 	PaQa_TeardownLoopbackContext( &loopbackContext );
    898 	if( numBadChannels > 0 )
    899 	{
    900 		g_testsFailed += 1;
    901 	}
    902 	return numBadChannels;	
    903 	
    904 error:
    905 	PaQa_TeardownLoopbackContext( &loopbackContext );
    906 	printf( "\n" );
    907 	g_testsFailed += 1;
    908 	return err;	
    909 }
    910 
    911 /*******************************************************************/
    912 static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
    913 {
    914 	memset( testParamsPtr, 0, sizeof(TestParameters) );
    915 	
    916 	testParamsPtr->samplesPerFrame = 2;
    917 	testParamsPtr->amplitude = 0.5;
    918 	testParamsPtr->sampleRate = 44100;
    919 	testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate);
    920 	testParamsPtr->framesPerBuffer = DEFAULT_FRAMES_PER_BUFFER;
    921 	testParamsPtr->baseFrequency = 200.0;
    922 	testParamsPtr->flags = PAQA_FLAG_TWO_STREAMS;
    923     testParamsPtr->streamFlags = paClipOff; /* we won't output out of range samples so don't bother clipping them */
    924 	
    925 	testParamsPtr->inputParameters.device = inputDevice;
    926 	testParamsPtr->inputParameters.sampleFormat = paFloat32;
    927 	testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame;
    928 	testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultLowInputLatency;
    929 	//testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultHighInputLatency;
    930 	
    931 	testParamsPtr->outputParameters.device = outputDevice;
    932 	testParamsPtr->outputParameters.sampleFormat = paFloat32;
    933 	testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame;
    934 	testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultLowOutputLatency;
    935 	//testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultHighOutputLatency;
    936 }
    937 
    938 /*******************************************************************/
    939 static void PaQa_OverrideTestParameters( TestParameters *testParamsPtr,  UserOptions *userOptions )
    940 {
    941 	// Check to see if a specific value was requested.
    942 	if( userOptions->sampleRate >= 0 )
    943 	{
    944 		testParamsPtr->sampleRate = userOptions->sampleRate;
    945 		testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate);
    946 	}
    947 	if( userOptions->framesPerBuffer >= 0 )
    948 	{
    949 		testParamsPtr->framesPerBuffer = userOptions->framesPerBuffer;
    950 	}
    951 	if( userOptions->inputLatency >= 0 )
    952 	{
    953 		testParamsPtr->inputParameters.suggestedLatency = userOptions->inputLatency * 0.001;
    954 	}
    955 	if( userOptions->outputLatency >= 0 )
    956 	{
    957 		testParamsPtr->outputParameters.suggestedLatency = userOptions->outputLatency * 0.001;
    958 	}
    959 	printf( "   Running with suggested latency (msec): input = %5.2f, out = %5.2f\n",
    960 		(testParamsPtr->inputParameters.suggestedLatency * 1000.0),
    961 		(testParamsPtr->outputParameters.suggestedLatency * 1000.0) );
    962 }
    963 
    964 /*******************************************************************/
    965 /** 
    966  * Run a series of tests on this loopback connection.
    967  * @return number of bad channel results
    968  */
    969 static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
    970 {
    971 	int iFlags;
    972 	int iRate;
    973 	int iSize;
    974 	int iFormat;
    975 	int savedValue;
    976 	TestParameters testParams;
    977 	const PaDeviceInfo *inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );	
    978 	const PaDeviceInfo *outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );		
    979     int totalBadChannels = 0;
    980 
    981 	// test half duplex first because it is more likely to work.
    982 	int flagSettings[] = { PAQA_FLAG_TWO_STREAMS, 0 };
    983 	int numFlagSettings = (sizeof(flagSettings)/sizeof(int));
    984 
    985 	double sampleRates[] = { 8000.0, 11025.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0, 96000.0 };
    986 	int numRates = (sizeof(sampleRates)/sizeof(double));
    987 	
    988 	// framesPerBuffer==0 means PA decides on the buffer size.
    989 	int framesPerBuffers[] = { 0, 16, 32, 40, 64, 100, 128, 256, 512, 1024 };
    990 	int numBufferSizes = (sizeof(framesPerBuffers)/sizeof(int));
    991 	
    992 	PaSampleFormat sampleFormats[] = { paFloat32, paUInt8, paInt8, paInt16, paInt32 };
    993 	const char *sampleFormatNames[] = { "paFloat32", "paUInt8", "paInt8", "paInt16", "paInt32" };
    994 	int numSampleFormats = (sizeof(sampleFormats)/sizeof(PaSampleFormat));
    995 	
    996     printf( "=============== Analysing Loopback %d to %d =====================\n", outputDevice, inputDevice  );
    997 	printf( "   Devices: %s => %s\n", outputDeviceInfo->name, inputDeviceInfo->name);
    998 	
    999 	PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
   1000 	
   1001 	PaQa_OverrideTestParameters( &testParams, userOptions );
   1002 	
   1003 	// Loop though combinations of audio parameters.
   1004 	for( iFlags=0; iFlags<numFlagSettings; iFlags++ )
   1005 	{
   1006 		int numRuns = 0;
   1007 
   1008 		testParams.flags = flagSettings[iFlags];
   1009 		printf( "\n************ Mode = %s ************\n",
   1010 			   (( testParams.flags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) );
   1011 
   1012 		printf("|-   requested  -|-  stream info latency  -|- measured ------------------------------\n");
   1013 		printf("|-sRate-|-fr/buf-|- in    - out   - total -|- over/under/calls for in, out -|- frm/buf -|-latency-|- channel results -\n");
   1014 
   1015 		// Loop though various sample rates.
   1016 		if( userOptions->sampleRate < 0 )
   1017 		{
   1018 			savedValue = testParams.sampleRate;
   1019 			for( iRate=0; iRate<numRates; iRate++ )
   1020 			{
   1021                 int numBadChannels;
   1022 
   1023 				// SAMPLE RATE
   1024 				testParams.sampleRate = sampleRates[iRate];
   1025 				testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate);
   1026 				
   1027 				numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
   1028 				totalBadChannels += numBadChannels;
   1029 			}
   1030 			testParams.sampleRate = savedValue;
   1031 			testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate);
   1032 			printf( "\n" );
   1033 			numRuns += 1;
   1034 		}
   1035 		
   1036 		// Loop through various buffer sizes.
   1037 		if( userOptions->framesPerBuffer < 0 )
   1038 		{
   1039 			savedValue = testParams.framesPerBuffer;
   1040 			for( iSize=0; iSize<numBufferSizes; iSize++ )
   1041 			{	
   1042                 int numBadChannels;
   1043 
   1044 				// BUFFER SIZE
   1045 				testParams.framesPerBuffer = framesPerBuffers[iSize];
   1046 				
   1047 				numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
   1048 				totalBadChannels += numBadChannels;			
   1049 			}
   1050 			testParams.framesPerBuffer = savedValue;
   1051 			printf( "\n" );		
   1052 			numRuns += 1;
   1053 		}
   1054 		// Run one with single parameters in case we did not do a series.
   1055 		if( numRuns == 0 )
   1056 		{
   1057 			int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
   1058 			totalBadChannels += numBadChannels;						
   1059 		}
   1060 	}
   1061 			
   1062 	printf("\nTest Sample Formats using Half Duplex IO -----\n" );
   1063     
   1064 	PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
   1065 	testParams.flags = PAQA_FLAG_TWO_STREAMS;	
   1066     for( iFlags= 0; iFlags<4; iFlags++ )
   1067     {
   1068         // Cycle through combinations of flags.
   1069         testParams.streamFlags = 0;
   1070         if( iFlags & 1 ) testParams.streamFlags |= paClipOff;
   1071         if( iFlags & 2 ) testParams.streamFlags |= paDitherOff;
   1072         
   1073         for( iFormat=0; iFormat<numSampleFormats; iFormat++ )
   1074         {	
   1075             int numBadChannels;
   1076             PaSampleFormat format = sampleFormats[ iFormat ];
   1077             testParams.inputParameters.sampleFormat = format;
   1078             testParams.outputParameters.sampleFormat = format;
   1079             printf("Sample format = %d = %s, PaStreamFlags = 0x%02X\n", (int) format, sampleFormatNames[iFormat], (unsigned int) testParams.streamFlags );
   1080             numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
   1081             totalBadChannels += numBadChannels;			
   1082         }
   1083     }
   1084 	printf( "\n" );
   1085 	printf( "****************************************\n");
   1086 	
   1087 	return totalBadChannels;
   1088 }
   1089 
   1090 /*******************************************************************/
   1091 int PaQa_CheckForClippedLoopback( LoopbackContext *loopbackContextPtr )
   1092 {
   1093 	int clipped = 0;
   1094 	TestParameters *testParamsPtr = loopbackContextPtr->test;
   1095 	
   1096 	// Start in the middle assuming past latency.
   1097 	int startFrame = testParamsPtr->maxFrames/2;
   1098 	int numFrames = testParamsPtr->maxFrames/2;
   1099 	
   1100 	// Check to see if the signal is clipped.
   1101 	double amplitudeLeft = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[0],
   1102 															testParamsPtr->baseFrequency, testParamsPtr->sampleRate,
   1103 															startFrame, numFrames );
   1104 	double gainLeft = amplitudeLeft / testParamsPtr->amplitude;
   1105 	double amplitudeRight = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[1],
   1106 															 testParamsPtr->baseFrequency, testParamsPtr->sampleRate,
   1107 															 startFrame, numFrames );
   1108 	double gainRight = amplitudeLeft / testParamsPtr->amplitude;
   1109 	printf("   Loop gain: left = %f, right = %f\n", gainLeft, gainRight );
   1110 
   1111 	if( (amplitudeLeft > 1.0 ) || (amplitudeRight > 1.0) )
   1112 	{
   1113 		printf("ERROR - loop gain is too high. Should be around than 1.0. Please lower output level and/or input gain.\n" );
   1114 		clipped = 1;
   1115 	}
   1116 	return clipped;
   1117 }
   1118 
   1119 /*******************************************************************/
   1120 int PaQa_MeasureBackgroundNoise( LoopbackContext *loopbackContextPtr, double *rmsPtr )
   1121 {
   1122 	int result = 0;
   1123 	*rmsPtr = 0.0;
   1124 	// Rewind so we can record some input.
   1125 	loopbackContextPtr->recordings[0].numFrames = 0;
   1126 	loopbackContextPtr->recordings[1].numFrames = 0;
   1127 	result = PaQa_RunInputOnly( loopbackContextPtr );
   1128 	if( result == 0 )
   1129 	{
   1130 		double leftRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[0].buffer,
   1131 													loopbackContextPtr->recordings[0].numFrames );
   1132 		double rightRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[1].buffer,
   1133 													 loopbackContextPtr->recordings[1].numFrames );
   1134 		*rmsPtr = (leftRMS + rightRMS) / 2.0;
   1135 	}
   1136 	return result;
   1137 }
   1138 
   1139 /*******************************************************************/
   1140 /** 
   1141  * Output a sine wave then try to detect it on input.
   1142  *
   1143  * @return 1 if loopback connected, 0 if not, or negative error.
   1144  */
   1145 int PaQa_CheckForLoopBack( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
   1146 {
   1147 	TestParameters testParams;
   1148 	LoopbackContext loopbackContext;
   1149     const PaDeviceInfo *inputDeviceInfo;	
   1150     const PaDeviceInfo *outputDeviceInfo;		
   1151 	PaError err = paNoError;
   1152 	double minAmplitude;
   1153 	int loopbackIsConnected;
   1154     int startFrame, numFrames;
   1155     double magLeft, magRight;
   1156 
   1157 	inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
   1158 	if( inputDeviceInfo == NULL )
   1159 	{
   1160 		printf("ERROR - Pa_GetDeviceInfo for input returned NULL.\n");
   1161 		return paInvalidDevice;
   1162 	}
   1163 	if( inputDeviceInfo->maxInputChannels < 2 )
   1164 	{
   1165 		return 0;
   1166 	}
   1167 	
   1168 	outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );
   1169 	if( outputDeviceInfo == NULL )
   1170 	{
   1171 		printf("ERROR - Pa_GetDeviceInfo for output returned NULL.\n");
   1172 		return paInvalidDevice;
   1173 	}
   1174 	if( outputDeviceInfo->maxOutputChannels < 2 )
   1175 	{
   1176 		return 0;
   1177 	}
   1178 	
   1179 	printf( "Look for loopback cable between \"%s\" => \"%s\"\n", outputDeviceInfo->name, inputDeviceInfo->name);
   1180 	
   1181 	printf( "   Default suggested input latency (msec): low = %5.2f, high = %5.2f\n",
   1182 		(inputDeviceInfo->defaultLowInputLatency * 1000.0),
   1183 		(inputDeviceInfo->defaultHighInputLatency * 1000.0) );
   1184 	printf( "   Default suggested output latency (msec): low = %5.2f, high = %5.2f\n",
   1185 		(outputDeviceInfo->defaultLowOutputLatency * 1000.0),
   1186 		(outputDeviceInfo->defaultHighOutputLatency * 1000.0) );
   1187 		
   1188 	PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
   1189 	
   1190 	PaQa_OverrideTestParameters( &testParams, userOptions );
   1191 	
   1192 	testParams.maxFrames = (int) (LOOPBACK_DETECTION_DURATION_SECONDS * testParams.sampleRate);	
   1193 	minAmplitude = testParams.amplitude / 4.0;
   1194 	
   1195 	// Check to see if the selected formats are supported.
   1196 	if( Pa_IsFormatSupported( &testParams.inputParameters, NULL, testParams.sampleRate ) != paFormatIsSupported )
   1197 	{
   1198 		printf( "Input not supported for this format!\n" );
   1199 		return 0;
   1200 	}
   1201 	if( Pa_IsFormatSupported( NULL, &testParams.outputParameters, testParams.sampleRate ) != paFormatIsSupported )
   1202 	{
   1203 		printf( "Output not supported for this format!\n" );
   1204 		return 0;
   1205 	}
   1206 	
   1207 	PaQa_SetupLoopbackContext( &loopbackContext, &testParams );
   1208 			
   1209 	if( inputDevice == outputDevice )
   1210 	{
   1211 		// Use full duplex if checking for loopback on one device.
   1212 		testParams.flags &= ~PAQA_FLAG_TWO_STREAMS;
   1213 	}
   1214 	else
   1215 	{
   1216 		// Use half duplex if checking for loopback on two different device.
   1217 		testParams.flags = PAQA_FLAG_TWO_STREAMS;
   1218 	}
   1219 	err = PaQa_RunLoopback( &loopbackContext );
   1220 	QA_ASSERT_TRUE("loopback detection callback did not run", (loopbackContext.callbackCount > 1) );
   1221 	
   1222 	// Analyse recording to see if we captured the output.
   1223 	// Start in the middle assuming past latency.
   1224 	startFrame = testParams.maxFrames/2;
   1225 	numFrames = testParams.maxFrames/2;
   1226 	magLeft = PaQa_CorrelateSine( &loopbackContext.recordings[0],
   1227 									loopbackContext.generators[0].frequency,
   1228 									testParams.sampleRate,
   1229 									startFrame, numFrames, NULL );
   1230 	magRight = PaQa_CorrelateSine( &loopbackContext.recordings[1],
   1231 									loopbackContext.generators[1].frequency,
   1232 									testParams.sampleRate,
   1233 									startFrame, numFrames, NULL );
   1234 	printf("   Amplitudes: left = %f, right = %f\n", magLeft, magRight );
   1235 	
   1236 	// Check for backwards cable.
   1237     loopbackIsConnected = ((magLeft > minAmplitude) && (magRight > minAmplitude));
   1238 
   1239 	if( !loopbackIsConnected )
   1240 	{
   1241 		double magLeftReverse = PaQa_CorrelateSine( &loopbackContext.recordings[0],
   1242 												   loopbackContext.generators[1].frequency,
   1243 												   testParams.sampleRate,
   1244 												   startFrame, numFrames, NULL );
   1245 		
   1246 		double magRightReverse = PaQa_CorrelateSine( &loopbackContext.recordings[1], 
   1247 													loopbackContext.generators[0].frequency,
   1248 													testParams.sampleRate,
   1249 													startFrame, numFrames, NULL );
   1250 		
   1251 		if ((magLeftReverse > minAmplitude) && (magRightReverse>minAmplitude))
   1252 		{
   1253 			printf("ERROR - You seem to have the left and right channels swapped on the loopback cable!\n");
   1254 		}
   1255 	}
   1256 	else
   1257 	{
   1258 		double rms = 0.0;
   1259 		if( PaQa_CheckForClippedLoopback( &loopbackContext ) )
   1260 		{
   1261 			// Clipped so don't use this loopback.
   1262 			loopbackIsConnected = 0;
   1263 		}
   1264 		
   1265 		err = PaQa_MeasureBackgroundNoise( &loopbackContext, &rms );
   1266 		printf("   Background noise = %f\n", rms );
   1267 		if( err )
   1268 		{
   1269 			printf("ERROR - Could not measure background noise on this input!\n");
   1270 			loopbackIsConnected = 0;
   1271 		}
   1272 		else if( rms > MAX_BACKGROUND_NOISE_RMS )
   1273 		{			
   1274 			printf("ERROR - There is too much background noise on this input!\n");
   1275 			loopbackIsConnected = 0;
   1276 		}
   1277 	}
   1278 	
   1279 	PaQa_TeardownLoopbackContext( &loopbackContext );
   1280 	return loopbackIsConnected;	
   1281 	
   1282 error:
   1283 	PaQa_TeardownLoopbackContext( &loopbackContext );
   1284 	return err;	
   1285 }
   1286 
   1287 /*******************************************************************/
   1288 /**
   1289  * If there is a loopback connection then run the analysis.
   1290  */
   1291 static int CheckLoopbackAndScan( UserOptions *userOptions,
   1292 								PaDeviceIndex iIn, PaDeviceIndex iOut )
   1293 {
   1294 	int loopbackConnected = PaQa_CheckForLoopBack( userOptions, iIn, iOut );
   1295 	if( loopbackConnected > 0 )
   1296 	{
   1297 		PaQa_AnalyzeLoopbackConnection( userOptions, iIn, iOut );
   1298 		return 1;
   1299 	}
   1300 	return 0;
   1301 }
   1302 								
   1303 /*******************************************************************/
   1304 /**
   1305  * Scan every combination of output to input device.
   1306  * If a loopback is found the analyse the combination.
   1307  * The scan can be overriden using the -i and -o command line options.
   1308  */
   1309 static int ScanForLoopback(UserOptions *userOptions)
   1310 {
   1311 	PaDeviceIndex iIn,iOut;
   1312 	int  numLoopbacks = 0;
   1313     int  numDevices;
   1314     numDevices = Pa_GetDeviceCount();    
   1315 		
   1316 	// If both devices are specified then just use that combination.
   1317 	if ((userOptions->inputDevice >= 0) && (userOptions->outputDevice >= 0))
   1318 	{
   1319 		numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, userOptions->outputDevice );
   1320 	}
   1321 	else if (userOptions->inputDevice >= 0)
   1322 	{
   1323 		// Just scan for output.
   1324 		for( iOut=0; iOut<numDevices; iOut++ )
   1325 		{					
   1326 			numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, iOut );
   1327 		}
   1328 	}
   1329 	else if (userOptions->outputDevice >= 0)
   1330 	{
   1331 		// Just scan for input.
   1332 		for( iIn=0; iIn<numDevices; iIn++ )
   1333 		{					
   1334 			numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, userOptions->outputDevice );
   1335 		}
   1336 	}
   1337 	else
   1338 	{	
   1339 		// Scan both.
   1340 		for( iOut=0; iOut<numDevices; iOut++ )
   1341 		{
   1342 			for( iIn=0; iIn<numDevices; iIn++ )
   1343 			{				
   1344 				numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, iOut );
   1345 			}
   1346 		}
   1347 	}
   1348 	QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) );
   1349 	return numLoopbacks;
   1350 	
   1351 error:
   1352 	return -1;
   1353 }
   1354 
   1355 /*==========================================================================================*/
   1356 int TestSampleFormatConversion( void )
   1357 {
   1358 	int i;
   1359 	const float floatInput[] = { 1.0, 0.5, -0.5, -1.0 };
   1360 	
   1361 	const char charInput[] = { 127, 64, -64, -128 };
   1362 	const unsigned char ucharInput[] = { 255, 128+64, 64, 0 };
   1363 	const short shortInput[] = { 32767, 32768/2, -32768/2, -32768 };
   1364 	const int intInput[] = { 2147483647, 2147483647/2, -1073741824 /*-2147483648/2 doesn't work in msvc*/, -2147483648 };
   1365 	
   1366 	float floatOutput[4];
   1367 	short shortOutput[4];
   1368 	int intOutput[4];	
   1369 	unsigned char ucharOutput[4];
   1370 	char charOutput[4];
   1371 	
   1372 	QA_ASSERT_EQUALS("int must be 32-bit", 4, (int) sizeof(int) );
   1373 	QA_ASSERT_EQUALS("short must be 16-bit", 2, (int) sizeof(short) );
   1374 	
   1375 	// from Float ======
   1376 	PaQa_ConvertFromFloat( floatInput, 4, paUInt8, ucharOutput );
   1377 	for( i=0; i<4; i++ )
   1378 	{
   1379 		QA_ASSERT_CLOSE_INT( "paFloat32 -> paUInt8 -> error", ucharInput[i], ucharOutput[i], 1 );
   1380 	}
   1381 	
   1382 	PaQa_ConvertFromFloat( floatInput, 4, paInt8, charOutput );
   1383 	for( i=0; i<4; i++ )
   1384 	{
   1385 		QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt8 -> error", charInput[i], charOutput[i], 1 );
   1386 	}
   1387 	
   1388 	PaQa_ConvertFromFloat( floatInput, 4, paInt16, shortOutput );
   1389 	for( i=0; i<4; i++ )
   1390 	{
   1391 		QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt16 error", shortInput[i], shortOutput[i], 1 );
   1392 	}
   1393 		
   1394 	PaQa_ConvertFromFloat( floatInput, 4, paInt32, intOutput );
   1395 	for( i=0; i<4; i++ )
   1396 	{
   1397 		QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt32 error", intInput[i], intOutput[i], 0x00010000 );
   1398 	}
   1399 	
   1400 	
   1401 	// to Float ======
   1402 	memset( floatOutput, 0, sizeof(floatOutput) );
   1403 	PaQa_ConvertToFloat( ucharInput, 4, paUInt8, floatOutput );
   1404 	for( i=0; i<4; i++ )
   1405 	{
   1406 		QA_ASSERT_CLOSE( "paUInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 );
   1407 	}
   1408 	
   1409 	memset( floatOutput, 0, sizeof(floatOutput) );
   1410 	PaQa_ConvertToFloat( charInput, 4, paInt8, floatOutput );
   1411 	for( i=0; i<4; i++ )
   1412 	{
   1413 		QA_ASSERT_CLOSE( "paInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 );
   1414 	}
   1415 	
   1416 	memset( floatOutput, 0, sizeof(floatOutput) );
   1417 	PaQa_ConvertToFloat( shortInput, 4, paInt16, floatOutput );
   1418 	for( i=0; i<4; i++ )
   1419 	{
   1420 		QA_ASSERT_CLOSE( "paInt16 -> paFloat32 error", floatInput[i], floatOutput[i], 0.001 );
   1421 	}
   1422 	
   1423 	memset( floatOutput, 0, sizeof(floatOutput) );
   1424 	PaQa_ConvertToFloat( intInput, 4, paInt32, floatOutput );
   1425 	for( i=0; i<4; i++ )
   1426 	{
   1427 		QA_ASSERT_CLOSE( "paInt32 -> paFloat32 error", floatInput[i], floatOutput[i], 0.00001 );
   1428 	}
   1429 	
   1430 	return 0;
   1431 	
   1432 error:
   1433 	return -1;
   1434 }
   1435 
   1436 
   1437 /*******************************************************************/
   1438 void usage( const char *name )
   1439 {
   1440 	printf("%s [-i# -o# -l# -r# -s# -m -w -dDir]\n", name);
   1441 	printf("  -i# - Input device ID. Will scan for loopback cable if not specified.\n");
   1442 	printf("  -o# - Output device ID. Will scan for loopback if not specified.\n");
   1443 	printf("  -l# - Latency for both input and output in milliseconds.\n");
   1444 	printf("  --inputLatency # Input latency in milliseconds.\n");	
   1445 	printf("  --outputLatency # Output latency in milliseconds.\n");
   1446 	printf("  -r# - Sample Rate in Hz.  Will use multiple common rates if not specified.\n");
   1447 	printf("  -s# - Size of callback buffer in frames, framesPerBuffer. Will use common values if not specified.\n");
   1448 	printf("  -w  - Save bad recordings in a WAV file.\n");
   1449 	printf("  -dDir - Path for Directory for WAV files. Default is current directory.\n");
   1450 	printf("  -m  - Just test the DSP Math code and not the audio devices.\n");
   1451 	printf("  -v  - Verbose reports.\n");
   1452 }
   1453 
   1454 /*******************************************************************/
   1455 int main( int argc, char **argv )
   1456 {
   1457     int i;
   1458 	UserOptions userOptions;
   1459 	int result = 0;
   1460 	int justMath = 0;
   1461     char *executableName = argv[0];
   1462 
   1463 	printf("PortAudio LoopBack Test built " __DATE__ " at " __TIME__ "\n");
   1464 
   1465 	if( argc > 1 ){
   1466 		printf("running with arguments:");
   1467 		for(i=1; i < argc; ++i )
   1468 			printf(" %s", argv[i] );
   1469 		printf("\n");
   1470 	}else{
   1471 		printf("running with no arguments\n");
   1472 	}
   1473 	
   1474 	memset(&userOptions, 0, sizeof(userOptions));
   1475 	userOptions.inputDevice = paNoDevice;
   1476 	userOptions.outputDevice = paNoDevice;
   1477 	userOptions.sampleRate = -1;
   1478 	userOptions.framesPerBuffer = -1;
   1479 	userOptions.inputLatency = -1;
   1480 	userOptions.outputLatency = -1;
   1481 	userOptions.waveFilePath = ".";
   1482 	
   1483 	// Process arguments. Skip name of executable.
   1484 	i = 1;
   1485 	while( i<argc )
   1486 	{
   1487 		char *arg = argv[i];
   1488 		if( arg[0] == '-' )
   1489 		{
   1490 			switch(arg[1])
   1491 			{
   1492 				case 'i':
   1493 					userOptions.inputDevice = atoi(&arg[2]);
   1494 					break;
   1495 				case 'o':
   1496 					userOptions.outputDevice = atoi(&arg[2]);
   1497 					break;
   1498 				case 'l':
   1499 					userOptions.inputLatency = userOptions.outputLatency = atoi(&arg[2]);
   1500 					break;
   1501 				case 'r':
   1502 					userOptions.sampleRate = atoi(&arg[2]);
   1503 					break;
   1504 				case 's':
   1505 					userOptions.framesPerBuffer = atoi(&arg[2]);
   1506 					break;
   1507 					
   1508 				case 'm':
   1509 					printf("Option -m set so just testing math and not the audio devices.\n");
   1510 					justMath = 1;
   1511 					break;
   1512 					
   1513 				case 'w':
   1514 					userOptions.saveBadWaves = 1;
   1515 					break;
   1516 				case 'd':
   1517 					userOptions.waveFilePath = &arg[2];
   1518 					break;
   1519 					
   1520 				case 'v':
   1521 					userOptions.verbose = 1;
   1522 					break;
   1523 					
   1524 				case 'h':
   1525 					usage( executableName );
   1526 					exit(0);
   1527 					break;
   1528 					
   1529 				case '-':
   1530 				{
   1531 					if( strcmp( &arg[2], "inputLatency" ) == 0 )
   1532 					{
   1533 						i += 1;
   1534 						userOptions.inputLatency = atoi(argv[i]);
   1535 					}
   1536 					else if( strcmp( &arg[2], "outputLatency" ) == 0 )
   1537 					{
   1538 						i += 1;
   1539 						userOptions.outputLatency = atoi(argv[i]);					
   1540 					}
   1541 					else
   1542 					{
   1543 						printf("Illegal option: %s\n", arg);
   1544 						usage( executableName );
   1545 						exit(1);
   1546 					}
   1547 
   1548 				}
   1549 					break;
   1550 					
   1551 					
   1552 				default:
   1553 					printf("Illegal option: %s\n", arg);
   1554 					usage( executableName );
   1555 					exit(1);
   1556 					break;
   1557 			}
   1558 		}
   1559 		else
   1560 		{
   1561 			printf("Illegal argument: %s\n", arg);
   1562 			usage( executableName );
   1563 			exit(1);
   1564 
   1565 		}
   1566 		i += 1;
   1567 	}
   1568 		
   1569 	result = PaQa_TestAnalyzer();
   1570 	
   1571 	// Test sample format conversion tool.
   1572 	result = TestSampleFormatConversion();
   1573 	
   1574 	if( (result == 0) && (justMath == 0) )
   1575 	{
   1576 		Pa_Initialize();
   1577 		printf( "PortAudio version number = %d\nPortAudio version text = '%s'\n",
   1578 			   Pa_GetVersion(), Pa_GetVersionText() );
   1579 		printf( "=============== PortAudio Devices ========================\n" );
   1580 		PaQa_ListAudioDevices();
   1581         if( Pa_GetDeviceCount() == 0 )
   1582             printf( "no devices found.\n" );
   1583         
   1584 		printf( "=============== Detect Loopback ==========================\n" );
   1585 		ScanForLoopback(&userOptions);
   1586  
   1587 		Pa_Terminate();
   1588 	}
   1589 
   1590 	if (g_testsFailed == 0)
   1591 	{
   1592 		printf("PortAudio QA SUCCEEDED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed );
   1593 		return 0;
   1594 
   1595 	}
   1596 	else
   1597 	{
   1598 		printf("PortAudio QA FAILED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed );
   1599 		return 1;
   1600 	}	
   1601 }