mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-11 17:13:37 +02:00
Update portaudio to upstream r1885
This commit is contained in:
92
lib-src/portaudio-v19/qa/loopback/README.txt
Normal file
92
lib-src/portaudio-v19/qa/loopback/README.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
README for PortAudio Loopback Test
|
||||
|
||||
Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
See complete license at end of file.
|
||||
|
||||
This folder contains code for a single executable that does a standalone test of PortAudio.
|
||||
It does not require a human to listen to the result. Instead it listens to itself using
|
||||
a loopback cable connected between the audio output and the audio input. Special pop detectors
|
||||
and phase analysers can detect errors in the audio stream.
|
||||
|
||||
This test can be run from a script as part of a nightly build and test.
|
||||
|
||||
--- How to Build the Loopback Test ---
|
||||
|
||||
The loopback test is not normally built by the makefile.
|
||||
To build the loopback test, enter:
|
||||
|
||||
./configure && make loopback
|
||||
|
||||
This will build the "bin/paloopback" executable.
|
||||
|
||||
--- How To Run Test ---
|
||||
|
||||
Connect stereo cables from one or more output audio devices to audio input devices.
|
||||
The test will scan all the ports and find the cables.
|
||||
|
||||
Adjust the volume levels of the hardware so you get a decent signal that will not clip.
|
||||
|
||||
Run the test from the command line with the following options:
|
||||
|
||||
-i# Input device ID. Will scan for loopback if not specified.
|
||||
-o# Output device ID. Will scan for loopback if not specified.
|
||||
-r# Sample Rate in Hz. Will use multiple common rates if not specified.
|
||||
-s# Size of callback buffer in frames, framesPerBuffer.
|
||||
-w Save bad recordings in a WAV file.
|
||||
-dDir Path for Directory for WAV files. Default is current directory.
|
||||
-m Just test the DSP Math code and not the audio devices.
|
||||
|
||||
If the -w option is set then any tests that fail will save the recording of the broken
|
||||
channel in a WAV file. The files will be numbered and shown in the report.
|
||||
|
||||
--- ToDo ---
|
||||
|
||||
* Add check for harmonic and enharmonic distortion.
|
||||
* Measure min/max peak values.
|
||||
* Detect DC bias.
|
||||
* Test against matrix of devices/APIs and settings.
|
||||
* Detect mono vs stereo loopback.
|
||||
* More command line options
|
||||
--quick
|
||||
--latency
|
||||
--duration
|
||||
* Automated build and test script with cron job.
|
||||
* Test on Windows.
|
||||
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2008 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
707
lib-src/portaudio-v19/qa/loopback/src/audio_analyzer.c
Normal file
707
lib-src/portaudio-v19/qa/loopback/src/audio_analyzer.c
Normal file
@@ -0,0 +1,707 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include "qa_tools.h"
|
||||
#include "audio_analyzer.h"
|
||||
#include "write_wav.h"
|
||||
|
||||
#define PAQA_POP_THRESHOLD (0.04)
|
||||
|
||||
/*==========================================================================================*/
|
||||
double PaQa_GetNthFrequency( double baseFrequency, int index )
|
||||
{
|
||||
// Use 13 tone equal tempered scale because it does not generate harmonic ratios.
|
||||
return baseFrequency * pow( 2.0, index / 13.0 );
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
void PaQa_EraseBuffer( float *buffer, int numFrames, int samplesPerFrame )
|
||||
{
|
||||
int i;
|
||||
int numSamples = numFrames * samplesPerFrame;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
*buffer++ = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
void PaQa_SetupSineGenerator( PaQaSineGenerator *generator, double frequency, double amplitude, double frameRate )
|
||||
{
|
||||
generator->phase = 0.0;
|
||||
generator->amplitude = amplitude;
|
||||
generator->frequency = frequency;
|
||||
generator->phaseIncrement = 2.0 * frequency * MATH_PI / frameRate;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
void PaQa_MixSine( PaQaSineGenerator *generator, float *buffer, int numSamples, int stride )
|
||||
{
|
||||
int i;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
float value = sinf( (float) generator->phase ) * generator->amplitude;
|
||||
*buffer += value; // Mix with existing value.
|
||||
buffer += stride;
|
||||
// Advance phase and wrap around.
|
||||
generator->phase += generator->phaseIncrement;
|
||||
if (generator->phase > MATH_TWO_PI)
|
||||
{
|
||||
generator->phase -= MATH_TWO_PI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
void PaQa_GenerateCrackDISABLED( float *buffer, int numSamples, int stride )
|
||||
{
|
||||
int i;
|
||||
int offset = numSamples/2;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
float phase = (MATH_TWO_PI * 0.5 * (i - offset)) / numSamples;
|
||||
float cosp = cosf( phase );
|
||||
float cos2 = cosp * cosp;
|
||||
// invert second half of signal
|
||||
float value = (i < offset) ? cos2 : (0-cos2);
|
||||
*buffer = value;
|
||||
buffer += stride;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_InitializeRecording( PaQaRecording *recording, int maxFrames, int frameRate )
|
||||
{
|
||||
int numBytes = maxFrames * sizeof(float);
|
||||
recording->buffer = (float*)malloc(numBytes);
|
||||
QA_ASSERT_TRUE( "Allocate recording buffer.", (recording->buffer != NULL) );
|
||||
recording->maxFrames = maxFrames; recording->sampleRate = frameRate;
|
||||
recording->numFrames = 0;
|
||||
return 0;
|
||||
error:
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
void PaQa_TerminateRecording( PaQaRecording *recording )
|
||||
{
|
||||
if (recording->buffer != NULL)
|
||||
{
|
||||
free( recording->buffer );
|
||||
recording->buffer = NULL;
|
||||
}
|
||||
recording->maxFrames = 0;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_WriteRecording( PaQaRecording *recording, float *buffer, int numFrames, int stride )
|
||||
{
|
||||
int i;
|
||||
int framesToWrite;
|
||||
float *data = &recording->buffer[recording->numFrames];
|
||||
|
||||
framesToWrite = numFrames;
|
||||
if ((framesToWrite + recording->numFrames) > recording->maxFrames)
|
||||
{
|
||||
framesToWrite = recording->maxFrames - recording->numFrames;
|
||||
}
|
||||
|
||||
for( i=0; i<framesToWrite; i++ )
|
||||
{
|
||||
*data++ = *buffer;
|
||||
buffer += stride;
|
||||
}
|
||||
recording->numFrames += framesToWrite;
|
||||
return (recording->numFrames >= recording->maxFrames);
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_WriteSilence( PaQaRecording *recording, int numFrames )
|
||||
{
|
||||
int i;
|
||||
int framesToRecord;
|
||||
float *data = &recording->buffer[recording->numFrames];
|
||||
|
||||
framesToRecord = numFrames;
|
||||
if ((framesToRecord + recording->numFrames) > recording->maxFrames)
|
||||
{
|
||||
framesToRecord = recording->maxFrames - recording->numFrames;
|
||||
}
|
||||
|
||||
for( i=0; i<framesToRecord; i++ )
|
||||
{
|
||||
*data++ = 0.0f;
|
||||
}
|
||||
recording->numFrames += framesToRecord;
|
||||
return (recording->numFrames >= recording->maxFrames);
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_RecordFreeze( PaQaRecording *recording, int numFrames )
|
||||
{
|
||||
int i;
|
||||
int framesToRecord;
|
||||
float *data = &recording->buffer[recording->numFrames];
|
||||
|
||||
framesToRecord = numFrames;
|
||||
if ((framesToRecord + recording->numFrames) > recording->maxFrames)
|
||||
{
|
||||
framesToRecord = recording->maxFrames - recording->numFrames;
|
||||
}
|
||||
|
||||
for( i=0; i<framesToRecord; i++ )
|
||||
{
|
||||
// Copy old value forward as if the signal had frozen.
|
||||
data[i] = data[i-1];
|
||||
}
|
||||
recording->numFrames += framesToRecord;
|
||||
return (recording->numFrames >= recording->maxFrames);
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Write recording to WAV file.
|
||||
*/
|
||||
int PaQa_SaveRecordingToWaveFile( PaQaRecording *recording, const char *filename )
|
||||
{
|
||||
WAV_Writer writer;
|
||||
int result = 0;
|
||||
#define NUM_SAMPLES (200)
|
||||
short data[NUM_SAMPLES];
|
||||
const int samplesPerFrame = 1;
|
||||
int numLeft = recording->numFrames;
|
||||
float *buffer = &recording->buffer[0];
|
||||
|
||||
result = Audio_WAV_OpenWriter( &writer, filename, recording->sampleRate, samplesPerFrame );
|
||||
if( result < 0 ) goto error;
|
||||
|
||||
while( numLeft > 0 )
|
||||
{
|
||||
int i;
|
||||
int numToSave = (numLeft > NUM_SAMPLES) ? NUM_SAMPLES : numLeft;
|
||||
// Convert double samples to shorts.
|
||||
for( i=0; i<numToSave; i++ )
|
||||
{
|
||||
double fval = *buffer++;
|
||||
// Convert float to int and clip to short range.
|
||||
int ival = fval * 32768.0;
|
||||
if( ival > 32767 ) ival = 32767;
|
||||
else if( ival < -32768 ) ival = -32768;
|
||||
data[i] = ival;
|
||||
}
|
||||
result = Audio_WAV_WriteShorts( &writer, data, numToSave );
|
||||
if( result < 0 ) goto error;
|
||||
numLeft -= numToSave;
|
||||
}
|
||||
|
||||
result = Audio_WAV_CloseWriter( &writer );
|
||||
if( result < 0 ) goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
printf("ERROR: result = %d\n", result );
|
||||
return result;
|
||||
#undef NUM_SAMPLES
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
|
||||
double PaQa_MeasureCrossingSlope( float *buffer, int numFrames )
|
||||
{
|
||||
int i;
|
||||
double slopeTotal = 0.0;
|
||||
int slopeCount = 0;
|
||||
float previous;
|
||||
double averageSlope = 0.0;
|
||||
|
||||
previous = buffer[0];
|
||||
for( i=1; i<numFrames; i++ )
|
||||
{
|
||||
float current = buffer[i];
|
||||
if( (current > 0.0) && (previous < 0.0) )
|
||||
{
|
||||
double delta = current - previous;
|
||||
slopeTotal += delta;
|
||||
slopeCount += 1;
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
if( slopeCount > 0 )
|
||||
{
|
||||
averageSlope = slopeTotal / slopeCount;
|
||||
}
|
||||
return averageSlope;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/*
|
||||
* We can't just measure the peaks cuz they may be clipped.
|
||||
* But the zero crossing should be intact.
|
||||
* The measured slope of a sine wave at zero should be:
|
||||
*
|
||||
* slope = sin( 2PI * frequency / sampleRate )
|
||||
*
|
||||
*/
|
||||
double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
|
||||
double frequency, double frameRate,
|
||||
int startFrame, int numFrames )
|
||||
{
|
||||
float *buffer = &recording->buffer[startFrame];
|
||||
double measuredSlope = PaQa_MeasureCrossingSlope( buffer, numFrames );
|
||||
double unitySlope = sin( MATH_TWO_PI * frequency / frameRate );
|
||||
double estimatedAmplitude = measuredSlope / unitySlope;
|
||||
return estimatedAmplitude;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double frameRate,
|
||||
int startFrame, int numFrames, double *phasePtr )
|
||||
{
|
||||
double magnitude = 0.0;
|
||||
int numLeft = numFrames;
|
||||
double phase = 0.0;
|
||||
double phaseIncrement = 2.0 * MATH_PI * frequency / frameRate;
|
||||
double sinAccumulator = 0.0;
|
||||
double cosAccumulator = 0.0;
|
||||
float *data = &recording->buffer[startFrame];
|
||||
|
||||
QA_ASSERT_TRUE( "startFrame out of bounds", (startFrame < recording->numFrames) );
|
||||
QA_ASSERT_TRUE( "numFrames out of bounds", ((startFrame+numFrames) <= recording->numFrames) );
|
||||
|
||||
while( numLeft > 0 )
|
||||
{
|
||||
double sample = (double) *data++;
|
||||
sinAccumulator += sample * sin( phase );
|
||||
cosAccumulator += sample * cos( phase );
|
||||
phase += phaseIncrement;
|
||||
if (phase > MATH_TWO_PI)
|
||||
{
|
||||
phase -= MATH_TWO_PI;
|
||||
}
|
||||
numLeft -= 1;
|
||||
}
|
||||
sinAccumulator = sinAccumulator / numFrames;
|
||||
cosAccumulator = cosAccumulator / numFrames;
|
||||
// TODO Why do I have to multiply by 2.0? Need it to make result come out right.
|
||||
magnitude = 2.0 * sqrt( (sinAccumulator * sinAccumulator) + (cosAccumulator * cosAccumulator ));
|
||||
if( phasePtr != NULL )
|
||||
{
|
||||
double phase = atan2( cosAccumulator, sinAccumulator );
|
||||
*phasePtr = phase;
|
||||
}
|
||||
return magnitude;
|
||||
error:
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
void PaQa_FilterRecording( PaQaRecording *input, PaQaRecording *output, BiquadFilter *filter )
|
||||
{
|
||||
int numToFilter = (input->numFrames > output->maxFrames) ? output->maxFrames : input->numFrames;
|
||||
BiquadFilter_Filter( filter, &input->buffer[0], &output->buffer[0], numToFilter );
|
||||
output->numFrames = numToFilter;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/** Scan until we get a correlation of a single that goes over the tolerance level,
|
||||
* peaks then drops to half the peak.
|
||||
* Look for inverse correlation as well.
|
||||
*/
|
||||
double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFrames, double threshold )
|
||||
{
|
||||
int ic,is;
|
||||
// How many buffers will fit in the recording?
|
||||
int maxCorrelations = recording->numFrames - numFrames;
|
||||
double maxSum = 0.0;
|
||||
int peakIndex = -1;
|
||||
double inverseMaxSum = 0.0;
|
||||
int inversePeakIndex = -1;
|
||||
double location = -1.0;
|
||||
|
||||
QA_ASSERT_TRUE( "numFrames out of bounds", (numFrames < recording->numFrames) );
|
||||
|
||||
for( ic=0; ic<maxCorrelations; ic++ )
|
||||
{
|
||||
int pastPeak;
|
||||
int inversePastPeak;
|
||||
|
||||
double sum = 0.0;
|
||||
// Correlate buffer against the recording.
|
||||
float *recorded = &recording->buffer[ ic ];
|
||||
for( is=0; is<numFrames; is++ )
|
||||
{
|
||||
float s1 = buffer[is];
|
||||
float s2 = *recorded++;
|
||||
sum += s1 * s2;
|
||||
}
|
||||
if( (sum > maxSum) )
|
||||
{
|
||||
maxSum = sum;
|
||||
peakIndex = ic;
|
||||
}
|
||||
if( ((-sum) > inverseMaxSum) )
|
||||
{
|
||||
inverseMaxSum = -sum;
|
||||
inversePeakIndex = ic;
|
||||
}
|
||||
pastPeak = (maxSum > threshold) && (sum < 0.5*maxSum);
|
||||
inversePastPeak = (inverseMaxSum > threshold) && ((-sum) < 0.5*inverseMaxSum);
|
||||
//printf("PaQa_FindFirstMatch: ic = %4d, sum = %8f, maxSum = %8f, inverseMaxSum = %8f\n", ic, sum, maxSum, inverseMaxSum );
|
||||
if( pastPeak && inversePastPeak )
|
||||
{
|
||||
if( maxSum > inverseMaxSum )
|
||||
{
|
||||
location = peakIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
location = inversePeakIndex;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
//printf("PaQa_FindFirstMatch: location = %4d\n", (int)location );
|
||||
return location;
|
||||
error:
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
// Measure the area under the curve by summing absolute value of each value.
|
||||
double PaQa_MeasureArea( float *buffer, int numFrames, int stride )
|
||||
{
|
||||
int is;
|
||||
double area = 0.0;
|
||||
for( is=0; is<numFrames; is++ )
|
||||
{
|
||||
area += fabs( *buffer );
|
||||
buffer += stride;
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
// Measure the area under the curve by summing absolute value of each value.
|
||||
double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames )
|
||||
{
|
||||
int is;
|
||||
double area = 0.0;
|
||||
double root;
|
||||
for( is=0; is<numFrames; is++ )
|
||||
{
|
||||
float value = *buffer++;
|
||||
area += value * value;
|
||||
}
|
||||
root = sqrt( area );
|
||||
return root / numFrames;
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================================*/
|
||||
// Compare the amplitudes of these two signals.
|
||||
// Return ratio of recorded signal over buffer signal.
|
||||
|
||||
double PaQa_CompareAmplitudes( PaQaRecording *recording, int startAt, float *buffer, int numFrames )
|
||||
{
|
||||
QA_ASSERT_TRUE( "startAt+numFrames out of bounds", ((startAt+numFrames) < recording->numFrames) );
|
||||
|
||||
{
|
||||
double recordedArea = PaQa_MeasureArea( &recording->buffer[startAt], numFrames, 1 );
|
||||
double bufferArea = PaQa_MeasureArea( buffer, numFrames, 1 );
|
||||
if( bufferArea == 0.0 ) return 100000000.0;
|
||||
return recordedArea / bufferArea;
|
||||
}
|
||||
error:
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================================*/
|
||||
double PaQa_ComputePhaseDifference( double phase1, double phase2 )
|
||||
{
|
||||
double delta = phase1 - phase2;
|
||||
while( delta > MATH_PI )
|
||||
{
|
||||
delta -= MATH_TWO_PI;
|
||||
}
|
||||
while( delta < -MATH_PI )
|
||||
{
|
||||
delta += MATH_TWO_PI;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_MeasureLatency( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
||||
{
|
||||
double threshold;
|
||||
PaQaSineGenerator generator;
|
||||
#define MAX_BUFFER_SIZE 2048
|
||||
float buffer[MAX_BUFFER_SIZE];
|
||||
double period = testTone->sampleRate / testTone->frequency;
|
||||
int cycleSize = (int) (period + 0.5);
|
||||
//printf("PaQa_AnalyseRecording: frequency = %8f, frameRate = %8f, period = %8f, cycleSize = %8d\n",
|
||||
// testTone->frequency, testTone->sampleRate, period, cycleSize );
|
||||
analysisResult->latency = -1;
|
||||
analysisResult->valid = (0);
|
||||
|
||||
// Set up generator to find matching first cycle.
|
||||
QA_ASSERT_TRUE( "cycleSize out of bounds", (cycleSize < MAX_BUFFER_SIZE) );
|
||||
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
|
||||
PaQa_EraseBuffer( buffer, cycleSize, testTone->samplesPerFrame );
|
||||
PaQa_MixSine( &generator, buffer, cycleSize, testTone->samplesPerFrame );
|
||||
|
||||
threshold = cycleSize * 0.02;
|
||||
analysisResult->latency = PaQa_FindFirstMatch( recording, buffer, cycleSize, threshold );
|
||||
QA_ASSERT_TRUE( "Could not find the start of the signal.", (analysisResult->latency >= 0) );
|
||||
analysisResult->amplitudeRatio = PaQa_CompareAmplitudes( recording, analysisResult->latency, buffer, cycleSize );
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
// Apply cosine squared window.
|
||||
void PaQa_FadeInRecording( PaQaRecording *recording, int startFrame, int count )
|
||||
{
|
||||
int is;
|
||||
double phase = 0.5 * MATH_PI;
|
||||
// Advance a quarter wave
|
||||
double phaseIncrement = 0.25 * 2.0 * MATH_PI / count;
|
||||
|
||||
assert( startFrame >= 0 );
|
||||
assert( count > 0 );
|
||||
|
||||
/* Zero out initial part of the recording. */
|
||||
for( is=0; is<startFrame; is++ )
|
||||
{
|
||||
recording->buffer[ is ] = 0.0f;
|
||||
}
|
||||
/* Fade in where signal begins. */
|
||||
for( is=0; is<count; is++ )
|
||||
{
|
||||
double c = cos( phase );
|
||||
double w = c * c;
|
||||
float x = recording->buffer[ is + startFrame ];
|
||||
float y = x * w;
|
||||
//printf("FADE %d : w=%f, x=%f, y=%f\n", is, w, x, y );
|
||||
recording->buffer[ is + startFrame ] = y;
|
||||
|
||||
phase += phaseIncrement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================================*/
|
||||
/** Apply notch filter and high pass filter then detect remaining energy.
|
||||
*/
|
||||
int PaQa_DetectPop( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
||||
{
|
||||
int result = 0;
|
||||
int i;
|
||||
double maxAmplitude;
|
||||
int maxPosition;
|
||||
|
||||
PaQaRecording notchOutput = { 0 };
|
||||
BiquadFilter notchFilter;
|
||||
|
||||
PaQaRecording hipassOutput = { 0 };
|
||||
BiquadFilter hipassFilter;
|
||||
|
||||
int frameRate = (int) recording->sampleRate;
|
||||
|
||||
analysisResult->popPosition = -1;
|
||||
analysisResult->popAmplitude = 0.0;
|
||||
|
||||
result = PaQa_InitializeRecording( ¬chOutput, recording->numFrames, frameRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
result = PaQa_InitializeRecording( &hipassOutput, recording->numFrames, frameRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
// Use notch filter to remove test tone.
|
||||
BiquadFilter_SetupNotch( ¬chFilter, testTone->frequency / frameRate, 0.5 );
|
||||
PaQa_FilterRecording( recording, ¬chOutput, ¬chFilter );
|
||||
//result = PaQa_SaveRecordingToWaveFile( ¬chOutput, "notch_output.wav" );
|
||||
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
||||
|
||||
// Apply fade-in window.
|
||||
PaQa_FadeInRecording( ¬chOutput, (int) analysisResult->latency, 500 );
|
||||
|
||||
// Use high pass to accentuate the edges of a pop. At higher frequency!
|
||||
BiquadFilter_SetupHighPass( &hipassFilter, 2.0 * testTone->frequency / frameRate, 0.5 );
|
||||
PaQa_FilterRecording( ¬chOutput, &hipassOutput, &hipassFilter );
|
||||
//result = PaQa_SaveRecordingToWaveFile( &hipassOutput, "hipass_output.wav" );
|
||||
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
||||
|
||||
// Scan remaining signal looking for peak.
|
||||
maxAmplitude = 0.0;
|
||||
maxPosition = -1;
|
||||
for( i=(int) analysisResult->latency; i<hipassOutput.numFrames; i++ )
|
||||
{
|
||||
float x = hipassOutput.buffer[i];
|
||||
float mag = fabs( x );
|
||||
if( mag > maxAmplitude )
|
||||
{
|
||||
maxAmplitude = mag;
|
||||
maxPosition = i;
|
||||
}
|
||||
}
|
||||
|
||||
if( maxAmplitude > PAQA_POP_THRESHOLD )
|
||||
{
|
||||
analysisResult->popPosition = maxPosition;
|
||||
analysisResult->popAmplitude = maxAmplitude;
|
||||
}
|
||||
|
||||
PaQa_TerminateRecording( ¬chOutput );
|
||||
PaQa_TerminateRecording( &hipassOutput );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_TerminateRecording( ¬chOutput );
|
||||
PaQa_TerminateRecording( &hipassOutput );
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_DetectPhaseError( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
||||
{
|
||||
int i;
|
||||
double period = testTone->sampleRate / testTone->frequency;
|
||||
int cycleSize = (int) (period + 0.5);
|
||||
|
||||
double maxAddedFrames = 0.0;
|
||||
double maxDroppedFrames = 0.0;
|
||||
|
||||
double previousPhase = 0.0;
|
||||
double previousFrameError = 0;
|
||||
int loopCount = 0;
|
||||
int skip = cycleSize;
|
||||
int windowSize = cycleSize;
|
||||
|
||||
// Scan recording starting with first cycle, looking for phase errors.
|
||||
analysisResult->numDroppedFrames = 0.0;
|
||||
analysisResult->numAddedFrames = 0.0;
|
||||
analysisResult->droppedFramesPosition = -1.0;
|
||||
analysisResult->addedFramesPosition = -1.0;
|
||||
|
||||
for( i=analysisResult->latency; i<(recording->numFrames - windowSize); i += skip )
|
||||
{
|
||||
double expectedPhase = previousPhase + (skip * MATH_TWO_PI / period);
|
||||
double expectedPhaseIncrement = PaQa_ComputePhaseDifference( expectedPhase, previousPhase );
|
||||
|
||||
double phase = 666.0;
|
||||
double mag = PaQa_CorrelateSine( recording, testTone->frequency, testTone->sampleRate, i, windowSize, &phase );
|
||||
if( (loopCount > 1) && (mag > 0.0) )
|
||||
{
|
||||
double phaseDelta = PaQa_ComputePhaseDifference( phase, previousPhase );
|
||||
double phaseError = PaQa_ComputePhaseDifference( phaseDelta, expectedPhaseIncrement );
|
||||
// Convert phaseError to equivalent number of frames.
|
||||
double frameError = period * phaseError / MATH_TWO_PI;
|
||||
double consecutiveFrameError = frameError + previousFrameError;
|
||||
// if( fabs(frameError) > 0.01 )
|
||||
// {
|
||||
// printf("FFFFFFFFFFFFF frameError = %f, at %d\n", frameError, i );
|
||||
// }
|
||||
if( consecutiveFrameError > 0.8 )
|
||||
{
|
||||
double droppedFrames = consecutiveFrameError;
|
||||
if (droppedFrames > (maxDroppedFrames * 1.001))
|
||||
{
|
||||
analysisResult->numDroppedFrames = droppedFrames;
|
||||
analysisResult->droppedFramesPosition = i + (windowSize/2);
|
||||
maxDroppedFrames = droppedFrames;
|
||||
}
|
||||
}
|
||||
else if( consecutiveFrameError < -0.8 )
|
||||
{
|
||||
double addedFrames = 0 - consecutiveFrameError;
|
||||
if (addedFrames > (maxAddedFrames * 1.001))
|
||||
{
|
||||
analysisResult->numAddedFrames = addedFrames;
|
||||
analysisResult->addedFramesPosition = i + (windowSize/2);
|
||||
maxAddedFrames = addedFrames;
|
||||
}
|
||||
}
|
||||
previousFrameError = frameError;
|
||||
|
||||
|
||||
//if( i<8000 )
|
||||
//{
|
||||
// printf("%d: phase = %8f, expected = %8f, delta = %8f, frameError = %8f\n", i, phase, expectedPhaseIncrement, phaseDelta, frameError );
|
||||
//}
|
||||
}
|
||||
previousPhase = phase;
|
||||
loopCount += 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
int PaQa_AnalyseRecording( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
memset( analysisResult, 0, sizeof(PaQaAnalysisResult) );
|
||||
result = PaQa_MeasureLatency( recording, testTone, analysisResult );
|
||||
QA_ASSERT_EQUALS( "latency measurement", 0, result );
|
||||
|
||||
if( (analysisResult->latency >= 0) && (analysisResult->amplitudeRatio > 0.1) )
|
||||
{
|
||||
analysisResult->valid = (1);
|
||||
|
||||
result = PaQa_DetectPop( recording, testTone, analysisResult );
|
||||
QA_ASSERT_EQUALS( "detect pop", 0, result );
|
||||
|
||||
result = PaQa_DetectPhaseError( recording, testTone, analysisResult );
|
||||
QA_ASSERT_EQUALS( "detect phase error", 0, result );
|
||||
}
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
187
lib-src/portaudio-v19/qa/loopback/src/audio_analyzer.h
Normal file
187
lib-src/portaudio-v19/qa/loopback/src/audio_analyzer.h
Normal file
@@ -0,0 +1,187 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#ifndef _AUDIO_ANALYZER_H
|
||||
#define _AUDIO_ANALYZER_H
|
||||
|
||||
#include "biquad_filter.h"
|
||||
|
||||
#define MATH_PI (3.141592653589793238462643)
|
||||
#define MATH_TWO_PI (2.0 * MATH_PI)
|
||||
|
||||
typedef struct PaQaSineGenerator_s
|
||||
{
|
||||
double phase;
|
||||
double phaseIncrement;
|
||||
double frequency;
|
||||
double amplitude;
|
||||
} PaQaSineGenerator;
|
||||
|
||||
/** Container for a monophonic audio sample in memory. */
|
||||
typedef struct PaQaRecording_s
|
||||
{
|
||||
/** Maximum number of frames that can fit in the allocated buffer. */
|
||||
int maxFrames;
|
||||
float *buffer;
|
||||
/** Actual number of valid frames in the buffer. */
|
||||
int numFrames;
|
||||
int sampleRate;
|
||||
} PaQaRecording;
|
||||
|
||||
typedef struct PaQaTestTone_s
|
||||
{
|
||||
int samplesPerFrame;
|
||||
int startDelay;
|
||||
double sampleRate;
|
||||
double frequency;
|
||||
double amplitude;
|
||||
} PaQaTestTone;
|
||||
|
||||
typedef struct PaQaAnalysisResult_s
|
||||
{
|
||||
int valid;
|
||||
/** Latency in samples from output to input. */
|
||||
double latency;
|
||||
double amplitudeRatio;
|
||||
double popAmplitude;
|
||||
double popPosition;
|
||||
double numDroppedFrames;
|
||||
double droppedFramesPosition;
|
||||
double numAddedFrames;
|
||||
double addedFramesPosition;
|
||||
} PaQaAnalysisResult;
|
||||
|
||||
|
||||
/*================================================================*/
|
||||
/*================= General DSP Tools ============================*/
|
||||
/*================================================================*/
|
||||
/**
|
||||
* Calculate Nth frequency of a series for use in testing multiple channels.
|
||||
* Series should avoid harmonic overlap between channels.
|
||||
*/
|
||||
double PaQa_GetNthFrequency( double baseFrequency, int index );
|
||||
|
||||
void PaQa_EraseBuffer( float *buffer, int numFrames, int samplesPerFrame );
|
||||
|
||||
void PaQa_MixSine( PaQaSineGenerator *generator, float *buffer, int numSamples, int stride );
|
||||
|
||||
void PaQa_WriteSine( float *buffer, int numSamples, int stride,
|
||||
double frequency, double amplitude );
|
||||
|
||||
/**
|
||||
* Generate a signal with a sharp edge in the middle that can be recognized despite some phase shift.
|
||||
*/
|
||||
void PaQa_GenerateCrack( float *buffer, int numSamples, int stride );
|
||||
|
||||
double PaQa_ComputePhaseDifference( double phase1, double phase2 );
|
||||
|
||||
/**
|
||||
* Measure the area under the curve by summing absolute value of each value.
|
||||
*/
|
||||
double PaQa_MeasureArea( float *buffer, int numFrames, int stride );
|
||||
|
||||
/**
|
||||
* Measure slope of the positive zero crossings.
|
||||
*/
|
||||
double PaQa_MeasureCrossingSlope( float *buffer, int numFrames );
|
||||
|
||||
|
||||
/**
|
||||
* Prepare an oscillator that can generate a sine tone for testing.
|
||||
*/
|
||||
void PaQa_SetupSineGenerator( PaQaSineGenerator *generator, double frequency, double amplitude, double frameRate );
|
||||
|
||||
/*================================================================*/
|
||||
/*================= Recordings ===================================*/
|
||||
/*================================================================*/
|
||||
/**
|
||||
* Allocate memory for containg a mono audio signal. Set up recording for writing.
|
||||
*/
|
||||
int PaQa_InitializeRecording( PaQaRecording *recording, int maxSamples, int sampleRate );
|
||||
|
||||
/**
|
||||
* Free memory allocated by PaQa_InitializeRecording.
|
||||
*/
|
||||
void PaQa_TerminateRecording( PaQaRecording *recording );
|
||||
|
||||
/**
|
||||
* Apply a biquad filter to the audio from the input recording and write it to the output recording.
|
||||
*/
|
||||
void PaQa_FilterRecording( PaQaRecording *input, PaQaRecording *output, BiquadFilter *filter );
|
||||
|
||||
|
||||
int PaQa_SaveRecordingToWaveFile( PaQaRecording *recording, const char *filename );
|
||||
|
||||
/**
|
||||
* @param stride is the spacing of samples to skip in the input buffer. To use every samples pass 1. To use every other sample pass 2.
|
||||
*/
|
||||
int PaQa_WriteRecording( PaQaRecording *recording, float *buffer, int numSamples, int stride );
|
||||
|
||||
/** Write zeros into a recording. */
|
||||
int PaQa_WriteSilence( PaQaRecording *recording, int numSamples );
|
||||
|
||||
int PaQa_RecordFreeze( PaQaRecording *recording, int numSamples );
|
||||
|
||||
double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double frameRate,
|
||||
int startFrame, int numSamples, double *phasePtr );
|
||||
|
||||
double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numSamples, double tolerance );
|
||||
|
||||
/**
|
||||
* Estimate the original amplitude of a clipped sine wave by measuring
|
||||
* its average slope at the zero crossings.
|
||||
*/
|
||||
double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
|
||||
double frequency, double frameRate,
|
||||
int startFrame, int numFrames );
|
||||
|
||||
double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames );
|
||||
|
||||
/**
|
||||
* Compare the amplitudes of these two signals.
|
||||
* Return ratio of recorded signal over buffer signal.
|
||||
*/
|
||||
double PaQa_CompareAmplitudes( PaQaRecording *recording, int startAt, float *buffer, int numSamples );
|
||||
|
||||
/**
|
||||
* Analyse a recording of a sine wave.
|
||||
* Measure latency and look for dropped frames, etc.
|
||||
*/
|
||||
int PaQa_AnalyseRecording( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult );
|
||||
|
||||
#endif /* _AUDIO_ANALYZER_H */
|
122
lib-src/portaudio-v19/qa/loopback/src/biquad_filter.c
Executable file
122
lib-src/portaudio-v19/qa/loopback/src/biquad_filter.c
Executable file
@@ -0,0 +1,122 @@
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "biquad_filter.h"
|
||||
|
||||
/**
|
||||
* Unit_BiquadFilter implements a second order IIR filter.
|
||||
Here is the equation that we use for this filter:
|
||||
y(n) = a0*x(n) + a1*x(n-1) + a2*x(n-2) - b1*y(n-1) - b2*y(n-2)
|
||||
*
|
||||
* @author (C) 2002 Phil Burk, SoftSynth.com, All Rights Reserved
|
||||
*/
|
||||
|
||||
#define FILTER_PI (3.141592653589793238462643)
|
||||
/***********************************************************
|
||||
** Calculate coefficients common to many parametric biquad filters.
|
||||
*/
|
||||
static void BiquadFilter_CalculateCommon( BiquadFilter *filter, double ratio, double Q )
|
||||
{
|
||||
double omega;
|
||||
|
||||
memset( filter, 0, sizeof(BiquadFilter) );
|
||||
|
||||
/* Don't let frequency get too close to Nyquist or filter will blow up. */
|
||||
if( ratio >= 0.499 ) ratio = 0.499;
|
||||
omega = 2.0 * (double)FILTER_PI * ratio;
|
||||
|
||||
filter->cos_omega = (double) cos( omega );
|
||||
filter->sin_omega = (double) sin( omega );
|
||||
filter->alpha = filter->sin_omega / (2.0 * Q);
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
** Calculate coefficients for Highpass filter.
|
||||
*/
|
||||
void BiquadFilter_SetupHighPass( BiquadFilter *filter, double ratio, double Q )
|
||||
{
|
||||
double scalar, opc;
|
||||
|
||||
if( ratio < BIQUAD_MIN_RATIO ) ratio = BIQUAD_MIN_RATIO;
|
||||
if( Q < BIQUAD_MIN_Q ) Q = BIQUAD_MIN_Q;
|
||||
|
||||
BiquadFilter_CalculateCommon( filter, ratio, Q );
|
||||
|
||||
scalar = 1.0 / (1.0 + filter->alpha);
|
||||
opc = (1.0 + filter->cos_omega);
|
||||
|
||||
filter->a0 = opc * 0.5 * scalar;
|
||||
filter->a1 = - opc * scalar;
|
||||
filter->a2 = filter->a0;
|
||||
filter->b1 = -2.0 * filter->cos_omega * scalar;
|
||||
filter->b2 = (1.0 - filter->alpha) * scalar;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
** Calculate coefficients for Notch filter.
|
||||
*/
|
||||
void BiquadFilter_SetupNotch( BiquadFilter *filter, double ratio, double Q )
|
||||
{
|
||||
double scalar, opc;
|
||||
|
||||
if( ratio < BIQUAD_MIN_RATIO ) ratio = BIQUAD_MIN_RATIO;
|
||||
if( Q < BIQUAD_MIN_Q ) Q = BIQUAD_MIN_Q;
|
||||
|
||||
BiquadFilter_CalculateCommon( filter, ratio, Q );
|
||||
|
||||
scalar = 1.0 / (1.0 + filter->alpha);
|
||||
opc = (1.0 + filter->cos_omega);
|
||||
|
||||
filter->a0 = scalar;
|
||||
filter->a1 = -2.0 * filter->cos_omega * scalar;
|
||||
filter->a2 = filter->a0;
|
||||
filter->b1 = filter->a1;
|
||||
filter->b2 = (1.0 - filter->alpha) * scalar;
|
||||
}
|
||||
|
||||
/*****************************************************************
|
||||
** Perform core IIR filter calculation without permutation.
|
||||
*/
|
||||
void BiquadFilter_Filter( BiquadFilter *filter, float *inputs, float *outputs, int numSamples )
|
||||
{
|
||||
int i;
|
||||
double xn, yn;
|
||||
// Pull values from structure to speed up the calculation.
|
||||
double a0 = filter->a0;
|
||||
double a1 = filter->a1;
|
||||
double a2 = filter->a2;
|
||||
double b1 = filter->b1;
|
||||
double b2 = filter->b2;
|
||||
double xn1 = filter->xn1;
|
||||
double xn2 = filter->xn2;
|
||||
double yn1 = filter->yn1;
|
||||
double yn2 = filter->yn2;
|
||||
|
||||
for( i=0; i<numSamples; i++)
|
||||
{
|
||||
// Generate outputs by filtering inputs.
|
||||
xn = inputs[i];
|
||||
yn = (a0 * xn) + (a1 * xn1) + (a2 * xn2) - (b1 * yn1) - (b2 * yn2);
|
||||
outputs[i] = yn;
|
||||
|
||||
// Delay input and output values.
|
||||
xn2 = xn1;
|
||||
xn1 = xn;
|
||||
yn2 = yn1;
|
||||
yn1 = yn;
|
||||
|
||||
if( (i & 7) == 0 )
|
||||
{
|
||||
// Apply a small bipolar impulse to filter to prevent arithmetic underflow.
|
||||
// Underflows can cause the FPU to interrupt the CPU.
|
||||
yn1 += (double) 1.0E-26;
|
||||
yn2 -= (double) 1.0E-26;
|
||||
}
|
||||
}
|
||||
|
||||
filter->xn1 = xn1;
|
||||
filter->xn2 = xn2;
|
||||
filter->yn1 = yn1;
|
||||
filter->yn2 = yn2;
|
||||
}
|
38
lib-src/portaudio-v19/qa/loopback/src/biquad_filter.h
Executable file
38
lib-src/portaudio-v19/qa/loopback/src/biquad_filter.h
Executable file
@@ -0,0 +1,38 @@
|
||||
#ifndef _BIQUADFILTER_H
|
||||
#define _BIQUADFILTER_H
|
||||
|
||||
|
||||
/**
|
||||
* Unit_BiquadFilter implements a second order IIR filter.
|
||||
*
|
||||
* @author (C) 2002 Phil Burk, SoftSynth.com, All Rights Reserved
|
||||
*/
|
||||
|
||||
#define BIQUAD_MIN_RATIO (0.000001)
|
||||
#define BIQUAD_MIN_Q (0.00001)
|
||||
|
||||
typedef struct BiquadFilter_s
|
||||
{
|
||||
double xn1; // storage for delayed signals
|
||||
double xn2;
|
||||
double yn1;
|
||||
double yn2;
|
||||
|
||||
double a0; // coefficients
|
||||
double a1;
|
||||
double a2;
|
||||
|
||||
double b1;
|
||||
double b2;
|
||||
|
||||
double cos_omega;
|
||||
double sin_omega;
|
||||
double alpha;
|
||||
} BiquadFilter;
|
||||
|
||||
void BiquadFilter_SetupHighPass( BiquadFilter *filter, double ratio, double Q );
|
||||
void BiquadFilter_SetupNotch( BiquadFilter *filter, double ratio, double Q );
|
||||
|
||||
void BiquadFilter_Filter( BiquadFilter *filter, float *inputs, float *outputs, int numSamples );
|
||||
|
||||
#endif
|
1601
lib-src/portaudio-v19/qa/loopback/src/paqa.c
Normal file
1601
lib-src/portaudio-v19/qa/loopback/src/paqa.c
Normal file
File diff suppressed because it is too large
Load Diff
171
lib-src/portaudio-v19/qa/loopback/src/paqa_tools.c
Normal file
171
lib-src/portaudio-v19/qa/loopback/src/paqa_tools.c
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#include "paqa_tools.h"
|
||||
|
||||
|
||||
/*******************************************************************/
|
||||
void PaQa_ListAudioDevices(void)
|
||||
{
|
||||
int i, numDevices;
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
numDevices = Pa_GetDeviceCount();
|
||||
for( i=0; i<numDevices; i++ )
|
||||
{
|
||||
deviceInfo = Pa_GetDeviceInfo( i );
|
||||
printf( "#%d: ", i );
|
||||
printf( "%2d in", deviceInfo->maxInputChannels );
|
||||
printf( ", %2d out", deviceInfo->maxOutputChannels );
|
||||
printf( ", %s", deviceInfo->name );
|
||||
printf( ", on %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name );
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
void PaQa_ConvertToFloat( const void *input, int numSamples, PaSampleFormat inFormat, float *output )
|
||||
{
|
||||
int i;
|
||||
switch( inFormat )
|
||||
{
|
||||
case paUInt8:
|
||||
{
|
||||
unsigned char *data = (unsigned char *)input;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
int value = *data++;
|
||||
value -= 128;
|
||||
*output++ = value / 128.0f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt8:
|
||||
{
|
||||
char *data = (char *)input;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
int value = *data++;
|
||||
*output++ = value / 128.0f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt16:
|
||||
{
|
||||
short *data = (short *)input;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
*output++ = *data++ / 32768.0f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt32:
|
||||
{
|
||||
int *data = (int *)input;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
int value = (*data++) >> 8;
|
||||
float fval = (float) (value / ((double) 0x00800000));
|
||||
*output++ = fval;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
void PaQa_ConvertFromFloat( const float *input, int numSamples, PaSampleFormat outFormat, void *output )
|
||||
{
|
||||
int i;
|
||||
switch( outFormat )
|
||||
{
|
||||
case paUInt8:
|
||||
{
|
||||
unsigned char *data = (unsigned char *)output;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
float value = *input++;
|
||||
int byte = ((int) (value * 127)) + 128;
|
||||
*data++ = (unsigned char) byte;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt8:
|
||||
{
|
||||
char *data = (char *)output;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
float value = *input++;
|
||||
int byte = (int) (value * 127);
|
||||
*data++ = (char) byte;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt16:
|
||||
{
|
||||
short *data = (short *)output;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
float value = *input++;
|
||||
// Use assymmetric conversion to avoid clipping.
|
||||
short sval = value * 32767.0;
|
||||
*data++ = sval;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt32:
|
||||
{
|
||||
int *data = (int *)output;
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
float value = *input++;
|
||||
// Use assymmetric conversion to avoid clipping.
|
||||
int ival = value * ((double) 0x007FFFF0);
|
||||
ival = ival << 8;
|
||||
*data++ = ival;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
52
lib-src/portaudio-v19/qa/loopback/src/paqa_tools.h
Normal file
52
lib-src/portaudio-v19/qa/loopback/src/paqa_tools.h
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#ifndef _PAQA_TOOLS_H
|
||||
#define _PAQA_TOOLS_H
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include "portaudio.h"
|
||||
|
||||
void PaQa_ListAudioDevices(void);
|
||||
|
||||
void PaQa_ConvertToFloat( const void *input, int numSamples, PaSampleFormat inFormat, float *output );
|
||||
|
||||
void PaQa_ConvertFromFloat( const float *input, int numSamples, PaSampleFormat outFormat, void *output );
|
||||
|
||||
#endif /* _PAQA_TOOLS_H */
|
74
lib-src/portaudio-v19/qa/loopback/src/qa_tools.h
Executable file
74
lib-src/portaudio-v19/qa/loopback/src/qa_tools.h
Executable file
@@ -0,0 +1,74 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#ifndef _QA_TOOLS_H
|
||||
#define _QA_TOOLS_H
|
||||
|
||||
extern int g_testsPassed;
|
||||
extern int g_testsFailed;
|
||||
|
||||
#define QA_ASSERT_TRUE( message, flag ) \
|
||||
if( !(flag) ) \
|
||||
{ \
|
||||
printf( "%s:%d - ERROR - %s\n", __FILE__, __LINE__, message ); \
|
||||
g_testsFailed++; \
|
||||
goto error; \
|
||||
} \
|
||||
else g_testsPassed++;
|
||||
|
||||
|
||||
#define QA_ASSERT_EQUALS( message, expected, actual ) \
|
||||
if( ((expected) != (actual)) ) \
|
||||
{ \
|
||||
printf( "%s:%d - ERROR - %s, expected %d, got %d\n", __FILE__, __LINE__, message, expected, actual ); \
|
||||
g_testsFailed++; \
|
||||
goto error; \
|
||||
} \
|
||||
else g_testsPassed++;
|
||||
|
||||
#define QA_ASSERT_CLOSE( message, expected, actual, tolerance ) \
|
||||
if (fabs((expected)-(actual))>(tolerance)) \
|
||||
{ \
|
||||
printf( "%s:%d - ERROR - %s, expected %f, got %f, tol=%f\n", __FILE__, __LINE__, message, ((double)(expected)), ((double)(actual)), ((double)(tolerance)) ); \
|
||||
g_testsFailed++; \
|
||||
goto error; \
|
||||
} \
|
||||
else g_testsPassed++;
|
||||
|
||||
|
||||
#endif
|
718
lib-src/portaudio-v19/qa/loopback/src/test_audio_analyzer.c
Normal file
718
lib-src/portaudio-v19/qa/loopback/src/test_audio_analyzer.c
Normal file
@@ -0,0 +1,718 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "qa_tools.h"
|
||||
#include "audio_analyzer.h"
|
||||
#include "test_audio_analyzer.h"
|
||||
#include "write_wav.h"
|
||||
#include "biquad_filter.h"
|
||||
|
||||
#define FRAMES_PER_BLOCK (64)
|
||||
#define PRINT_REPORTS 0
|
||||
|
||||
#define TEST_SAVED_WAVE (0)
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Detect a single tone.
|
||||
*/
|
||||
static int TestSingleMonoTone( void )
|
||||
{
|
||||
int result = 0;
|
||||
PaQaSineGenerator generator;
|
||||
PaQaRecording recording;
|
||||
float buffer[FRAMES_PER_BLOCK];
|
||||
double sampleRate = 44100.0;
|
||||
int maxFrames = ((int)sampleRate) * 1;
|
||||
int samplesPerFrame = 1;
|
||||
int stride = 1;
|
||||
int done = 0;
|
||||
|
||||
double freq = 234.5;
|
||||
double amp = 0.5;
|
||||
|
||||
double mag1, mag2;
|
||||
|
||||
// Setup a sine oscillator.
|
||||
PaQa_SetupSineGenerator( &generator, freq, amp, sampleRate );
|
||||
|
||||
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
done = 0;
|
||||
while (!done)
|
||||
{
|
||||
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame );
|
||||
PaQa_MixSine( &generator, buffer, FRAMES_PER_BLOCK, stride );
|
||||
done = PaQa_WriteRecording( &recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame );
|
||||
}
|
||||
|
||||
mag1 = PaQa_CorrelateSine( &recording, freq, sampleRate, 0, recording.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "exact frequency match", amp, mag1, 0.01 );
|
||||
|
||||
mag2 = PaQa_CorrelateSine( &recording, freq * 1.23, sampleRate, 0, recording.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "wrong frequency", 0.0, mag2, 0.01 );
|
||||
|
||||
PaQa_TerminateRecording( &recording );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_TerminateRecording( &recording);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Mix multiple tones and then detect them.
|
||||
*/
|
||||
|
||||
static int TestMixedMonoTones( void )
|
||||
{
|
||||
int i;
|
||||
int result = 0;
|
||||
#define NUM_TONES (5)
|
||||
PaQaSineGenerator generators[NUM_TONES];
|
||||
PaQaRecording recording;
|
||||
float buffer[FRAMES_PER_BLOCK];
|
||||
double sampleRate = 44100.0;
|
||||
int maxFrames = ((int)sampleRate) * 1;
|
||||
int samplesPerFrame = 1;
|
||||
|
||||
double baseFreq = 234.5;
|
||||
double amp = 0.1;
|
||||
|
||||
double mag2;
|
||||
|
||||
int stride = samplesPerFrame;
|
||||
int done = 0;
|
||||
|
||||
// Setup a sine oscillator.
|
||||
for( i=0; i<NUM_TONES; i++ )
|
||||
{
|
||||
PaQa_SetupSineGenerator( &generators[i], PaQa_GetNthFrequency( baseFreq, i ), amp, sampleRate );
|
||||
}
|
||||
|
||||
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
done = 0;
|
||||
while (!done)
|
||||
{
|
||||
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame );
|
||||
for( i=0; i<NUM_TONES; i++ )
|
||||
{
|
||||
PaQa_MixSine( &generators[i], buffer, FRAMES_PER_BLOCK, stride );
|
||||
}
|
||||
done = PaQa_WriteRecording( &recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame );
|
||||
}
|
||||
|
||||
for( i=0; i<NUM_TONES; i++ )
|
||||
{
|
||||
double mag = PaQa_CorrelateSine( &recording, PaQa_GetNthFrequency( baseFreq, i), sampleRate, 0, recording.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "exact frequency match", amp, mag, 0.01 );
|
||||
}
|
||||
|
||||
mag2 = PaQa_CorrelateSine( &recording, baseFreq * 0.87, sampleRate, 0, recording.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "wrong frequency", 0.0, mag2, 0.01 );
|
||||
|
||||
PaQa_TerminateRecording( &recording );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_TerminateRecording( &recording);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Generate a recording with added or dropped frames.
|
||||
*/
|
||||
|
||||
static void MakeRecordingWithAddedFrames( PaQaRecording *recording, PaQaTestTone *testTone, int glitchPosition, int framesToAdd )
|
||||
{
|
||||
PaQaSineGenerator generator;
|
||||
#define BUFFER_SIZE 512
|
||||
float buffer[BUFFER_SIZE];
|
||||
|
||||
int frameCounter = testTone->startDelay;
|
||||
|
||||
int stride = 1;
|
||||
// Record some initial silence.
|
||||
int done = PaQa_WriteSilence( recording, testTone->startDelay );
|
||||
|
||||
// Setup a sine oscillator.
|
||||
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
|
||||
|
||||
while (!done)
|
||||
{
|
||||
int framesThisLoop = BUFFER_SIZE;
|
||||
|
||||
if( frameCounter == glitchPosition )
|
||||
{
|
||||
if( framesToAdd > 0 )
|
||||
{
|
||||
// Record some frozen data without advancing the sine generator.
|
||||
done = PaQa_RecordFreeze( recording, framesToAdd );
|
||||
frameCounter += framesToAdd;
|
||||
}
|
||||
else if( framesToAdd < 0 )
|
||||
{
|
||||
// Advance sine generator a few frames.
|
||||
PaQa_MixSine( &generator, buffer, 0 - framesToAdd, stride );
|
||||
}
|
||||
|
||||
}
|
||||
else if( (frameCounter < glitchPosition) && ((frameCounter + framesThisLoop) > glitchPosition) )
|
||||
{
|
||||
// Go right up to the glitchPosition.
|
||||
framesThisLoop = glitchPosition - frameCounter;
|
||||
}
|
||||
|
||||
if( framesThisLoop > 0 )
|
||||
{
|
||||
PaQa_EraseBuffer( buffer, framesThisLoop, testTone->samplesPerFrame );
|
||||
PaQa_MixSine( &generator, buffer, framesThisLoop, stride );
|
||||
done = PaQa_WriteRecording( recording, buffer, framesThisLoop, testTone->samplesPerFrame );
|
||||
}
|
||||
frameCounter += framesThisLoop;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Generate a clean recording.
|
||||
*/
|
||||
|
||||
static void MakeCleanRecording( PaQaRecording *recording, PaQaTestTone *testTone )
|
||||
{
|
||||
PaQaSineGenerator generator;
|
||||
#define BUFFER_SIZE 512
|
||||
float buffer[BUFFER_SIZE];
|
||||
|
||||
int stride = 1;
|
||||
// Record some initial silence.
|
||||
int done = PaQa_WriteSilence( recording, testTone->startDelay );
|
||||
|
||||
// Setup a sine oscillator.
|
||||
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
|
||||
|
||||
// Generate recording with good phase.
|
||||
while (!done)
|
||||
{
|
||||
PaQa_EraseBuffer( buffer, BUFFER_SIZE, testTone->samplesPerFrame );
|
||||
PaQa_MixSine( &generator, buffer, BUFFER_SIZE, stride );
|
||||
done = PaQa_WriteRecording( recording, buffer, BUFFER_SIZE, testTone->samplesPerFrame );
|
||||
}
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Generate a recording with pop.
|
||||
*/
|
||||
|
||||
static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude )
|
||||
{
|
||||
int i;
|
||||
|
||||
MakeCleanRecording( recording, testTone );
|
||||
|
||||
// Apply glitch to good recording.
|
||||
if( (popPosition + popWidth) >= recording->numFrames )
|
||||
{
|
||||
popWidth = (recording->numFrames - popPosition) - 1;
|
||||
}
|
||||
|
||||
for( i=0; i<popWidth; i++ )
|
||||
{
|
||||
float good = recording->buffer[i+popPosition];
|
||||
float bad = (good > 0.0) ? (good - popAmplitude) : (good + popAmplitude);
|
||||
recording->buffer[i+popPosition] = bad;
|
||||
}
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Detect one phase error in a recording.
|
||||
*/
|
||||
static int TestDetectSinglePhaseError( double sampleRate, int cycleSize, int latencyFrames, int glitchPosition, int framesAdded )
|
||||
{
|
||||
int result = 0;
|
||||
PaQaRecording recording;
|
||||
PaQaTestTone testTone;
|
||||
PaQaAnalysisResult analysisResult = { 0.0 };
|
||||
int framesDropped = 0;
|
||||
int maxFrames = ((int)sampleRate) * 2;
|
||||
|
||||
testTone.samplesPerFrame = 1;
|
||||
testTone.sampleRate = sampleRate;
|
||||
testTone.frequency = sampleRate / cycleSize;
|
||||
testTone.amplitude = 0.5;
|
||||
testTone.startDelay = latencyFrames;
|
||||
|
||||
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
MakeRecordingWithAddedFrames( &recording, &testTone, glitchPosition, framesAdded );
|
||||
|
||||
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult );
|
||||
|
||||
if( framesAdded < 0 )
|
||||
{
|
||||
framesDropped = -framesAdded;
|
||||
framesAdded = 0;
|
||||
}
|
||||
|
||||
#if PRINT_REPORTS
|
||||
printf("\n=== Dropped Frame Analysis ===================\n");
|
||||
printf(" expected actual\n");
|
||||
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency );
|
||||
printf(" num added frames: %10.3f %10.3f\n", (double)framesAdded, analysisResult.numAddedFrames );
|
||||
printf(" added frames at: %10.3f %10.3f\n", (double)glitchPosition, analysisResult.addedFramesPosition );
|
||||
printf(" num dropped frames: %10.3f %10.3f\n", (double)framesDropped, analysisResult.numDroppedFrames );
|
||||
printf(" dropped frames at: %10.3f %10.3f\n", (double)glitchPosition, analysisResult.droppedFramesPosition );
|
||||
#endif
|
||||
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", latencyFrames, analysisResult.latency, 0.5 );
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording framesAdded", framesAdded, analysisResult.numAddedFrames, 1.0 );
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording framesDropped", framesDropped, analysisResult.numDroppedFrames, 1.0 );
|
||||
// QA_ASSERT_CLOSE( "PaQa_AnalyseRecording glitchPosition", glitchPosition, analysisResult.glitchPosition, cycleSize );
|
||||
|
||||
PaQa_TerminateRecording( &recording );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_TerminateRecording( &recording);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Test various dropped sample scenarios.
|
||||
*/
|
||||
static int TestDetectPhaseErrors( void )
|
||||
{
|
||||
int result;
|
||||
|
||||
result = TestDetectSinglePhaseError( 44100, 200, 477, -1, 0 );
|
||||
if( result < 0 ) return result;
|
||||
/*
|
||||
result = TestDetectSinglePhaseError( 44100, 200, 77, -1, 0 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
result = TestDetectSinglePhaseError( 44100, 200, 83, 3712, 9 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
result = TestDetectSinglePhaseError( 44100, 280, 83, 3712, 27 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
result = TestDetectSinglePhaseError( 44100, 200, 234, 3712, -9 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
result = TestDetectSinglePhaseError( 44100, 200, 2091, 8923, -2 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
result = TestDetectSinglePhaseError( 44100, 120, 1782, 5772, -18 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// Note that if the frequency is too high then it is hard to detect single dropped frames.
|
||||
result = TestDetectSinglePhaseError( 44100, 200, 500, 4251, -1 );
|
||||
if( result < 0 ) return result;
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Detect one pop in a recording.
|
||||
*/
|
||||
static int TestDetectSinglePop( double sampleRate, int cycleSize, int latencyFrames, int popPosition, int popWidth, double popAmplitude )
|
||||
{
|
||||
int result = 0;
|
||||
PaQaRecording recording;
|
||||
PaQaTestTone testTone;
|
||||
PaQaAnalysisResult analysisResult = { 0.0 };
|
||||
int maxFrames = ((int)sampleRate) * 2;
|
||||
|
||||
testTone.samplesPerFrame = 1;
|
||||
testTone.sampleRate = sampleRate;
|
||||
testTone.frequency = sampleRate / cycleSize;
|
||||
testTone.amplitude = 0.5;
|
||||
testTone.startDelay = latencyFrames;
|
||||
|
||||
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
MakeRecordingWithPop( &recording, &testTone, popPosition, popWidth, popAmplitude );
|
||||
|
||||
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult );
|
||||
|
||||
#if PRINT_REPORTS
|
||||
printf("\n=== Pop Analysis ===================\n");
|
||||
printf(" expected actual\n");
|
||||
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency );
|
||||
printf(" popPosition: %10.3f %10.3f\n", (double)popPosition, analysisResult.popPosition );
|
||||
printf(" popAmplitude: %10.3f %10.3f\n", popAmplitude, analysisResult.popAmplitude );
|
||||
printf(" cycleSize: %6d\n", cycleSize );
|
||||
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
|
||||
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
|
||||
printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
|
||||
printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
|
||||
#endif
|
||||
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", latencyFrames, analysisResult.latency, 0.5 );
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording popPosition", popPosition, analysisResult.popPosition, 10 );
|
||||
if( popWidth > 0 )
|
||||
{
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording popAmplitude", popAmplitude, analysisResult.popAmplitude, 0.1 * popAmplitude );
|
||||
}
|
||||
|
||||
PaQa_TerminateRecording( &recording );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_SaveRecordingToWaveFile( &recording, "bad_recording.wav" );
|
||||
PaQa_TerminateRecording( &recording);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Analyse recording with a DC offset.
|
||||
*/
|
||||
static int TestSingleInitialSpike( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
|
||||
{
|
||||
int i;
|
||||
int result = 0;
|
||||
// Account for highpass filter offset.
|
||||
int expectedLatency = latencyFrames + 1;
|
||||
PaQaRecording recording;
|
||||
|
||||
PaQaRecording hipassOutput = { 0 };
|
||||
BiquadFilter hipassFilter;
|
||||
|
||||
PaQaTestTone testTone;
|
||||
PaQaAnalysisResult analysisResult = { 0.0 };
|
||||
int maxFrames = ((int)sampleRate) * 2;
|
||||
|
||||
testTone.samplesPerFrame = 1;
|
||||
testTone.sampleRate = sampleRate;
|
||||
testTone.frequency = sampleRate / cycleSize;
|
||||
testTone.amplitude = -0.5;
|
||||
testTone.startDelay = latencyFrames;
|
||||
|
||||
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
result = PaQa_InitializeRecording( &hipassOutput, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
MakeCleanRecording( &recording, &testTone );
|
||||
|
||||
// Apply DC step.
|
||||
for( i=stepPosition; i<recording.numFrames; i++ )
|
||||
{
|
||||
recording.buffer[i] += stepAmplitude;
|
||||
}
|
||||
|
||||
// Use high pass as a DC blocker!
|
||||
BiquadFilter_SetupHighPass( &hipassFilter, 10.0 / sampleRate, 0.5 );
|
||||
PaQa_FilterRecording( &recording, &hipassOutput, &hipassFilter );
|
||||
|
||||
testTone.amplitude = 0.5;
|
||||
PaQa_AnalyseRecording( &hipassOutput, &testTone, &analysisResult );
|
||||
|
||||
#if PRINT_REPORTS
|
||||
printf("\n=== InitialSpike Analysis ===================\n");
|
||||
printf(" expected actual\n");
|
||||
printf(" latency: %10.3f %10.3f\n", (double)expectedLatency, analysisResult.latency );
|
||||
printf(" popPosition: %10.3f\n", analysisResult.popPosition );
|
||||
printf(" popAmplitude: %10.3f\n", analysisResult.popAmplitude );
|
||||
printf(" amplitudeRatio: %10.3f\n", analysisResult.amplitudeRatio );
|
||||
printf(" cycleSize: %6d\n", cycleSize );
|
||||
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
|
||||
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
|
||||
printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
|
||||
printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
|
||||
#endif
|
||||
|
||||
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", expectedLatency, analysisResult.latency, 4.0 );
|
||||
QA_ASSERT_EQUALS( "PaQa_AnalyseRecording no pop from step", -1, (int) analysisResult.popPosition );
|
||||
PaQa_TerminateRecording( &recording );
|
||||
PaQa_TerminateRecording( &hipassOutput );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_SaveRecordingToWaveFile( &recording, "bad_step_original.wav" );
|
||||
PaQa_SaveRecordingToWaveFile( &hipassOutput, "bad_step_hipass.wav" );
|
||||
PaQa_TerminateRecording( &recording);
|
||||
PaQa_TerminateRecording( &hipassOutput );
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Test various dropped sample scenarios.
|
||||
*/
|
||||
static int TestDetectPops( void )
|
||||
{
|
||||
int result;
|
||||
|
||||
// No pop.
|
||||
result = TestDetectSinglePop( 44100, 200, 477, -1, 0, 0.0 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// short pop
|
||||
result = TestDetectSinglePop( 44100, 300, 810, 3987, 1, 0.5 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// medium long pop
|
||||
result = TestDetectSinglePop( 44100, 300, 810, 9876, 5, 0.5 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// short tiny pop
|
||||
result = TestDetectSinglePop( 44100, 250, 810, 5672, 1, 0.05 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Test analysis when there is a DC offset step before the sine signal.
|
||||
*/
|
||||
static int TestInitialSpike( void )
|
||||
{
|
||||
int result;
|
||||
|
||||
//( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
|
||||
// No spike.
|
||||
result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.0 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// Small spike.
|
||||
result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.1 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// short pop like Ross's error.
|
||||
result = TestSingleInitialSpike( 8000, 32, 42, 2000, 0.1 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// Medium spike.
|
||||
result = TestSingleInitialSpike( 44100, 40, 190, 3000, 0.5 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
// Spike near sine.
|
||||
//result = TestSingleInitialSpike( 44100, 2900, 140, 3000, 0.1 );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if TEST_SAVED_WAVE
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Simple test that writes a sawtooth waveform to a file.
|
||||
*/
|
||||
static int TestSavedWave()
|
||||
{
|
||||
int i,j;
|
||||
WAV_Writer writer;
|
||||
int result = 0;
|
||||
#define NUM_SAMPLES (200)
|
||||
short data[NUM_SAMPLES];
|
||||
short saw = 0;
|
||||
|
||||
|
||||
result = Audio_WAV_OpenWriter( &writer, "test_sawtooth.wav", 44100, 1 );
|
||||
if( result < 0 ) goto error;
|
||||
|
||||
for( i=0; i<15; i++ )
|
||||
{
|
||||
for( j=0; j<NUM_SAMPLES; j++ )
|
||||
{
|
||||
data[j] = saw;
|
||||
saw += 293;
|
||||
}
|
||||
result = Audio_WAV_WriteShorts( &writer, data, NUM_SAMPLES );
|
||||
if( result < 0 ) goto error;
|
||||
}
|
||||
|
||||
result = Audio_WAV_CloseWriter( &writer );
|
||||
if( result < 0 ) goto error;
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
printf("ERROR: result = %d\n", result );
|
||||
return result;
|
||||
}
|
||||
#endif /* TEST_SAVED_WAVE */
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Easy way to generate a sine tone recording.
|
||||
*/
|
||||
void PaQa_FillWithSine( PaQaRecording *recording, double sampleRate, double freq, double amp )
|
||||
{
|
||||
PaQaSineGenerator generator;
|
||||
float buffer[FRAMES_PER_BLOCK];
|
||||
int samplesPerFrame = 1;
|
||||
int stride = 1;
|
||||
int done = 0;
|
||||
|
||||
// Setup a sine oscillator.
|
||||
PaQa_SetupSineGenerator( &generator, freq, amp, sampleRate );
|
||||
|
||||
done = 0;
|
||||
while (!done)
|
||||
{
|
||||
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame );
|
||||
PaQa_MixSine( &generator, buffer, FRAMES_PER_BLOCK, stride );
|
||||
done = PaQa_WriteRecording( recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
* Generate a tone then knock it out using a filter.
|
||||
* Also check using filter slightly off tune to see if some energy gets through.
|
||||
*/
|
||||
static int TestNotchFilter( void )
|
||||
{
|
||||
int result = 0;
|
||||
PaQaRecording original = { 0 };
|
||||
PaQaRecording filtered = { 0 };
|
||||
BiquadFilter notchFilter;
|
||||
double sampleRate = 44100.0;
|
||||
int maxFrames = ((int)sampleRate) * 1;
|
||||
|
||||
double freq = 234.5;
|
||||
double amp = 0.5;
|
||||
|
||||
double mag1, mag2, mag3;
|
||||
|
||||
result = PaQa_InitializeRecording( &original, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
PaQa_FillWithSine( &original, sampleRate, freq, amp );
|
||||
|
||||
//result = PaQa_SaveRecordingToWaveFile( &original, "original.wav" );
|
||||
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
||||
|
||||
mag1 = PaQa_CorrelateSine( &original, freq, sampleRate, 0, original.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "exact frequency match", amp, mag1, 0.01 );
|
||||
|
||||
// Filter with exact frequency.
|
||||
result = PaQa_InitializeRecording( &filtered, maxFrames, (int) sampleRate );
|
||||
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
|
||||
|
||||
BiquadFilter_SetupNotch( ¬chFilter, freq / sampleRate, 0.5 );
|
||||
PaQa_FilterRecording( &original, &filtered, ¬chFilter );
|
||||
result = PaQa_SaveRecordingToWaveFile( &filtered, "filtered1.wav" );
|
||||
QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
||||
|
||||
mag2 = PaQa_CorrelateSine( &filtered, freq, sampleRate, 0, filtered.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "should eliminate tone", 0.0, mag2, 0.01 );
|
||||
|
||||
// Filter with mismatched frequency.
|
||||
BiquadFilter_SetupNotch( ¬chFilter, 1.07 * freq / sampleRate, 2.0 );
|
||||
PaQa_FilterRecording( &original, &filtered, ¬chFilter );
|
||||
|
||||
//result = PaQa_SaveRecordingToWaveFile( &filtered, "badfiltered.wav" );
|
||||
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
|
||||
|
||||
mag3 = PaQa_CorrelateSine( &filtered, freq, sampleRate, 0, filtered.numFrames, NULL );
|
||||
QA_ASSERT_CLOSE( "should eliminate tone", amp*0.26, mag3, 0.01 );
|
||||
|
||||
|
||||
PaQa_TerminateRecording( &original );
|
||||
PaQa_TerminateRecording( &filtered );
|
||||
return 0;
|
||||
|
||||
error:
|
||||
PaQa_TerminateRecording( &original);
|
||||
PaQa_TerminateRecording( &filtered );
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/*==========================================================================================*/
|
||||
/**
|
||||
*/
|
||||
int PaQa_TestAnalyzer( void )
|
||||
{
|
||||
int result;
|
||||
|
||||
#if TEST_SAVED_WAVE
|
||||
// Write a simple wave file.
|
||||
if ((result = TestSavedWave()) != 0) return result;
|
||||
#endif /* TEST_SAVED_WAVE */
|
||||
|
||||
// Generate single tone and verify presence.
|
||||
if ((result = TestSingleMonoTone()) != 0) return result;
|
||||
|
||||
// Generate prime series of tones and verify presence.
|
||||
if ((result = TestMixedMonoTones()) != 0) return result;
|
||||
|
||||
// Detect dropped or added samples in a sine wave recording.
|
||||
if ((result = TestDetectPhaseErrors()) != 0) return result;
|
||||
|
||||
// Test to see if notch filter can knock out the test tone.
|
||||
if ((result = TestNotchFilter()) != 0) return result;
|
||||
|
||||
// Detect pops that get back in phase.
|
||||
if ((result = TestDetectPops()) != 0) return result;
|
||||
|
||||
// Test to see if the latency detector can be tricked like it was on Ross' Windows machine.
|
||||
if ((result = TestInitialSpike()) != 0) return result;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
46
lib-src/portaudio-v19/qa/loopback/src/test_audio_analyzer.h
Normal file
46
lib-src/portaudio-v19/qa/loopback/src/test_audio_analyzer.h
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#ifndef _TEST_AUDIO_ANALYZER_H
|
||||
#define _TEST_AUDIO_ANALYZER_H
|
||||
|
||||
/** Test the audio analyzer by itself without any PortAudio calls. */
|
||||
int PaQa_TestAnalyzer( void );
|
||||
|
||||
|
||||
#endif /* _TEST_AUDIO_ANALYZER_H */
|
242
lib-src/portaudio-v19/qa/loopback/src/write_wav.c
Executable file
242
lib-src/portaudio-v19/qa/loopback/src/write_wav.c
Executable file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Very simple WAV file writer for saving captured audio.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "write_wav.h"
|
||||
|
||||
|
||||
/* Write long word data to a little endian format byte array. */
|
||||
static void WriteLongLE( unsigned char **addrPtr, unsigned long data )
|
||||
{
|
||||
unsigned char *addr = *addrPtr;
|
||||
*addr++ = (unsigned char) data;
|
||||
*addr++ = (unsigned char) (data>>8);
|
||||
*addr++ = (unsigned char) (data>>16);
|
||||
*addr++ = (unsigned char) (data>>24);
|
||||
*addrPtr = addr;
|
||||
}
|
||||
|
||||
/* Write short word data to a little endian format byte array. */
|
||||
static void WriteShortLE( unsigned char **addrPtr, unsigned short data )
|
||||
{
|
||||
unsigned char *addr = *addrPtr;
|
||||
*addr++ = (unsigned char) data;
|
||||
*addr++ = (unsigned char) (data>>8);
|
||||
*addrPtr = addr;
|
||||
}
|
||||
|
||||
/* Write IFF ChunkType data to a byte array. */
|
||||
static void WriteChunkType( unsigned char **addrPtr, unsigned long cktyp )
|
||||
{
|
||||
unsigned char *addr = *addrPtr;
|
||||
*addr++ = (unsigned char) (cktyp>>24);
|
||||
*addr++ = (unsigned char) (cktyp>>16);
|
||||
*addr++ = (unsigned char) (cktyp>>8);
|
||||
*addr++ = (unsigned char) cktyp;
|
||||
*addrPtr = addr;
|
||||
}
|
||||
|
||||
#define WAV_HEADER_SIZE (4 + 4 + 4 + /* RIFF+size+WAVE */ \
|
||||
4 + 4 + 16 + /* fmt chunk */ \
|
||||
4 + 4 ) /* data chunk */
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
* Open named file and write WAV header to the file.
|
||||
* The header includes the DATA chunk type and size.
|
||||
* Returns number of bytes written to file or negative error code.
|
||||
*/
|
||||
long Audio_WAV_OpenWriter( WAV_Writer *writer, const char *fileName, int frameRate, int samplesPerFrame )
|
||||
{
|
||||
unsigned int bytesPerSecond;
|
||||
unsigned char header[ WAV_HEADER_SIZE ];
|
||||
unsigned char *addr = header;
|
||||
int numWritten;
|
||||
|
||||
writer->dataSize = 0;
|
||||
writer->dataSizeOffset = 0;
|
||||
|
||||
writer->fid = fopen( fileName, "wb" );
|
||||
if( writer->fid == NULL )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Write RIFF header. */
|
||||
WriteChunkType( &addr, RIFF_ID );
|
||||
|
||||
/* Write RIFF size as zero for now. Will patch later. */
|
||||
WriteLongLE( &addr, 0 );
|
||||
|
||||
/* Write WAVE form ID. */
|
||||
WriteChunkType( &addr, WAVE_ID );
|
||||
|
||||
/* Write format chunk based on AudioSample structure. */
|
||||
WriteChunkType( &addr, FMT_ID );
|
||||
WriteLongLE( &addr, 16 );
|
||||
WriteShortLE( &addr, WAVE_FORMAT_PCM );
|
||||
bytesPerSecond = frameRate * samplesPerFrame * sizeof( short);
|
||||
WriteShortLE( &addr, (short) samplesPerFrame );
|
||||
WriteLongLE( &addr, frameRate );
|
||||
WriteLongLE( &addr, bytesPerSecond );
|
||||
WriteShortLE( &addr, (short) (samplesPerFrame * sizeof( short)) ); /* bytesPerBlock */
|
||||
WriteShortLE( &addr, (short) 16 ); /* bits per sample */
|
||||
|
||||
/* Write ID and size for 'data' chunk. */
|
||||
WriteChunkType( &addr, DATA_ID );
|
||||
/* Save offset so we can patch it later. */
|
||||
writer->dataSizeOffset = (int) (addr - header);
|
||||
WriteLongLE( &addr, 0 );
|
||||
|
||||
numWritten = fwrite( header, 1, sizeof(header), writer->fid );
|
||||
if( numWritten != sizeof(header) ) return -1;
|
||||
|
||||
return (int) numWritten;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Write to the data chunk portion of a WAV file.
|
||||
* Returns bytes written or negative error code.
|
||||
*/
|
||||
long Audio_WAV_WriteShorts( WAV_Writer *writer,
|
||||
short *samples,
|
||||
int numSamples
|
||||
)
|
||||
{
|
||||
unsigned char buffer[2];
|
||||
unsigned char *bufferPtr;
|
||||
int i;
|
||||
short *p = samples;
|
||||
int numWritten;
|
||||
int bytesWritten;
|
||||
if( numSamples <= 0 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for( i=0; i<numSamples; i++ )
|
||||
{
|
||||
bufferPtr = buffer;
|
||||
WriteShortLE( &bufferPtr, *p++ );
|
||||
numWritten = fwrite( buffer, 1, sizeof( buffer), writer->fid );
|
||||
if( numWritten != sizeof(buffer) ) return -1;
|
||||
}
|
||||
bytesWritten = numSamples * sizeof(short);
|
||||
writer->dataSize += bytesWritten;
|
||||
return (int) bytesWritten;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Close WAV file.
|
||||
* Update chunk sizes so it can be read by audio applications.
|
||||
*/
|
||||
long Audio_WAV_CloseWriter( WAV_Writer *writer )
|
||||
{
|
||||
unsigned char buffer[4];
|
||||
unsigned char *bufferPtr;
|
||||
int numWritten;
|
||||
int riffSize;
|
||||
|
||||
/* Go back to beginning of file and update DATA size */
|
||||
int result = fseek( writer->fid, writer->dataSizeOffset, SEEK_SET );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
bufferPtr = buffer;
|
||||
WriteLongLE( &bufferPtr, writer->dataSize );
|
||||
numWritten = fwrite( buffer, 1, sizeof( buffer), writer->fid );
|
||||
if( numWritten != sizeof(buffer) ) return -1;
|
||||
|
||||
/* Update RIFF size */
|
||||
result = fseek( writer->fid, 4, SEEK_SET );
|
||||
if( result < 0 ) return result;
|
||||
|
||||
riffSize = writer->dataSize + (WAV_HEADER_SIZE - 8);
|
||||
bufferPtr = buffer;
|
||||
WriteLongLE( &bufferPtr, riffSize );
|
||||
numWritten = fwrite( buffer, 1, sizeof( buffer), writer->fid );
|
||||
if( numWritten != sizeof(buffer) ) return -1;
|
||||
|
||||
fclose( writer->fid );
|
||||
writer->fid = NULL;
|
||||
return writer->dataSize;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Simple test that write a sawtooth waveform to a file.
|
||||
*/
|
||||
#if 0
|
||||
int main( void )
|
||||
{
|
||||
int i;
|
||||
WAV_Writer writer;
|
||||
int result;
|
||||
#define NUM_SAMPLES (200)
|
||||
short data[NUM_SAMPLES];
|
||||
short saw = 0;
|
||||
|
||||
for( i=0; i<NUM_SAMPLES; i++ )
|
||||
{
|
||||
data[i] = saw;
|
||||
saw += 293;
|
||||
}
|
||||
|
||||
|
||||
result = Audio_WAV_OpenWriter( &writer, "rendered_midi.wav", 44100, 1 );
|
||||
if( result < 0 ) goto error;
|
||||
|
||||
for( i=0; i<15; i++ )
|
||||
{
|
||||
result = Audio_WAV_WriteShorts( &writer, data, NUM_SAMPLES );
|
||||
if( result < 0 ) goto error;
|
||||
}
|
||||
|
||||
result = Audio_WAV_CloseWriter( &writer );
|
||||
if( result < 0 ) goto error;
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
printf("ERROR: result = %d\n", result );
|
||||
return result;
|
||||
}
|
||||
#endif
|
103
lib-src/portaudio-v19/qa/loopback/src/write_wav.h
Executable file
103
lib-src/portaudio-v19/qa/loopback/src/write_wav.h
Executable file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Latest Version at: http://www.portaudio.com
|
||||
*
|
||||
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
#ifndef _WAV_WRITER_H
|
||||
#define _WAV_WRITER_H
|
||||
|
||||
/*
|
||||
* WAV file writer.
|
||||
*
|
||||
* Author: Phil Burk
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Define WAV Chunk and FORM types as 4 byte integers. */
|
||||
#define RIFF_ID (('R'<<24) | ('I'<<16) | ('F'<<8) | 'F')
|
||||
#define WAVE_ID (('W'<<24) | ('A'<<16) | ('V'<<8) | 'E')
|
||||
#define FMT_ID (('f'<<24) | ('m'<<16) | ('t'<<8) | ' ')
|
||||
#define DATA_ID (('d'<<24) | ('a'<<16) | ('t'<<8) | 'a')
|
||||
#define FACT_ID (('f'<<24) | ('a'<<16) | ('c'<<8) | 't')
|
||||
|
||||
/* Errors returned by Audio_ParseSampleImage_WAV */
|
||||
#define WAV_ERR_CHUNK_SIZE (-1) /* Chunk size is illegal or past file size. */
|
||||
#define WAV_ERR_FILE_TYPE (-2) /* Not a WAV file. */
|
||||
#define WAV_ERR_ILLEGAL_VALUE (-3) /* Illegal or unsupported value. Eg. 927 bits/sample */
|
||||
#define WAV_ERR_FORMAT_TYPE (-4) /* Unsupported format, eg. compressed. */
|
||||
#define WAV_ERR_TRUNCATED (-5) /* End of file missing. */
|
||||
|
||||
/* WAV PCM data format ID */
|
||||
#define WAVE_FORMAT_PCM (1)
|
||||
#define WAVE_FORMAT_IMA_ADPCM (0x0011)
|
||||
|
||||
|
||||
typedef struct WAV_Writer_s
|
||||
{
|
||||
FILE *fid;
|
||||
/* Offset in file for data size. */
|
||||
int dataSizeOffset;
|
||||
int dataSize;
|
||||
} WAV_Writer;
|
||||
|
||||
/*********************************************************************************
|
||||
* Open named file and write WAV header to the file.
|
||||
* The header includes the DATA chunk type and size.
|
||||
* Returns number of bytes written to file or negative error code.
|
||||
*/
|
||||
long Audio_WAV_OpenWriter( WAV_Writer *writer, const char *fileName, int frameRate, int samplesPerFrame );
|
||||
|
||||
/*********************************************************************************
|
||||
* Write to the data chunk portion of a WAV file.
|
||||
* Returns bytes written or negative error code.
|
||||
*/
|
||||
long Audio_WAV_WriteShorts( WAV_Writer *writer,
|
||||
short *samples,
|
||||
int numSamples
|
||||
);
|
||||
|
||||
/*********************************************************************************
|
||||
* Close WAV file.
|
||||
* Update chunk sizes so it can be read by audio applications.
|
||||
*/
|
||||
long Audio_WAV_CloseWriter( WAV_Writer *writer );
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* _WAV_WRITER_H */
|
368
lib-src/portaudio-v19/qa/paqa_devs.c
Normal file
368
lib-src/portaudio-v19/qa/paqa_devs.c
Normal file
@@ -0,0 +1,368 @@
|
||||
/** @file paqa_devs.c
|
||||
@ingroup qa_src
|
||||
@brief Self Testing Quality Assurance app for PortAudio
|
||||
Try to open each device and run through all the
|
||||
possible configurations. This test does not verify
|
||||
that the configuration works well. It just verifies
|
||||
that it does not crash. It requires a human to listen to
|
||||
the outputs.
|
||||
|
||||
@author Phil Burk http://www.softsynth.com
|
||||
|
||||
Pieter adapted to V19 API. Test now relies heavily on
|
||||
Pa_IsFormatSupported(). Uses same 'standard' sample rates
|
||||
as in test pa_devs.c.
|
||||
*/
|
||||
/*
|
||||
* $Id: paqa_devs.c 1756 2011-09-08 06:09:29Z philburk $
|
||||
*
|
||||
* This program uses the PortAudio Portable Audio Library.
|
||||
* For more information see: http://www.portaudio.com
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "portaudio.h"
|
||||
#include "pa_trace.h"
|
||||
|
||||
/****************************************** Definitions ***********/
|
||||
#define MODE_INPUT (0)
|
||||
#define MODE_OUTPUT (1)
|
||||
|
||||
typedef struct PaQaData
|
||||
{
|
||||
unsigned long framesLeft;
|
||||
int numChannels;
|
||||
int bytesPerSample;
|
||||
int mode;
|
||||
short sawPhase;
|
||||
PaSampleFormat format;
|
||||
}
|
||||
PaQaData;
|
||||
|
||||
/****************************************** Prototypes ***********/
|
||||
static void TestDevices( int mode );
|
||||
static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate,
|
||||
int numChannels );
|
||||
static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate,
|
||||
int numChannels, PaSampleFormat format );
|
||||
static int QaCallback( const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData );
|
||||
|
||||
/****************************************** Globals ***********/
|
||||
static int gNumPassed = 0;
|
||||
static int gNumFailed = 0;
|
||||
|
||||
/****************************************** Macros ***********/
|
||||
/* Print ERROR if it fails. Tally success or failure. */
|
||||
/* Odd do-while wrapper seems to be needed for some compilers. */
|
||||
#define EXPECT(_exp) \
|
||||
do \
|
||||
{ \
|
||||
if ((_exp)) {\
|
||||
/* printf("SUCCESS for %s\n", #_exp ); */ \
|
||||
gNumPassed++; \
|
||||
} \
|
||||
else { \
|
||||
printf("ERROR - 0x%x - %s for %s\n", result, \
|
||||
((result == 0) ? "-" : Pa_GetErrorText(result)), \
|
||||
#_exp ); \
|
||||
gNumFailed++; \
|
||||
goto error; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/*******************************************************************/
|
||||
/* This routine will be called by the PortAudio engine when audio is needed.
|
||||
** It may be called at interrupt level on some machines so don't do anything
|
||||
** that could mess up the system like calling malloc() or free().
|
||||
*/
|
||||
static int QaCallback( const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData )
|
||||
{
|
||||
unsigned long i;
|
||||
short phase;
|
||||
PaQaData *data = (PaQaData *) userData;
|
||||
(void) inputBuffer;
|
||||
|
||||
/* Play simple sawtooth wave. */
|
||||
if( data->mode == MODE_OUTPUT )
|
||||
{
|
||||
phase = data->sawPhase;
|
||||
switch( data->format )
|
||||
{
|
||||
case paFloat32:
|
||||
{
|
||||
float *out = (float *) outputBuffer;
|
||||
for( i=0; i<framesPerBuffer; i++ )
|
||||
{
|
||||
phase += 0x123;
|
||||
*out++ = (float) (phase * (1.0 / 32768.0));
|
||||
if( data->numChannels == 2 )
|
||||
{
|
||||
*out++ = (float) (phase * (1.0 / 32768.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt32:
|
||||
{
|
||||
int *out = (int *) outputBuffer;
|
||||
for( i=0; i<framesPerBuffer; i++ )
|
||||
{
|
||||
phase += 0x123;
|
||||
*out++ = ((int) phase ) << 16;
|
||||
if( data->numChannels == 2 )
|
||||
{
|
||||
*out++ = ((int) phase ) << 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case paInt16:
|
||||
{
|
||||
short *out = (short *) outputBuffer;
|
||||
for( i=0; i<framesPerBuffer; i++ )
|
||||
{
|
||||
phase += 0x123;
|
||||
*out++ = phase;
|
||||
if( data->numChannels == 2 )
|
||||
{
|
||||
*out++ = phase;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
unsigned char *out = (unsigned char *) outputBuffer;
|
||||
unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample;
|
||||
for( i=0; i<numBytes; i++ )
|
||||
{
|
||||
*out++ = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
data->sawPhase = phase;
|
||||
}
|
||||
/* Are we through yet? */
|
||||
if( data->framesLeft > framesPerBuffer )
|
||||
{
|
||||
PaUtil_AddTraceMessage("QaCallback: running. framesLeft", data->framesLeft );
|
||||
data->framesLeft -= framesPerBuffer;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
PaUtil_AddTraceMessage("QaCallback: DONE! framesLeft", data->framesLeft );
|
||||
data->framesLeft = 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
int main(void);
|
||||
int main(void)
|
||||
{
|
||||
PaError result;
|
||||
EXPECT( ((result=Pa_Initialize()) == 0) );
|
||||
printf("Test OUTPUT ---------------\n");
|
||||
TestDevices( MODE_OUTPUT );
|
||||
printf("Test INPUT ---------------\n");
|
||||
TestDevices( MODE_INPUT );
|
||||
error:
|
||||
Pa_Terminate();
|
||||
printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed );
|
||||
return (gNumFailed > 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* Try each output device, through its full range of capabilities. */
|
||||
static void TestDevices( int mode )
|
||||
{
|
||||
int id, jc, i;
|
||||
int maxChannels;
|
||||
const PaDeviceInfo *pdi;
|
||||
static double standardSampleRates[] = { 8000.0, 9600.0, 11025.0, 12000.0,
|
||||
16000.0, 22050.0, 24000.0,
|
||||
32000.0, 44100.0, 48000.0,
|
||||
88200.0, 96000.0,
|
||||
-1.0 }; /* Negative terminated list. */
|
||||
int numDevices = Pa_GetDeviceCount();
|
||||
for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */
|
||||
{
|
||||
pdi = Pa_GetDeviceInfo( id );
|
||||
/* Try 1 to maxChannels on each device. */
|
||||
maxChannels = (( mode == MODE_INPUT ) ? pdi->maxInputChannels : pdi->maxOutputChannels);
|
||||
|
||||
for( jc=1; jc<=maxChannels; jc++ )
|
||||
{
|
||||
printf("\n========================================================================\n");
|
||||
printf(" Device = %s\n", pdi->name );
|
||||
printf("========================================================================\n");
|
||||
/* Try each standard sample rate. */
|
||||
for( i=0; standardSampleRates[i] > 0; i++ )
|
||||
{
|
||||
TestFormats( mode, (PaDeviceIndex)id, standardSampleRates[i], jc );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate,
|
||||
int numChannels )
|
||||
{
|
||||
TestAdvance( mode, deviceID, sampleRate, numChannels, paFloat32 );
|
||||
TestAdvance( mode, deviceID, sampleRate, numChannels, paInt16 );
|
||||
TestAdvance( mode, deviceID, sampleRate, numChannels, paInt32 );
|
||||
/* TestAdvance( mode, deviceID, sampleRate, numChannels, paInt24 ); */
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate,
|
||||
int numChannels, PaSampleFormat format )
|
||||
{
|
||||
PaStreamParameters inputParameters, outputParameters, *ipp, *opp;
|
||||
PaStream *stream = NULL;
|
||||
PaError result = paNoError;
|
||||
PaQaData myData;
|
||||
#define FRAMES_PER_BUFFER (64)
|
||||
const int kNumSeconds = 100;
|
||||
|
||||
/* Setup data for synthesis thread. */
|
||||
myData.framesLeft = (unsigned long) (sampleRate * kNumSeconds);
|
||||
myData.numChannels = numChannels;
|
||||
myData.mode = mode;
|
||||
myData.format = format;
|
||||
switch( format )
|
||||
{
|
||||
case paFloat32:
|
||||
case paInt32:
|
||||
case paInt24:
|
||||
myData.bytesPerSample = 4;
|
||||
break;
|
||||
/* case paPackedInt24:
|
||||
myData.bytesPerSample = 3;
|
||||
break; */
|
||||
default:
|
||||
myData.bytesPerSample = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if( mode == MODE_INPUT )
|
||||
{
|
||||
inputParameters.device = deviceID;
|
||||
inputParameters.channelCount = numChannels;
|
||||
inputParameters.sampleFormat = format;
|
||||
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
|
||||
inputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
ipp = &inputParameters;
|
||||
}
|
||||
else
|
||||
{
|
||||
ipp = NULL;
|
||||
}
|
||||
|
||||
if( mode == MODE_OUTPUT )
|
||||
{
|
||||
outputParameters.device = deviceID;
|
||||
outputParameters.channelCount = numChannels;
|
||||
outputParameters.sampleFormat = format;
|
||||
outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
|
||||
outputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
opp = &outputParameters;
|
||||
}
|
||||
else
|
||||
{
|
||||
opp = NULL;
|
||||
}
|
||||
|
||||
if(paFormatIsSupported == Pa_IsFormatSupported( ipp, opp, sampleRate ))
|
||||
{
|
||||
printf("------ TestAdvance: %s, device = %d, rate = %g, numChannels = %d, format = %lu -------\n",
|
||||
( mode == MODE_INPUT ) ? "INPUT" : "OUTPUT",
|
||||
deviceID, sampleRate, numChannels, (unsigned long)format);
|
||||
EXPECT( ((result = Pa_OpenStream( &stream,
|
||||
ipp,
|
||||
opp,
|
||||
sampleRate,
|
||||
FRAMES_PER_BUFFER,
|
||||
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
||||
QaCallback,
|
||||
&myData ) ) == 0) );
|
||||
if( stream )
|
||||
{
|
||||
PaTime oldStamp, newStamp;
|
||||
unsigned long oldFrames;
|
||||
int minDelay = ( mode == MODE_INPUT ) ? 1000 : 400;
|
||||
/* Was:
|
||||
int minNumBuffers = Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate );
|
||||
int msec = (int) ((minNumBuffers * 3 * 1000.0 * FRAMES_PER_BUFFER) / sampleRate);
|
||||
*/
|
||||
int msec = (int)( 3.0 *
|
||||
(( mode == MODE_INPUT ) ? inputParameters.suggestedLatency : outputParameters.suggestedLatency ));
|
||||
if( msec < minDelay ) msec = minDelay;
|
||||
printf("msec = %d\n", msec); /**/
|
||||
EXPECT( ((result=Pa_StartStream( stream )) == 0) );
|
||||
/* Check to make sure PortAudio is advancing timeStamp. */
|
||||
oldStamp = Pa_GetStreamTime(stream);
|
||||
Pa_Sleep(msec);
|
||||
newStamp = Pa_GetStreamTime(stream);
|
||||
printf("oldStamp = %g, newStamp = %g\n", oldStamp, newStamp ); /**/
|
||||
EXPECT( (oldStamp < newStamp) );
|
||||
/* Check to make sure callback is decrementing framesLeft. */
|
||||
oldFrames = myData.framesLeft;
|
||||
Pa_Sleep(msec);
|
||||
printf("oldFrames = %lu, myData.framesLeft = %lu\n", oldFrames, myData.framesLeft ); /**/
|
||||
EXPECT( (oldFrames > myData.framesLeft) );
|
||||
EXPECT( ((result=Pa_CloseStream( stream )) == 0) );
|
||||
stream = NULL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
error:
|
||||
if( stream != NULL ) Pa_CloseStream( stream );
|
||||
return -1;
|
||||
}
|
403
lib-src/portaudio-v19/qa/paqa_errs.c
Normal file
403
lib-src/portaudio-v19/qa/paqa_errs.c
Normal file
@@ -0,0 +1,403 @@
|
||||
/** @file paqa_errs.c
|
||||
@ingroup qa_src
|
||||
@brief Self Testing Quality Assurance app for PortAudio
|
||||
Do lots of bad things to test error reporting.
|
||||
@author Phil Burk http://www.softsynth.com
|
||||
Pieter Suurmond adapted to V19 API.
|
||||
*/
|
||||
/*
|
||||
* $Id: paqa_errs.c 1756 2011-09-08 06:09:29Z philburk $
|
||||
*
|
||||
* This program uses the PortAudio Portable Audio Library.
|
||||
* For more information see: http://www.portaudio.com
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
/*--------- Definitions ---------*/
|
||||
#define MODE_INPUT (0)
|
||||
#define MODE_OUTPUT (1)
|
||||
#define FRAMES_PER_BUFFER (64)
|
||||
#define SAMPLE_RATE (44100.0)
|
||||
|
||||
typedef struct PaQaData
|
||||
{
|
||||
unsigned long framesLeft;
|
||||
int numChannels;
|
||||
int bytesPerSample;
|
||||
int mode;
|
||||
}
|
||||
PaQaData;
|
||||
|
||||
static int gNumPassed = 0; /* Two globals */
|
||||
static int gNumFailed = 0;
|
||||
|
||||
/*------------------- Macros ------------------------------*/
|
||||
/* Print ERROR if it fails. Tally success or failure. Odd */
|
||||
/* do-while wrapper seems to be needed for some compilers. */
|
||||
|
||||
#define EXPECT(_exp) \
|
||||
do \
|
||||
{ \
|
||||
if ((_exp)) {\
|
||||
gNumPassed++; \
|
||||
} \
|
||||
else { \
|
||||
printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \
|
||||
gNumFailed++; \
|
||||
goto error; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define HOPEFOR(_exp) \
|
||||
do \
|
||||
{ \
|
||||
if ((_exp)) {\
|
||||
gNumPassed++; \
|
||||
} \
|
||||
else { \
|
||||
printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \
|
||||
gNumFailed++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* This routine will be called by the PortAudio engine when audio is needed.
|
||||
It may be called at interrupt level on some machines so don't do anything
|
||||
that could mess up the system like calling malloc() or free().
|
||||
*/
|
||||
static int QaCallback( const void* inputBuffer,
|
||||
void* outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void* userData )
|
||||
{
|
||||
unsigned long i;
|
||||
unsigned char* out = (unsigned char *) outputBuffer;
|
||||
PaQaData* data = (PaQaData *) userData;
|
||||
|
||||
(void)inputBuffer; /* Prevent "unused variable" warnings. */
|
||||
|
||||
/* Zero out buffer so we don't hear terrible noise. */
|
||||
if( data->mode == MODE_OUTPUT )
|
||||
{
|
||||
unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample;
|
||||
for( i=0; i<numBytes; i++ )
|
||||
{
|
||||
*out++ = 0;
|
||||
}
|
||||
}
|
||||
/* Are we through yet? */
|
||||
if( data->framesLeft > framesPerBuffer )
|
||||
{
|
||||
data->framesLeft -= framesPerBuffer;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
data->framesLeft = 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static PaDeviceIndex FindInputOnlyDevice(void)
|
||||
{
|
||||
PaDeviceIndex result = Pa_GetDefaultInputDevice();
|
||||
if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxOutputChannels == 0 )
|
||||
return result;
|
||||
|
||||
for( result = 0; result < Pa_GetDeviceCount(); ++result )
|
||||
{
|
||||
if( Pa_GetDeviceInfo(result)->maxOutputChannels == 0 )
|
||||
return result;
|
||||
}
|
||||
|
||||
return paNoDevice;
|
||||
}
|
||||
|
||||
static PaDeviceIndex FindOutputOnlyDevice(void)
|
||||
{
|
||||
PaDeviceIndex result = Pa_GetDefaultOutputDevice();
|
||||
if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxInputChannels == 0 )
|
||||
return result;
|
||||
|
||||
for( result = 0; result < Pa_GetDeviceCount(); ++result )
|
||||
{
|
||||
if( Pa_GetDeviceInfo(result)->maxInputChannels == 0 )
|
||||
return result;
|
||||
}
|
||||
|
||||
return paNoDevice;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------------------------------*/
|
||||
static int TestBadOpens( void )
|
||||
{
|
||||
PaStream* stream = NULL;
|
||||
PaError result;
|
||||
PaQaData myData;
|
||||
PaStreamParameters ipp, opp;
|
||||
const PaDeviceInfo* info = NULL;
|
||||
|
||||
|
||||
/* Setup data for synthesis thread. */
|
||||
myData.framesLeft = (unsigned long) (SAMPLE_RATE * 100); /* 100 seconds */
|
||||
myData.numChannels = 1;
|
||||
myData.mode = MODE_OUTPUT;
|
||||
|
||||
/*----------------------------- No devices specified: */
|
||||
ipp.device = opp.device = paNoDevice;
|
||||
ipp.channelCount = opp.channelCount = 0; /* Also no channels. */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
/* Take the low latency of the default device for all subsequent tests. */
|
||||
info = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice());
|
||||
ipp.suggestedLatency = info ? info->defaultLowInputLatency : 0.100;
|
||||
info = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice());
|
||||
opp.suggestedLatency = info ? info->defaultLowOutputLatency : 0.100;
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidDevice));
|
||||
|
||||
/*----------------------------- No devices specified #2: */
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, NULL,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidDevice));
|
||||
|
||||
/*----------------------------- Out of range input device specified: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = Pa_GetDeviceCount(); /* And no output device, and no channels. */
|
||||
opp.channelCount = 0; opp.device = paNoDevice;
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidDevice));
|
||||
|
||||
/*----------------------------- Out of range output device specified: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */
|
||||
opp.channelCount = 0; opp.device = Pa_GetDeviceCount();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidDevice));
|
||||
|
||||
if (Pa_GetDefaultInputDevice() != paNoDevice) {
|
||||
/*----------------------------- Zero input channels: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = Pa_GetDefaultInputDevice();
|
||||
opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no output channels. */
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
|
||||
}
|
||||
|
||||
if (Pa_GetDefaultOutputDevice() != paNoDevice) {
|
||||
/*----------------------------- Zero output channels: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no input channels. */
|
||||
opp.channelCount = 0; opp.device = Pa_GetDefaultOutputDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
|
||||
}
|
||||
/*----------------------------- Nonzero input and output channels but no output device: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 2; ipp.device = Pa_GetDefaultInputDevice(); /* Both stereo. */
|
||||
opp.channelCount = 2; opp.device = paNoDevice;
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidDevice));
|
||||
|
||||
/*----------------------------- Nonzero input and output channels but no input device: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 2; ipp.device = paNoDevice;
|
||||
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidDevice));
|
||||
|
||||
if (Pa_GetDefaultOutputDevice() != paNoDevice) {
|
||||
/*----------------------------- NULL stream pointer: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice; /* Output is more likely than input. */
|
||||
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); /* Only 2 output channels. */
|
||||
HOPEFOR(((result = Pa_OpenStream(NULL, &ipp, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paBadStreamPtr));
|
||||
|
||||
/*----------------------------- Low sample rate: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice;
|
||||
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
1.0, FRAMES_PER_BUFFER, /* 1 cycle per second (1 Hz) is too low. */
|
||||
paClipOff, QaCallback, &myData )) == paInvalidSampleRate));
|
||||
|
||||
/*----------------------------- High sample rate: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice;
|
||||
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
10000000.0, FRAMES_PER_BUFFER, /* 10^6 cycles per second (10 MHz) is too high. */
|
||||
paClipOff, QaCallback, &myData )) == paInvalidSampleRate));
|
||||
|
||||
/*----------------------------- NULL callback: */
|
||||
/* NULL callback is valid in V19 -- it means use blocking read/write stream
|
||||
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice;
|
||||
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff,
|
||||
NULL,
|
||||
&myData )) == paNullCallback));
|
||||
*/
|
||||
|
||||
/*----------------------------- Bad flag: */
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice;
|
||||
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
255, /* Is 8 maybe legal V19 API? */
|
||||
QaCallback, &myData )) == paInvalidFlag));
|
||||
}
|
||||
|
||||
/*----------------------------- using input device as output device: */
|
||||
if( FindInputOnlyDevice() != paNoDevice )
|
||||
{
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */
|
||||
opp.channelCount = 2; opp.device = FindInputOnlyDevice();
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
|
||||
}
|
||||
|
||||
/*----------------------------- using output device as input device: */
|
||||
if( FindOutputOnlyDevice() != paNoDevice )
|
||||
{
|
||||
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
|
||||
ipp.sampleFormat = opp.sampleFormat = paFloat32;
|
||||
ipp.channelCount = 2; ipp.device = FindOutputOnlyDevice();
|
||||
opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no channels. */
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL,
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
|
||||
|
||||
}
|
||||
|
||||
if( stream != NULL ) Pa_CloseStream( stream );
|
||||
return result;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------------------*/
|
||||
static int TestBadActions( void )
|
||||
{
|
||||
PaStream* stream = NULL;
|
||||
const PaDeviceInfo* deviceInfo = NULL;
|
||||
PaError result = 0;
|
||||
PaQaData myData;
|
||||
PaStreamParameters opp;
|
||||
const PaDeviceInfo* info = NULL;
|
||||
|
||||
/* Setup data for synthesis thread. */
|
||||
myData.framesLeft = (unsigned long)(SAMPLE_RATE * 100); /* 100 seconds */
|
||||
myData.numChannels = 1;
|
||||
myData.mode = MODE_OUTPUT;
|
||||
|
||||
opp.device = Pa_GetDefaultOutputDevice(); /* Default output. */
|
||||
opp.channelCount = 2; /* Stereo output. */
|
||||
opp.hostApiSpecificStreamInfo = NULL;
|
||||
opp.sampleFormat = paFloat32;
|
||||
info = Pa_GetDeviceInfo(opp.device);
|
||||
opp.suggestedLatency = info ? info->defaultLowOutputLatency : 0.100;
|
||||
|
||||
if (opp.device != paNoDevice) {
|
||||
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, /* Take NULL as input parame- */
|
||||
&opp, /* ters, meaning try only output. */
|
||||
SAMPLE_RATE, FRAMES_PER_BUFFER,
|
||||
paClipOff, QaCallback, &myData )) == paNoError));
|
||||
}
|
||||
|
||||
HOPEFOR(((deviceInfo = Pa_GetDeviceInfo(paNoDevice)) == NULL));
|
||||
HOPEFOR(((deviceInfo = Pa_GetDeviceInfo(87654)) == NULL));
|
||||
HOPEFOR(((result = Pa_StartStream(NULL)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = Pa_StopStream(NULL)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = Pa_IsStreamStopped(NULL)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = Pa_IsStreamActive(NULL)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = Pa_CloseStream(NULL)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = Pa_SetStreamFinishedCallback(NULL, NULL)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = !Pa_GetStreamInfo(NULL))));
|
||||
HOPEFOR(((result = Pa_GetStreamTime(NULL)) == 0.0));
|
||||
HOPEFOR(((result = Pa_GetStreamCpuLoad(NULL)) == 0.0));
|
||||
HOPEFOR(((result = Pa_ReadStream(NULL, NULL, 0)) == paBadStreamPtr));
|
||||
HOPEFOR(((result = Pa_WriteStream(NULL, NULL, 0)) == paBadStreamPtr));
|
||||
|
||||
/** @todo test Pa_GetStreamReadAvailable and Pa_GetStreamWriteAvailable */
|
||||
|
||||
if (stream != NULL) Pa_CloseStream(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------*/
|
||||
int main(void);
|
||||
int main(void)
|
||||
{
|
||||
PaError result;
|
||||
|
||||
EXPECT(((result = Pa_Initialize()) == paNoError));
|
||||
TestBadOpens();
|
||||
TestBadActions();
|
||||
error:
|
||||
Pa_Terminate();
|
||||
printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed);
|
||||
return 0;
|
||||
}
|
482
lib-src/portaudio-v19/qa/paqa_latency.c
Normal file
482
lib-src/portaudio-v19/qa/paqa_latency.c
Normal file
@@ -0,0 +1,482 @@
|
||||
/** @file paqa_latency.c
|
||||
@ingroup qa_src
|
||||
@brief Test latency estimates.
|
||||
@author Ross Bencina <rossb@audiomulch.com>
|
||||
@author Phil Burk <philburk@softsynth.com>
|
||||
*/
|
||||
/*
|
||||
* $Id: patest_sine.c 1368 2008-03-01 00:38:27Z rossb $
|
||||
*
|
||||
* This program uses the PortAudio Portable Audio Library.
|
||||
* For more information see: http://www.portaudio.com/
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "portaudio.h"
|
||||
#include "loopback/src/qa_tools.h"
|
||||
|
||||
#define NUM_SECONDS (5)
|
||||
#define SAMPLE_RATE (44100)
|
||||
#define FRAMES_PER_BUFFER (64)
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI (3.14159265)
|
||||
#endif
|
||||
|
||||
#define TABLE_SIZE (200)
|
||||
typedef struct
|
||||
{
|
||||
float sine[TABLE_SIZE];
|
||||
int left_phase;
|
||||
int right_phase;
|
||||
char message[20];
|
||||
int minFramesPerBuffer;
|
||||
int maxFramesPerBuffer;
|
||||
int callbackCount;
|
||||
PaTime minDeltaDacTime;
|
||||
PaTime maxDeltaDacTime;
|
||||
PaStreamCallbackTimeInfo previousTimeInfo;
|
||||
}
|
||||
paTestData;
|
||||
|
||||
/* Used to tally the results of the QA tests. */
|
||||
int g_testsPassed = 0;
|
||||
int g_testsFailed = 0;
|
||||
|
||||
/* This routine will be called by the PortAudio engine when audio is needed.
|
||||
** It may called at interrupt level on some machines so don't do anything
|
||||
** that could mess up the system like calling malloc() or free().
|
||||
*/
|
||||
static int patestCallback( const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData )
|
||||
{
|
||||
paTestData *data = (paTestData*)userData;
|
||||
float *out = (float*)outputBuffer;
|
||||
unsigned long i;
|
||||
|
||||
(void) timeInfo; /* Prevent unused variable warnings. */
|
||||
(void) statusFlags;
|
||||
(void) inputBuffer;
|
||||
|
||||
if( data->minFramesPerBuffer > framesPerBuffer )
|
||||
{
|
||||
data->minFramesPerBuffer = framesPerBuffer;
|
||||
}
|
||||
if( data->maxFramesPerBuffer < framesPerBuffer )
|
||||
{
|
||||
data->maxFramesPerBuffer = framesPerBuffer;
|
||||
}
|
||||
|
||||
/* Measure min and max output time stamp delta. */
|
||||
if( data->callbackCount > 0 )
|
||||
{
|
||||
PaTime delta = timeInfo->outputBufferDacTime - data->previousTimeInfo.outputBufferDacTime;
|
||||
if( data->minDeltaDacTime > delta )
|
||||
{
|
||||
data->minDeltaDacTime = delta;
|
||||
}
|
||||
if( data->maxDeltaDacTime < delta )
|
||||
{
|
||||
data->maxDeltaDacTime = delta;
|
||||
}
|
||||
}
|
||||
data->previousTimeInfo = *timeInfo;
|
||||
|
||||
for( i=0; i<framesPerBuffer; i++ )
|
||||
{
|
||||
*out++ = data->sine[data->left_phase]; /* left */
|
||||
*out++ = data->sine[data->right_phase]; /* right */
|
||||
data->left_phase += 1;
|
||||
if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE;
|
||||
data->right_phase += 3; /* higher pitch so we can distinguish left and right. */
|
||||
if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE;
|
||||
}
|
||||
|
||||
data->callbackCount += 1;
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
PaError paqaCheckLatency( PaStreamParameters *outputParamsPtr,
|
||||
paTestData *dataPtr, double sampleRate, unsigned long framesPerBuffer )
|
||||
{
|
||||
PaError err;
|
||||
PaStream *stream;
|
||||
const PaStreamInfo* streamInfo;
|
||||
|
||||
dataPtr->minFramesPerBuffer = 9999999;
|
||||
dataPtr->maxFramesPerBuffer = 0;
|
||||
dataPtr->minDeltaDacTime = 9999999.0;
|
||||
dataPtr->maxDeltaDacTime = 0.0;
|
||||
dataPtr->callbackCount = 0;
|
||||
|
||||
printf("Stream parameter: suggestedOutputLatency = %g\n", outputParamsPtr->suggestedLatency );
|
||||
if( framesPerBuffer == paFramesPerBufferUnspecified ){
|
||||
printf("Stream parameter: user framesPerBuffer = paFramesPerBufferUnspecified\n" );
|
||||
}else{
|
||||
printf("Stream parameter: user framesPerBuffer = %lu\n", framesPerBuffer );
|
||||
}
|
||||
err = Pa_OpenStream(
|
||||
&stream,
|
||||
NULL, /* no input */
|
||||
outputParamsPtr,
|
||||
sampleRate,
|
||||
framesPerBuffer,
|
||||
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
||||
patestCallback,
|
||||
dataPtr );
|
||||
if( err != paNoError ) goto error1;
|
||||
|
||||
streamInfo = Pa_GetStreamInfo( stream );
|
||||
printf("Stream info: inputLatency = %g\n", streamInfo->inputLatency );
|
||||
printf("Stream info: outputLatency = %g\n", streamInfo->outputLatency );
|
||||
|
||||
err = Pa_StartStream( stream );
|
||||
if( err != paNoError ) goto error2;
|
||||
|
||||
printf("Play for %d seconds.\n", NUM_SECONDS );
|
||||
Pa_Sleep( NUM_SECONDS * 1000 );
|
||||
|
||||
printf(" minFramesPerBuffer = %4d\n", dataPtr->minFramesPerBuffer );
|
||||
printf(" maxFramesPerBuffer = %4d\n", dataPtr->maxFramesPerBuffer );
|
||||
printf(" minDeltaDacTime = %f\n", dataPtr->minDeltaDacTime );
|
||||
printf(" maxDeltaDacTime = %f\n", dataPtr->maxDeltaDacTime );
|
||||
|
||||
err = Pa_StopStream( stream );
|
||||
if( err != paNoError ) goto error2;
|
||||
|
||||
err = Pa_CloseStream( stream );
|
||||
Pa_Sleep( 1 * 1000 );
|
||||
|
||||
|
||||
printf("-------------------------------------\n");
|
||||
return err;
|
||||
error2:
|
||||
Pa_CloseStream( stream );
|
||||
error1:
|
||||
printf("-------------------------------------\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************/
|
||||
static int paqaNoopCallback( const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData )
|
||||
{
|
||||
(void)inputBuffer;
|
||||
(void)outputBuffer;
|
||||
(void)framesPerBuffer;
|
||||
(void)timeInfo;
|
||||
(void)statusFlags;
|
||||
(void)userData;
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
static int paqaCheckMultipleSuggested( PaDeviceIndex deviceIndex, int isInput )
|
||||
{
|
||||
int i;
|
||||
int numLoops = 10;
|
||||
PaError err;
|
||||
PaStream *stream;
|
||||
PaStreamParameters streamParameters;
|
||||
const PaStreamInfo* streamInfo;
|
||||
double lowLatency;
|
||||
double highLatency;
|
||||
double finalLatency;
|
||||
double sampleRate = SAMPLE_RATE;
|
||||
const PaDeviceInfo *pdi = Pa_GetDeviceInfo( deviceIndex );
|
||||
double previousLatency = 0.0;
|
||||
int numChannels = 1;
|
||||
float toleranceRatio = 1.0;
|
||||
|
||||
printf("------------------------ paqaCheckMultipleSuggested - %s\n",
|
||||
(isInput ? "INPUT" : "OUTPUT") );
|
||||
if( isInput )
|
||||
{
|
||||
lowLatency = pdi->defaultLowInputLatency;
|
||||
highLatency = pdi->defaultHighInputLatency;
|
||||
numChannels = (pdi->maxInputChannels < 2) ? 1 : 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowLatency = pdi->defaultLowOutputLatency;
|
||||
highLatency = pdi->defaultHighOutputLatency;
|
||||
numChannels = (pdi->maxOutputChannels < 2) ? 1 : 2;
|
||||
}
|
||||
streamParameters.channelCount = numChannels;
|
||||
streamParameters.device = deviceIndex;
|
||||
streamParameters.hostApiSpecificStreamInfo = NULL;
|
||||
streamParameters.sampleFormat = paFloat32;
|
||||
sampleRate = pdi->defaultSampleRate;
|
||||
|
||||
printf(" lowLatency = %g\n", lowLatency );
|
||||
printf(" highLatency = %g\n", highLatency );
|
||||
printf(" numChannels = %d\n", numChannels );
|
||||
printf(" sampleRate = %g\n", sampleRate );
|
||||
|
||||
if( (highLatency - lowLatency) < 0.001 )
|
||||
{
|
||||
numLoops = 1;
|
||||
}
|
||||
|
||||
for( i=0; i<numLoops; i++ )
|
||||
{
|
||||
if( numLoops == 1 )
|
||||
streamParameters.suggestedLatency = lowLatency;
|
||||
else
|
||||
streamParameters.suggestedLatency = lowLatency + ((highLatency - lowLatency) * i /(numLoops - 1));
|
||||
|
||||
printf(" suggestedLatency[%d] = %6.4f\n", i, streamParameters.suggestedLatency );
|
||||
|
||||
err = Pa_OpenStream(
|
||||
&stream,
|
||||
(isInput ? &streamParameters : NULL),
|
||||
(isInput ? NULL : &streamParameters),
|
||||
sampleRate,
|
||||
paFramesPerBufferUnspecified,
|
||||
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
||||
paqaNoopCallback,
|
||||
NULL );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
streamInfo = Pa_GetStreamInfo( stream );
|
||||
|
||||
err = Pa_CloseStream( stream );
|
||||
|
||||
if( isInput )
|
||||
{
|
||||
finalLatency = streamInfo->inputLatency;
|
||||
}
|
||||
else
|
||||
{
|
||||
finalLatency = streamInfo->outputLatency;
|
||||
}
|
||||
printf(" finalLatency = %6.4f\n", finalLatency );
|
||||
/* For the default low & high latency values, expect quite close; for other requested
|
||||
* values, at worst the next power-of-2 may result (eg 513 -> 1024) */
|
||||
toleranceRatio = ( (i == 0) || (i == ( numLoops - 1 )) ) ? 0.1 : 1.0;
|
||||
QA_ASSERT_CLOSE( "final latency should be close to suggested latency",
|
||||
streamParameters.suggestedLatency, finalLatency, (streamParameters.suggestedLatency * toleranceRatio) );
|
||||
if( i == 0 )
|
||||
{
|
||||
previousLatency = finalLatency;
|
||||
}
|
||||
}
|
||||
|
||||
if( numLoops > 1 )
|
||||
{
|
||||
QA_ASSERT_TRUE( " final latency should increase with suggested latency", (finalLatency > previousLatency) );
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
static int paqaVerifySuggestedLatency( void )
|
||||
{
|
||||
PaDeviceIndex id;
|
||||
int result = 0;
|
||||
const PaDeviceInfo *pdi;
|
||||
int numDevices = Pa_GetDeviceCount();
|
||||
|
||||
printf("\n ------------------------ paqaVerifySuggestedLatency\n");
|
||||
for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */
|
||||
{
|
||||
pdi = Pa_GetDeviceInfo( id );
|
||||
printf("\nUsing device #%d: '%s' (%s)\n", id, pdi->name, Pa_GetHostApiInfo(pdi->hostApi)->name);
|
||||
if( pdi->maxOutputChannels > 0 )
|
||||
{
|
||||
if( paqaCheckMultipleSuggested( id, 0 ) < 0 )
|
||||
{
|
||||
printf("OUTPUT CHECK FAILED !!! #%d: '%s'\n", id, pdi->name);
|
||||
result -= 1;
|
||||
}
|
||||
}
|
||||
if( pdi->maxInputChannels > 0 )
|
||||
{
|
||||
if( paqaCheckMultipleSuggested( id, 1 ) < 0 )
|
||||
{
|
||||
printf("INPUT CHECK FAILED !!! #%d: '%s'\n", id, pdi->name);
|
||||
result -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*******************************************************************/
|
||||
static int paqaVerifyDeviceInfoLatency( void )
|
||||
{
|
||||
PaDeviceIndex id;
|
||||
const PaDeviceInfo *pdi;
|
||||
int numDevices = Pa_GetDeviceCount();
|
||||
|
||||
printf("\n ------------------------ paqaVerifyDeviceInfoLatency\n");
|
||||
for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */
|
||||
{
|
||||
pdi = Pa_GetDeviceInfo( id );
|
||||
printf("Using device #%d: '%s' (%s)\n", id, pdi->name, Pa_GetHostApiInfo(pdi->hostApi)->name);
|
||||
if( pdi->maxOutputChannels > 0 )
|
||||
{
|
||||
printf(" Output defaultLowOutputLatency = %f seconds\n", pdi->defaultLowOutputLatency);
|
||||
printf(" Output defaultHighOutputLatency = %f seconds\n", pdi->defaultHighOutputLatency);
|
||||
QA_ASSERT_TRUE( "defaultLowOutputLatency should be > 0", (pdi->defaultLowOutputLatency > 0.0) );
|
||||
QA_ASSERT_TRUE( "defaultHighOutputLatency should be > 0", (pdi->defaultHighOutputLatency > 0.0) );
|
||||
//QA_ASSERT_TRUE( "defaultHighOutputLatency should be > Low", (pdi->defaultHighOutputLatency > pdi->defaultLowOutputLatency) );
|
||||
}
|
||||
if( pdi->maxInputChannels > 0 )
|
||||
{
|
||||
printf(" Input defaultLowOutputLatency = %f seconds\n", pdi->defaultLowInputLatency);
|
||||
printf(" Input defaultHighOutputLatency = %f seconds\n", pdi->defaultHighInputLatency);
|
||||
QA_ASSERT_TRUE( "defaultLowOutputLatency should be > 0", (pdi->defaultLowInputLatency > 0.0) );
|
||||
QA_ASSERT_TRUE( "defaultHighOutputLatency should be > 0", (pdi->defaultHighInputLatency > 0.0) );
|
||||
//QA_ASSERT_TRUE( "defaultHighOutputLatency should be > Low", (pdi->defaultHighInputLatency > pdi->defaultLowInputLatency) );
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************/
|
||||
int main(void);
|
||||
int main(void)
|
||||
{
|
||||
PaStreamParameters outputParameters;
|
||||
PaError err;
|
||||
paTestData data;
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
int i;
|
||||
int framesPerBuffer;
|
||||
double sampleRate = SAMPLE_RATE;
|
||||
|
||||
printf("\nPortAudio QA: investigate output latency.\n");
|
||||
|
||||
/* initialise sinusoidal wavetable */
|
||||
for( i=0; i<TABLE_SIZE; i++ )
|
||||
{
|
||||
data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. );
|
||||
}
|
||||
data.left_phase = data.right_phase = 0;
|
||||
|
||||
err = Pa_Initialize();
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
/* Run self tests. */
|
||||
if( paqaVerifyDeviceInfoLatency() < 0 ) goto error;
|
||||
|
||||
if( paqaVerifySuggestedLatency() < 0 ) goto error;
|
||||
|
||||
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
|
||||
if (outputParameters.device == paNoDevice) {
|
||||
fprintf(stderr,"Error: No default output device.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
printf("\n\nNow running Audio Output Tests...\n");
|
||||
printf("-------------------------------------\n");
|
||||
|
||||
outputParameters.channelCount = 2; /* stereo output */
|
||||
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */
|
||||
deviceInfo = Pa_GetDeviceInfo( outputParameters.device );
|
||||
printf("Using device #%d: '%s' (%s)\n", outputParameters.device, deviceInfo->name, Pa_GetHostApiInfo(deviceInfo->hostApi)->name);
|
||||
printf("Device info: defaultLowOutputLatency = %f seconds\n", deviceInfo->defaultLowOutputLatency);
|
||||
printf("Device info: defaultHighOutputLatency = %f seconds\n", deviceInfo->defaultHighOutputLatency);
|
||||
sampleRate = deviceInfo->defaultSampleRate;
|
||||
printf("Sample Rate for following tests: %g\n", sampleRate);
|
||||
outputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
printf("-------------------------------------\n");
|
||||
|
||||
// Try to use a small buffer that is smaller than we think the device can handle.
|
||||
// Try to force combining multiple user buffers into a host buffer.
|
||||
printf("------------- Try a very small buffer.\n");
|
||||
framesPerBuffer = 9;
|
||||
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, framesPerBuffer );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
printf("------------- 64 frame buffer with 1.1 * defaultLow latency.\n");
|
||||
framesPerBuffer = 64;
|
||||
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency * 1.1;
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, framesPerBuffer );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
// Try to create a huge buffer that is bigger than the allowed device maximum.
|
||||
printf("------------- Try a huge buffer.\n");
|
||||
framesPerBuffer = 16*1024;
|
||||
outputParameters.suggestedLatency = ((double)framesPerBuffer) / sampleRate; // approximate
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, framesPerBuffer );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
printf("------------- Try suggestedLatency = 0.0\n");
|
||||
outputParameters.suggestedLatency = 0.0;
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
printf("------------- Try suggestedLatency = defaultLowOutputLatency\n");
|
||||
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
printf("------------- Try suggestedLatency = defaultHighOutputLatency\n");
|
||||
outputParameters.suggestedLatency = deviceInfo->defaultHighOutputLatency;
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
printf("------------- Try suggestedLatency = defaultHighOutputLatency * 4\n");
|
||||
outputParameters.suggestedLatency = deviceInfo->defaultHighOutputLatency * 4;
|
||||
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
|
||||
if( err != paNoError ) goto error;
|
||||
|
||||
Pa_Terminate();
|
||||
printf("SUCCESS - test finished.\n");
|
||||
return err;
|
||||
|
||||
error:
|
||||
Pa_Terminate();
|
||||
fprintf( stderr, "ERROR - test failed.\n" );
|
||||
fprintf( stderr, "Error number: %d\n", err );
|
||||
fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
|
||||
return err;
|
||||
}
|
Reference in New Issue
Block a user