|
|
|
@ -34,12 +34,123 @@
|
|
|
|
|
|
|
|
|
|
// Tag for machine readable results as property = value pairs
|
|
|
|
|
#define LOOPBACK_RESULT_TAG "RESULT: "
|
|
|
|
|
#define LOOPBACK_SAMPLE_RATE 48000
|
|
|
|
|
|
|
|
|
|
#define MILLIS_PER_SECOND 1000
|
|
|
|
|
constexpr int32_t kDefaultSampleRate = 48000;
|
|
|
|
|
constexpr int32_t kMillisPerSecond = 1000;
|
|
|
|
|
constexpr int32_t kMinLatencyMillis = 4; // arbitrary and very low
|
|
|
|
|
constexpr int32_t kMaxLatencyMillis = 400; // arbitrary and generous
|
|
|
|
|
constexpr double kMaxEchoGain = 10.0; // based on experiments, otherwise too noisy
|
|
|
|
|
constexpr double kMinimumConfidence = 0.5;
|
|
|
|
|
|
|
|
|
|
#define MAX_ZEROTH_PARTIAL_BINS 40
|
|
|
|
|
constexpr double MAX_ECHO_GAIN = 10.0; // based on experiments, otherwise autocorrelation too noisy
|
|
|
|
|
static void printAudioScope(float sample) {
|
|
|
|
|
const int maxStars = 80; // arbitrary, fits on one line
|
|
|
|
|
char c = '*';
|
|
|
|
|
if (sample < -1.0) {
|
|
|
|
|
sample = -1.0;
|
|
|
|
|
c = '$';
|
|
|
|
|
} else if (sample > 1.0) {
|
|
|
|
|
sample = 1.0;
|
|
|
|
|
c = '$';
|
|
|
|
|
}
|
|
|
|
|
int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
|
|
|
|
|
for (int i = 0; i < numSpaces; i++) {
|
|
|
|
|
putchar(' ');
|
|
|
|
|
}
|
|
|
|
|
printf("%c\n", c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
FIR filter designed with
|
|
|
|
|
http://t-filter.appspot.com
|
|
|
|
|
|
|
|
|
|
sampling frequency: 48000 Hz
|
|
|
|
|
|
|
|
|
|
* 0 Hz - 8000 Hz
|
|
|
|
|
gain = 1.2
|
|
|
|
|
desired ripple = 5 dB
|
|
|
|
|
actual ripple = 5.595266169703693 dB
|
|
|
|
|
|
|
|
|
|
* 12000 Hz - 20000 Hz
|
|
|
|
|
gain = 0
|
|
|
|
|
desired attenuation = -40 dB
|
|
|
|
|
actual attenuation = -37.58691566571914 dB
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#define FILTER_TAP_NUM 11
|
|
|
|
|
|
|
|
|
|
static const float sFilterTaps8000[FILTER_TAP_NUM] = {
|
|
|
|
|
-0.05944219353343189f,
|
|
|
|
|
-0.07303434839503208f,
|
|
|
|
|
-0.037690487672689066f,
|
|
|
|
|
0.1870480506596512f,
|
|
|
|
|
0.3910337357836833f,
|
|
|
|
|
0.5333672385425637f,
|
|
|
|
|
0.3910337357836833f,
|
|
|
|
|
0.1870480506596512f,
|
|
|
|
|
-0.037690487672689066f,
|
|
|
|
|
-0.07303434839503208f,
|
|
|
|
|
-0.05944219353343189f
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class LowPassFilter {
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Filter one input sample.
|
|
|
|
|
* @return filtered output
|
|
|
|
|
*/
|
|
|
|
|
float filter(float input) {
|
|
|
|
|
float output = 0.0f;
|
|
|
|
|
mX[mCursor] = input;
|
|
|
|
|
// Index backwards over x.
|
|
|
|
|
int xIndex = mCursor + FILTER_TAP_NUM;
|
|
|
|
|
// Write twice so we avoid having to wrap in the middle of the convolution.
|
|
|
|
|
mX[xIndex] = input;
|
|
|
|
|
for (int i = 0; i < FILTER_TAP_NUM; i++) {
|
|
|
|
|
output += sFilterTaps8000[i] * mX[xIndex--];
|
|
|
|
|
}
|
|
|
|
|
if (++mCursor >= FILTER_TAP_NUM) {
|
|
|
|
|
mCursor = 0;
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if PASSED
|
|
|
|
|
*/
|
|
|
|
|
bool test() {
|
|
|
|
|
// Measure the impulse of the filter at different phases so we exercise
|
|
|
|
|
// all the wraparound cases in the FIR.
|
|
|
|
|
for (int offset = 0; offset < (FILTER_TAP_NUM * 2); offset++ ) {
|
|
|
|
|
// printf("LowPassFilter: cursor = %d\n", mCursor);
|
|
|
|
|
// Offset by one each time.
|
|
|
|
|
if (filter(0.0f) != 0.0f) {
|
|
|
|
|
printf("ERROR: filter should return 0.0 before impulse response\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < FILTER_TAP_NUM; i++) {
|
|
|
|
|
float output = filter((i == 0) ? 1.0f : 0.0f); // impulse
|
|
|
|
|
if (output != sFilterTaps8000[i]) {
|
|
|
|
|
printf("ERROR: filter should return impulse response\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < FILTER_TAP_NUM; i++) {
|
|
|
|
|
if (filter(0.0f) != 0.0f) {
|
|
|
|
|
printf("ERROR: filter should return 0.0 after impulse response\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
float mX[FILTER_TAP_NUM * 2]{}; // twice as big as needed to avoid wrapping
|
|
|
|
|
int32_t mCursor = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// A narrow impulse seems to have better immunity against over estimating the
|
|
|
|
|
// latency due to detecting subharmonics by the auto-correlator.
|
|
|
|
@ -78,6 +189,12 @@ private:
|
|
|
|
|
int64_t mSeed = 99887766;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct LatencyReport_s {
|
|
|
|
|
double latencyInFrames;
|
|
|
|
|
double confidence;
|
|
|
|
|
} LatencyReport;
|
|
|
|
|
|
|
|
|
|
static double calculateCorrelation(const float *a,
|
|
|
|
|
const float *b,
|
|
|
|
|
int windowSize)
|
|
|
|
@ -101,130 +218,75 @@ static double calculateCorrelation(const float *a,
|
|
|
|
|
return correlation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int calculateCorrelations(const float *haystack, int haystackSize,
|
|
|
|
|
const float *needle, int needleSize,
|
|
|
|
|
float *results, int resultSize)
|
|
|
|
|
{
|
|
|
|
|
int maxCorrelations = haystackSize - needleSize;
|
|
|
|
|
int numCorrelations = std::min(maxCorrelations, resultSize);
|
|
|
|
|
|
|
|
|
|
for (int ic = 0; ic < numCorrelations; ic++) {
|
|
|
|
|
double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
|
|
|
|
|
results[ic] = correlation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return numCorrelations;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*==========================================================================================*/
|
|
|
|
|
/**
|
|
|
|
|
* Scan until we get a correlation of a single scan that goes over the tolerance level,
|
|
|
|
|
* peaks then drops back down.
|
|
|
|
|
*/
|
|
|
|
|
static double findFirstMatch(const float *haystack, int haystackSize,
|
|
|
|
|
const float *needle, int needleSize, double threshold )
|
|
|
|
|
{
|
|
|
|
|
int ic;
|
|
|
|
|
// How many correlations can we calculate?
|
|
|
|
|
int numCorrelations = haystackSize - needleSize;
|
|
|
|
|
double maxCorrelation = 0.0;
|
|
|
|
|
int peakIndex = -1;
|
|
|
|
|
double location = -1.0;
|
|
|
|
|
const double backThresholdScaler = 0.5;
|
|
|
|
|
|
|
|
|
|
for (ic = 0; ic < numCorrelations; ic++) {
|
|
|
|
|
double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
|
|
|
|
|
|
|
|
|
|
if( (correlation > maxCorrelation) ) {
|
|
|
|
|
maxCorrelation = correlation;
|
|
|
|
|
peakIndex = ic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//printf("PaQa_FindFirstMatch: ic = %4d, correlation = %8f, maxSum = %8f\n",
|
|
|
|
|
// ic, correlation, maxSum );
|
|
|
|
|
// Are we past what we were looking for?
|
|
|
|
|
if((maxCorrelation > threshold) && (correlation < backThresholdScaler * maxCorrelation)) {
|
|
|
|
|
location = peakIndex;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
static int measureLatencyFromEchos(const float *data,
|
|
|
|
|
int32_t numFloats,
|
|
|
|
|
int32_t sampleRate,
|
|
|
|
|
LatencyReport *report) {
|
|
|
|
|
// Allocate results array
|
|
|
|
|
const int minReasonableLatencyFrames = sampleRate * kMinLatencyMillis / kMillisPerSecond;
|
|
|
|
|
const int maxReasonableLatencyFrames = sampleRate * kMaxLatencyMillis / kMillisPerSecond;
|
|
|
|
|
int32_t maxCorrelationSize = maxReasonableLatencyFrames * 3;
|
|
|
|
|
int numCorrelations = std::min(numFloats, maxCorrelationSize);
|
|
|
|
|
float *correlations = new float[numCorrelations]{};
|
|
|
|
|
float *harmonicSums = new float[numCorrelations]{};
|
|
|
|
|
|
|
|
|
|
// Perform sliding auto-correlation.
|
|
|
|
|
// Skip first frames to avoid huge peak at zero offset.
|
|
|
|
|
for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
|
|
|
|
|
int32_t remaining = numFloats - i;
|
|
|
|
|
float correlation = (float) calculateCorrelation(&data[i], data, remaining);
|
|
|
|
|
correlations[i] = correlation;
|
|
|
|
|
// printf("correlation[%d] = %f\n", ic, correlation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return location;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct LatencyReport_s {
|
|
|
|
|
double latencyInFrames;
|
|
|
|
|
double confidence;
|
|
|
|
|
} LatencyReport;
|
|
|
|
|
|
|
|
|
|
// Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
|
|
|
|
|
// Using first echo instead of the original impulse for a better match.
|
|
|
|
|
static int measureLatencyFromEchos(const float *haystack, int haystackSize,
|
|
|
|
|
const float *needle, int needleSize,
|
|
|
|
|
LatencyReport *report) {
|
|
|
|
|
const double threshold = 0.1;
|
|
|
|
|
printf("measureLatencyFromEchos: haystackSize = %d, needleSize = %d\n",
|
|
|
|
|
haystackSize, needleSize);
|
|
|
|
|
|
|
|
|
|
// Find first peak
|
|
|
|
|
int first = (int) (findFirstMatch(haystack,
|
|
|
|
|
haystackSize,
|
|
|
|
|
needle,
|
|
|
|
|
needleSize,
|
|
|
|
|
threshold) + 0.5);
|
|
|
|
|
|
|
|
|
|
// Use first echo as the needle for the other echos because
|
|
|
|
|
// it will be more similar.
|
|
|
|
|
needle = &haystack[first];
|
|
|
|
|
int again = (int) (findFirstMatch(haystack,
|
|
|
|
|
haystackSize,
|
|
|
|
|
needle,
|
|
|
|
|
needleSize,
|
|
|
|
|
threshold) + 0.5);
|
|
|
|
|
|
|
|
|
|
printf("measureLatencyFromEchos: first = %d, again at %d\n", first, again);
|
|
|
|
|
first = again;
|
|
|
|
|
|
|
|
|
|
// Allocate results array
|
|
|
|
|
int remaining = haystackSize - first;
|
|
|
|
|
const int maxReasonableLatencyFrames = 48000 * 2; // arbitrary but generous value
|
|
|
|
|
int numCorrelations = std::min(remaining, maxReasonableLatencyFrames);
|
|
|
|
|
float *correlations = new float[numCorrelations];
|
|
|
|
|
float *harmonicSums = new float[numCorrelations](); // set to zero
|
|
|
|
|
|
|
|
|
|
// Generate correlation for every position.
|
|
|
|
|
numCorrelations = calculateCorrelations(&haystack[first], remaining,
|
|
|
|
|
needle, needleSize,
|
|
|
|
|
correlations, numCorrelations);
|
|
|
|
|
|
|
|
|
|
// Add higher harmonics mapped onto lower harmonics.
|
|
|
|
|
// This reinforces the "fundamental" echo.
|
|
|
|
|
const int numEchoes = 10;
|
|
|
|
|
// Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
|
|
|
|
|
// Add higher harmonics mapped onto lower harmonics. This reinforces the "fundamental" echo.
|
|
|
|
|
const int numEchoes = 8;
|
|
|
|
|
for (int partial = 1; partial < numEchoes; partial++) {
|
|
|
|
|
for (int i = 0; i < numCorrelations; i++) {
|
|
|
|
|
for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
|
|
|
|
|
harmonicSums[i / partial] += correlations[i] / partial;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find highest peak in correlation array.
|
|
|
|
|
float maxCorrelation = 0.0;
|
|
|
|
|
float sumOfPeaks = 0.0;
|
|
|
|
|
int peakIndex = 0;
|
|
|
|
|
const int skip = MAX_ZEROTH_PARTIAL_BINS; // skip low bins
|
|
|
|
|
for (int i = skip; i < numCorrelations; i++) {
|
|
|
|
|
for (int i = 0; i < numCorrelations; i++) {
|
|
|
|
|
if (harmonicSums[i] > maxCorrelation) {
|
|
|
|
|
maxCorrelation = harmonicSums[i];
|
|
|
|
|
sumOfPeaks += maxCorrelation;
|
|
|
|
|
peakIndex = i;
|
|
|
|
|
printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
|
|
|
|
|
// printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
report->latencyInFrames = peakIndex;
|
|
|
|
|
if (sumOfPeaks < 0.0001) {
|
|
|
|
|
/*
|
|
|
|
|
{
|
|
|
|
|
int32_t topPeak = peakIndex * 7 / 2;
|
|
|
|
|
for (int i = 0; i < topPeak; i++) {
|
|
|
|
|
float sample = harmonicSums[i];
|
|
|
|
|
printf("%4d: %7.5f ", i, sample);
|
|
|
|
|
printAudioScope(sample);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Calculate confidence.
|
|
|
|
|
if (maxCorrelation < 0.001) {
|
|
|
|
|
report->confidence = 0.0;
|
|
|
|
|
} else {
|
|
|
|
|
report->confidence = maxCorrelation / sumOfPeaks;
|
|
|
|
|
// Compare peak to average value around peak.
|
|
|
|
|
int32_t numSamples = std::min(numCorrelations, peakIndex * 2);
|
|
|
|
|
if (numSamples <= 0) {
|
|
|
|
|
report->confidence = 0.0;
|
|
|
|
|
} else {
|
|
|
|
|
double sum = 0.0;
|
|
|
|
|
for (int i = 0; i < numSamples; i++) {
|
|
|
|
|
sum += harmonicSums[i];
|
|
|
|
|
}
|
|
|
|
|
const double average = sum / numSamples;
|
|
|
|
|
const double ratio = average / maxCorrelation; // will be < 1.0
|
|
|
|
|
report->confidence = 1.0 - sqrt(ratio);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete[] correlations;
|
|
|
|
@ -320,7 +382,9 @@ public:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(info.channels == 1);
|
|
|
|
|
assert(info.format == SF_FORMAT_FLOAT);
|
|
|
|
|
|
|
|
|
|
setSampleRate(info.samplerate);
|
|
|
|
|
allocate(info.frames);
|
|
|
|
|
mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
|
|
|
|
|
|
|
|
|
@ -328,11 +392,49 @@ public:
|
|
|
|
|
return mFrameCounter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Square the samples so they are all positive and so the peaks are emphasized.
|
|
|
|
|
*/
|
|
|
|
|
void square() {
|
|
|
|
|
for (int i = 0; i < mFrameCounter; i++) {
|
|
|
|
|
const float sample = mData[i];
|
|
|
|
|
mData[i] = sample * sample;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Low pass filter the recording using a simple FIR filter.
|
|
|
|
|
* Note that the lowpass filter cutoff tracks the sample rate.
|
|
|
|
|
* That is OK because the impulse width is a fixed number of samples.
|
|
|
|
|
*/
|
|
|
|
|
void lowPassFilter() {
|
|
|
|
|
for (int i = 0; i < mFrameCounter; i++) {
|
|
|
|
|
mData[i] = mLowPassFilter.filter(mData[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove DC offset using a one-pole one-zero IIR filter.
|
|
|
|
|
*/
|
|
|
|
|
void dcBlocker() {
|
|
|
|
|
const float R = 0.996; // narrow notch at zero Hz
|
|
|
|
|
float x1 = 0.0;
|
|
|
|
|
float y1 = 0.0;
|
|
|
|
|
for (int i = 0; i < mFrameCounter; i++) {
|
|
|
|
|
const float x = mData[i];
|
|
|
|
|
const float y = x - x1 + (R * y1);
|
|
|
|
|
mData[i] = y;
|
|
|
|
|
y1 = y;
|
|
|
|
|
x1 = x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
float *mData = nullptr;
|
|
|
|
|
int32_t mFrameCounter = 0;
|
|
|
|
|
int32_t mMaxFrames = 0;
|
|
|
|
|
int32_t mSampleRate = 48000; // common default
|
|
|
|
|
float *mData = nullptr;
|
|
|
|
|
int32_t mFrameCounter = 0;
|
|
|
|
|
int32_t mMaxFrames = 0;
|
|
|
|
|
int32_t mSampleRate = kDefaultSampleRate; // common default
|
|
|
|
|
LowPassFilter mLowPassFilter;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ====================================================================================
|
|
|
|
@ -352,8 +454,12 @@ public:
|
|
|
|
|
|
|
|
|
|
virtual void printStatus() {};
|
|
|
|
|
|
|
|
|
|
virtual int getResult() {
|
|
|
|
|
return -1;
|
|
|
|
|
int32_t getResult() {
|
|
|
|
|
return mResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setResult(int32_t result) {
|
|
|
|
|
mResult = result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual bool isDone() {
|
|
|
|
@ -382,7 +488,7 @@ public:
|
|
|
|
|
static float measurePeakAmplitude(float *inputData, int inputChannelCount, int numFrames) {
|
|
|
|
|
float peak = 0.0f;
|
|
|
|
|
for (int i = 0; i < numFrames; i++) {
|
|
|
|
|
float pos = fabs(*inputData);
|
|
|
|
|
const float pos = fabs(*inputData);
|
|
|
|
|
if (pos > peak) {
|
|
|
|
|
peak = pos;
|
|
|
|
|
}
|
|
|
|
@ -393,7 +499,8 @@ public:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
int32_t mSampleRate = LOOPBACK_SAMPLE_RATE;
|
|
|
|
|
int32_t mSampleRate = kDefaultSampleRate;
|
|
|
|
|
int32_t mResult = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class PeakDetector {
|
|
|
|
@ -412,24 +519,6 @@ private:
|
|
|
|
|
float mPrevious = 0.0f;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void printAudioScope(float sample) {
|
|
|
|
|
const int maxStars = 80; // arbitrary, fits on one line
|
|
|
|
|
char c = '*';
|
|
|
|
|
if (sample < -1.0) {
|
|
|
|
|
sample = -1.0;
|
|
|
|
|
c = '$';
|
|
|
|
|
} else if (sample > 1.0) {
|
|
|
|
|
sample = 1.0;
|
|
|
|
|
c = '$';
|
|
|
|
|
}
|
|
|
|
|
int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
|
|
|
|
|
for (int i = 0; i < numSpaces; i++) {
|
|
|
|
|
putchar(' ');
|
|
|
|
|
}
|
|
|
|
|
printf("%c\n", c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ====================================================================================
|
|
|
|
|
/**
|
|
|
|
|
* Measure latency given a loopback stream data.
|
|
|
|
@ -450,17 +539,13 @@ public:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void reset() override {
|
|
|
|
|
mDownCounter = 200;
|
|
|
|
|
mDownCounter = getSampleRate() / 2;
|
|
|
|
|
mLoopCounter = 0;
|
|
|
|
|
mMeasuredLoopGain = 0.0f;
|
|
|
|
|
mEchoGain = 1.0f;
|
|
|
|
|
mState = STATE_INITIAL_SILENCE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual int getResult() {
|
|
|
|
|
return mState == STATE_DONE ? 0 : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual bool isDone() {
|
|
|
|
|
return mState == STATE_DONE || mState == STATE_FAILED;
|
|
|
|
|
}
|
|
|
|
@ -473,27 +558,57 @@ public:
|
|
|
|
|
return mEchoGain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void report() override {
|
|
|
|
|
bool testLowPassFilter() {
|
|
|
|
|
LowPassFilter filter;
|
|
|
|
|
return filter.test();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void report() override {
|
|
|
|
|
printf("EchoAnalyzer ---------------\n");
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "measured.gain = %f\n", mMeasuredLoopGain);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "echo.gain = %f\n", mEchoGain);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "test.state = %d\n", mState);
|
|
|
|
|
if (mMeasuredLoopGain >= 0.9999) {
|
|
|
|
|
if (getResult() != 0) {
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "result = %d\n", getResult());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// printf("LowPassFilter test %s\n", testLowPassFilter() ? "PASSED" : "FAILED");
|
|
|
|
|
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "measured.gain = %8f\n", mMeasuredLoopGain);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "echo.gain = %8f\n", mEchoGain);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "test.state = %8d\n", mState);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "test.state.name = %8s\n", convertStateToText(mState));
|
|
|
|
|
|
|
|
|
|
if (mState == STATE_WAITING_FOR_SILENCE) {
|
|
|
|
|
printf("WARNING - Stuck waiting for silence. Input may be too noisy!\n");
|
|
|
|
|
setResult(ERROR_NOISY);
|
|
|
|
|
} else if (mMeasuredLoopGain >= 0.9999) {
|
|
|
|
|
printf(" ERROR - clipping, turn down volume slightly\n");
|
|
|
|
|
setResult(ERROR_CLIPPING);
|
|
|
|
|
} else if (mState != STATE_DONE && mState != STATE_GATHERING_ECHOS) {
|
|
|
|
|
printf("WARNING - Bad state. Check volume on device.\n");
|
|
|
|
|
setResult(ERROR_INVALID_STATE);
|
|
|
|
|
} else {
|
|
|
|
|
const float *needle = s_Impulse;
|
|
|
|
|
int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
|
|
|
|
|
float *haystack = mAudioRecording.getData();
|
|
|
|
|
int haystackSize = mAudioRecording.size();
|
|
|
|
|
measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &mLatencyReport);
|
|
|
|
|
if (mLatencyReport.confidence < 0.01) {
|
|
|
|
|
printf(" ERROR - confidence too low = %f\n", mLatencyReport.confidence);
|
|
|
|
|
} else {
|
|
|
|
|
double latencyMillis = 1000.0 * mLatencyReport.latencyInFrames / getSampleRate();
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n", mLatencyReport.latencyInFrames);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "latency.msec = %8.2f\n", latencyMillis);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n", mLatencyReport.confidence);
|
|
|
|
|
// Cleanup the signal to improve the auto-correlation.
|
|
|
|
|
mAudioRecording.dcBlocker();
|
|
|
|
|
mAudioRecording.square();
|
|
|
|
|
mAudioRecording.lowPassFilter();
|
|
|
|
|
|
|
|
|
|
printf("Please wait several seconds for auto-correlation to complete.\n");
|
|
|
|
|
measureLatencyFromEchos(mAudioRecording.getData(),
|
|
|
|
|
mAudioRecording.size(),
|
|
|
|
|
getSampleRate(),
|
|
|
|
|
&mLatencyReport);
|
|
|
|
|
|
|
|
|
|
double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
|
|
|
|
|
/ getSampleRate();
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n",
|
|
|
|
|
mLatencyReport.latencyInFrames);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "latency.msec = %8.2f\n",
|
|
|
|
|
latencyMillis);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n",
|
|
|
|
|
mLatencyReport.confidence);
|
|
|
|
|
if (mLatencyReport.confidence < kMinimumConfidence) {
|
|
|
|
|
printf(" ERROR - confidence too low!\n");
|
|
|
|
|
setResult(ERROR_CONFIDENCE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -519,6 +634,11 @@ public:
|
|
|
|
|
sendImpulses(outputData, outputChannelCount, kImpulseSizeInFrames);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @return number of frames for a typical block of processing
|
|
|
|
|
int32_t getBlockFrames() {
|
|
|
|
|
return getSampleRate() / 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void process(float *inputData, int inputChannelCount,
|
|
|
|
|
float *outputData, int outputChannelCount,
|
|
|
|
|
int numFrames) override {
|
|
|
|
@ -527,7 +647,7 @@ public:
|
|
|
|
|
int numWritten;
|
|
|
|
|
int numSamples;
|
|
|
|
|
|
|
|
|
|
echo_state_t nextState = mState;
|
|
|
|
|
echo_state nextState = mState;
|
|
|
|
|
|
|
|
|
|
switch (mState) {
|
|
|
|
|
case STATE_INITIAL_SILENCE:
|
|
|
|
@ -536,10 +656,11 @@ public:
|
|
|
|
|
for (int i = 0; i < numSamples; i++) {
|
|
|
|
|
outputData[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
if (mDownCounter-- <= 0) {
|
|
|
|
|
mDownCounter -= numFrames;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
nextState = STATE_MEASURING_GAIN;
|
|
|
|
|
//printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
|
|
|
|
|
mDownCounter = 8;
|
|
|
|
|
mDownCounter = getBlockFrames() * 2;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -548,14 +669,16 @@ public:
|
|
|
|
|
peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
|
|
|
|
|
// If we get several in a row then go to next state.
|
|
|
|
|
if (peak > mPulseThreshold) {
|
|
|
|
|
if (mDownCounter-- <= 0) {
|
|
|
|
|
mDownCounter -= numFrames;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
//printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
|
|
|
|
|
// mLoopCounter, peak);
|
|
|
|
|
mDownCounter = 8;
|
|
|
|
|
mDownCounter = getBlockFrames();
|
|
|
|
|
mMeasuredLoopGain = peak; // assumes original pulse amplitude is one
|
|
|
|
|
mSilenceThreshold = peak * 0.1; // scale silence to measured pulse
|
|
|
|
|
// Calculate gain that will give us a nice decaying echo.
|
|
|
|
|
mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
|
|
|
|
|
if (mEchoGain > MAX_ECHO_GAIN) {
|
|
|
|
|
if (mEchoGain > kMaxEchoGain) {
|
|
|
|
|
printf("ERROR - loop gain too low. Increase the volume.\n");
|
|
|
|
|
nextState = STATE_FAILED;
|
|
|
|
|
} else {
|
|
|
|
@ -563,7 +686,7 @@ public:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (numFrames > kImpulseSizeInFrames){ // ignore short callbacks
|
|
|
|
|
mDownCounter = 8;
|
|
|
|
|
mDownCounter = getBlockFrames();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -576,13 +699,14 @@ public:
|
|
|
|
|
peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
|
|
|
|
|
// If we get several in a row then go to next state.
|
|
|
|
|
if (peak < mSilenceThreshold) {
|
|
|
|
|
if (mDownCounter-- <= 0) {
|
|
|
|
|
mDownCounter -= numFrames;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
nextState = STATE_SENDING_PULSE;
|
|
|
|
|
//printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
|
|
|
|
|
mDownCounter = 8;
|
|
|
|
|
mDownCounter = getBlockFrames();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mDownCounter = 8;
|
|
|
|
|
mDownCounter = getBlockFrames();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -615,11 +739,11 @@ public:
|
|
|
|
|
}
|
|
|
|
|
if (numWritten < numFrames) {
|
|
|
|
|
nextState = STATE_DONE;
|
|
|
|
|
//printf("%5d: switch to STATE_DONE\n", mLoopCounter);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case STATE_DONE:
|
|
|
|
|
case STATE_FAILED:
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -633,12 +757,23 @@ public:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int load(const char *fileName) override {
|
|
|
|
|
return mAudioRecording.load(fileName);
|
|
|
|
|
int result = mAudioRecording.load(fileName);
|
|
|
|
|
setSampleRate(mAudioRecording.getSampleRate());
|
|
|
|
|
mState = STATE_DONE;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
|
|
enum echo_state_t {
|
|
|
|
|
enum error_code {
|
|
|
|
|
ERROR_OK = 0,
|
|
|
|
|
ERROR_NOISY = -99,
|
|
|
|
|
ERROR_CLIPPING,
|
|
|
|
|
ERROR_CONFIDENCE,
|
|
|
|
|
ERROR_INVALID_STATE
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum echo_state {
|
|
|
|
|
STATE_INITIAL_SILENCE,
|
|
|
|
|
STATE_MEASURING_GAIN,
|
|
|
|
|
STATE_WAITING_FOR_SILENCE,
|
|
|
|
@ -648,6 +783,35 @@ private:
|
|
|
|
|
STATE_FAILED
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const char *convertStateToText(echo_state state) {
|
|
|
|
|
const char *result = "Unknown";
|
|
|
|
|
switch(state) {
|
|
|
|
|
case STATE_INITIAL_SILENCE:
|
|
|
|
|
result = "INIT";
|
|
|
|
|
break;
|
|
|
|
|
case STATE_MEASURING_GAIN:
|
|
|
|
|
result = "GAIN";
|
|
|
|
|
break;
|
|
|
|
|
case STATE_WAITING_FOR_SILENCE:
|
|
|
|
|
result = "SILENCE";
|
|
|
|
|
break;
|
|
|
|
|
case STATE_SENDING_PULSE:
|
|
|
|
|
result = "PULSE";
|
|
|
|
|
break;
|
|
|
|
|
case STATE_GATHERING_ECHOS:
|
|
|
|
|
result = "ECHOS";
|
|
|
|
|
break;
|
|
|
|
|
case STATE_DONE:
|
|
|
|
|
result = "DONE";
|
|
|
|
|
break;
|
|
|
|
|
case STATE_FAILED:
|
|
|
|
|
result = "FAILED";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int32_t mDownCounter = 500;
|
|
|
|
|
int32_t mLoopCounter = 0;
|
|
|
|
|
int32_t mSampleIndex = 0;
|
|
|
|
@ -656,7 +820,7 @@ private:
|
|
|
|
|
float mMeasuredLoopGain = 0.0f;
|
|
|
|
|
float mDesiredEchoGain = 0.95f;
|
|
|
|
|
float mEchoGain = 1.0f;
|
|
|
|
|
echo_state_t mState = STATE_INITIAL_SILENCE;
|
|
|
|
|
echo_state mState = STATE_INITIAL_SILENCE;
|
|
|
|
|
|
|
|
|
|
AudioRecording mAudioRecording; // contains only the input after the gain detection burst
|
|
|
|
|
LatencyReport mLatencyReport;
|
|
|
|
@ -674,27 +838,38 @@ private:
|
|
|
|
|
class SineAnalyzer : public LoopbackProcessor {
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
virtual int getResult() {
|
|
|
|
|
return mState == STATE_LOCKED ? 0 : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void report() override {
|
|
|
|
|
printf("SineAnalyzer ------------------\n");
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "peak.amplitude = %7.5f\n", mPeakAmplitude);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "sine.magnitude = %7.5f\n", mMagnitude);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "phase.offset = %7.5f\n", mPhaseOffset);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "ref.phase = %7.5f\n", mPhase);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "frames.accumulated = %6d\n", mFramesAccumulated);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "sine.period = %6d\n", mSinePeriod);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "test.state = %6d\n", mState);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "frame.count = %6d\n", mFrameCounter);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "peak.amplitude = %8f\n", mPeakAmplitude);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "sine.magnitude = %8f\n", mMagnitude);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "peak.noise = %8f\n", mPeakNoise);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "rms.noise = %8f\n", mRootMeanSquareNoise);
|
|
|
|
|
float amplitudeRatio = mMagnitude / mPeakNoise;
|
|
|
|
|
float signalToNoise = amplitudeRatio * amplitudeRatio;
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "signal.to.noise = %8.2f\n", signalToNoise);
|
|
|
|
|
float signalToNoiseDB = 10.0 * log(signalToNoise);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "signal.to.noise.db = %8.2f\n", signalToNoiseDB);
|
|
|
|
|
if (signalToNoiseDB < MIN_SNRATIO_DB) {
|
|
|
|
|
printf("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.\n", MIN_SNRATIO_DB);
|
|
|
|
|
setResult(ERROR_NOISY);
|
|
|
|
|
}
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "frames.accumulated = %8d\n", mFramesAccumulated);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "sine.period = %8d\n", mSinePeriod);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "test.state = %8d\n", mState);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "frame.count = %8d\n", mFrameCounter);
|
|
|
|
|
// Did we ever get a lock?
|
|
|
|
|
bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
|
|
|
|
|
if (!gotLock) {
|
|
|
|
|
printf("ERROR - failed to lock on reference sine tone\n");
|
|
|
|
|
setResult(ERROR_NO_LOCK);
|
|
|
|
|
} else {
|
|
|
|
|
// Only print if meaningful.
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "glitch.count = %6d\n", mGlitchCount);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "glitch.count = %8d\n", mGlitchCount);
|
|
|
|
|
printf(LOOPBACK_RESULT_TAG "max.glitch = %8f\n", mMaxGlitchDelta);
|
|
|
|
|
if (mGlitchCount > 0) {
|
|
|
|
|
printf("ERROR - number of glitches > 0\n");
|
|
|
|
|
setResult(ERROR_GLITCHES);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -732,15 +907,48 @@ public:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numFrames; i++) {
|
|
|
|
|
bool sineEnabled = true;
|
|
|
|
|
float sample = inputData[i * inputChannelCount];
|
|
|
|
|
|
|
|
|
|
float sinOut = sinf(mPhase);
|
|
|
|
|
|
|
|
|
|
switch (mState) {
|
|
|
|
|
case STATE_IDLE:
|
|
|
|
|
sineEnabled = false;
|
|
|
|
|
mDownCounter--;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
mState = STATE_MEASURE_NOISE;
|
|
|
|
|
mDownCounter = NOISE_FRAME_COUNT;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case STATE_MEASURE_NOISE:
|
|
|
|
|
sineEnabled = false;
|
|
|
|
|
mPeakNoise = std::max(abs(sample), mPeakNoise);
|
|
|
|
|
mNoiseSumSquared += sample * sample;
|
|
|
|
|
mDownCounter--;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
mState = STATE_WAITING_FOR_SIGNAL;
|
|
|
|
|
mRootMeanSquareNoise = sqrt(mNoiseSumSquared / NOISE_FRAME_COUNT);
|
|
|
|
|
mTolerance = std::max(MIN_TOLERANCE, mPeakNoise * 2.0f);
|
|
|
|
|
mPhase = 0.0; // prevent spike at start
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case STATE_IMMUNE:
|
|
|
|
|
mDownCounter--;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
mState = STATE_WAITING_FOR_SIGNAL;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case STATE_WAITING_FOR_SIGNAL:
|
|
|
|
|
if (peak > mThreshold) {
|
|
|
|
|
mState = STATE_WAITING_FOR_LOCK;
|
|
|
|
|
//printf("%5d: switch to STATE_WAITING_FOR_LOCK\n", mFrameCounter);
|
|
|
|
|
resetAccumulator();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case STATE_WAITING_FOR_LOCK:
|
|
|
|
|
mSinAccumulator += sample * sinOut;
|
|
|
|
|
mCosAccumulator += sample * cosf(mPhase);
|
|
|
|
@ -766,13 +974,14 @@ public:
|
|
|
|
|
// printf(" predicted = %f, actual = %f\n", predicted, sample);
|
|
|
|
|
|
|
|
|
|
float diff = predicted - sample;
|
|
|
|
|
if (fabs(diff) > mTolerance) {
|
|
|
|
|
float absDiff = fabs(diff);
|
|
|
|
|
mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
|
|
|
|
|
if (absDiff > mTolerance) {
|
|
|
|
|
mGlitchCount++;
|
|
|
|
|
//printf("%5d: Got a glitch # %d, predicted = %f, actual = %f\n",
|
|
|
|
|
// mFrameCounter, mGlitchCount, predicted, sample);
|
|
|
|
|
mState = STATE_IMMUNE;
|
|
|
|
|
//printf("%5d: switch to STATE_IMMUNE\n", mFrameCounter);
|
|
|
|
|
mDownCounter = mSinePeriod; // Set duration of IMMUNE state.
|
|
|
|
|
mDownCounter = mSinePeriod * PERIODS_IMMUNE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Track incoming signal and slowly adjust magnitude to account
|
|
|
|
@ -792,44 +1001,23 @@ public:
|
|
|
|
|
} break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float output = 0.0f;
|
|
|
|
|
// Output sine wave so we can measure it.
|
|
|
|
|
outputData[i * outputChannelCount] = (sinOut * mOutputAmplitude)
|
|
|
|
|
+ (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
|
|
|
|
|
// printf("%5d: sin(%f) = %f, %f\n", i, mPhase, sinOut, mPhaseIncrement);
|
|
|
|
|
|
|
|
|
|
// advance and wrap phase
|
|
|
|
|
mPhase += mPhaseIncrement;
|
|
|
|
|
if (mPhase > M_PI) {
|
|
|
|
|
mPhase -= (2.0 * M_PI);
|
|
|
|
|
if (sineEnabled) {
|
|
|
|
|
output = (sinOut * mOutputAmplitude)
|
|
|
|
|
+ (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
|
|
|
|
|
// printf("%5d: sin(%f) = %f, %f\n", i, mPhase, sinOut, mPhaseIncrement);
|
|
|
|
|
// advance and wrap phase
|
|
|
|
|
mPhase += mPhaseIncrement;
|
|
|
|
|
if (mPhase > M_PI) {
|
|
|
|
|
mPhase -= (2.0 * M_PI);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
outputData[i * outputChannelCount] = output;
|
|
|
|
|
|
|
|
|
|
mFrameCounter++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Do these once per buffer.
|
|
|
|
|
switch (mState) {
|
|
|
|
|
case STATE_IDLE:
|
|
|
|
|
mState = STATE_IMMUNE; // so we can tell when
|
|
|
|
|
break;
|
|
|
|
|
case STATE_IMMUNE:
|
|
|
|
|
mDownCounter -= numFrames;
|
|
|
|
|
if (mDownCounter <= 0) {
|
|
|
|
|
mState = STATE_WAITING_FOR_SIGNAL;
|
|
|
|
|
//printf("%5d: switch to STATE_WAITING_FOR_SIGNAL\n", mFrameCounter);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case STATE_WAITING_FOR_SIGNAL:
|
|
|
|
|
if (peak > mThreshold) {
|
|
|
|
|
mState = STATE_WAITING_FOR_LOCK;
|
|
|
|
|
//printf("%5d: switch to STATE_WAITING_FOR_LOCK\n", mFrameCounter);
|
|
|
|
|
resetAccumulator();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case STATE_WAITING_FOR_LOCK:
|
|
|
|
|
case STATE_LOCKED:
|
|
|
|
|
break;
|
|
|
|
|
mFrameCounter++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void resetAccumulator() {
|
|
|
|
@ -840,18 +1028,31 @@ public:
|
|
|
|
|
|
|
|
|
|
void reset() override {
|
|
|
|
|
mGlitchCount = 0;
|
|
|
|
|
mState = STATE_IMMUNE;
|
|
|
|
|
mDownCounter = IMMUNE_FRAME_COUNT;
|
|
|
|
|
mState = STATE_IDLE;
|
|
|
|
|
mDownCounter = IDLE_FRAME_COUNT;
|
|
|
|
|
mPhaseIncrement = 2.0 * M_PI / mSinePeriod;
|
|
|
|
|
printf("phaseInc = %f for period %d\n", mPhaseIncrement, mSinePeriod);
|
|
|
|
|
resetAccumulator();
|
|
|
|
|
mProcessCount = 0;
|
|
|
|
|
mPeakNoise = 0.0f;
|
|
|
|
|
mNoiseSumSquared = 0.0;
|
|
|
|
|
mRootMeanSquareNoise = 0.0;
|
|
|
|
|
mPhase = 0.0f;
|
|
|
|
|
mMaxGlitchDelta = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
|
|
enum error_code {
|
|
|
|
|
OK,
|
|
|
|
|
ERROR_NO_LOCK = -80,
|
|
|
|
|
ERROR_GLITCHES,
|
|
|
|
|
ERROR_NOISY
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum sine_state_t {
|
|
|
|
|
STATE_IDLE,
|
|
|
|
|
STATE_MEASURE_NOISE,
|
|
|
|
|
STATE_IMMUNE,
|
|
|
|
|
STATE_WAITING_FOR_SIGNAL,
|
|
|
|
|
STATE_WAITING_FOR_LOCK,
|
|
|
|
@ -859,10 +1060,16 @@ private:
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum constants {
|
|
|
|
|
IMMUNE_FRAME_COUNT = 48 * 500,
|
|
|
|
|
PERIODS_NEEDED_FOR_LOCK = 8
|
|
|
|
|
// Arbitrary durations, assuming 48000 Hz
|
|
|
|
|
IDLE_FRAME_COUNT = 48 * 100,
|
|
|
|
|
NOISE_FRAME_COUNT = 48 * 600,
|
|
|
|
|
PERIODS_NEEDED_FOR_LOCK = 8,
|
|
|
|
|
PERIODS_IMMUNE = 2,
|
|
|
|
|
MIN_SNRATIO_DB = 65
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static constexpr float MIN_TOLERANCE = 0.01;
|
|
|
|
|
|
|
|
|
|
int mSinePeriod = 79;
|
|
|
|
|
double mPhaseIncrement = 0.0;
|
|
|
|
|
double mPhase = 0.0;
|
|
|
|
@ -870,25 +1077,29 @@ private:
|
|
|
|
|
double mPreviousPhaseOffset = 0.0;
|
|
|
|
|
double mMagnitude = 0.0;
|
|
|
|
|
double mThreshold = 0.005;
|
|
|
|
|
double mTolerance = 0.01;
|
|
|
|
|
double mTolerance = MIN_TOLERANCE;
|
|
|
|
|
int32_t mFramesAccumulated = 0;
|
|
|
|
|
int32_t mProcessCount = 0;
|
|
|
|
|
double mSinAccumulator = 0.0;
|
|
|
|
|
double mCosAccumulator = 0.0;
|
|
|
|
|
float mMaxGlitchDelta = 0.0f;
|
|
|
|
|
int32_t mGlitchCount = 0;
|
|
|
|
|
double mPeakAmplitude = 0.0;
|
|
|
|
|
int mDownCounter = IMMUNE_FRAME_COUNT;
|
|
|
|
|
int mDownCounter = IDLE_FRAME_COUNT;
|
|
|
|
|
int32_t mFrameCounter = 0;
|
|
|
|
|
float mOutputAmplitude = 0.75;
|
|
|
|
|
|
|
|
|
|
// measure background noise
|
|
|
|
|
float mPeakNoise = 0.0f;
|
|
|
|
|
double mNoiseSumSquared = 0.0;
|
|
|
|
|
double mRootMeanSquareNoise = 0.0;
|
|
|
|
|
|
|
|
|
|
PseudoRandom mWhiteNoise;
|
|
|
|
|
float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
|
|
|
|
|
|
|
|
|
|
sine_state_t mState = STATE_IDLE;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#undef LOOPBACK_SAMPLE_RATE
|
|
|
|
|
#undef LOOPBACK_RESULT_TAG
|
|
|
|
|
|
|
|
|
|
#endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
|
|
|
|
|