LoopbackAnalyzer.h revision 68326fe017a18957419a81489b67f75831f9a674
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Tools for measuring latency and for detecting glitches.
19 * These classes are pure math and can be used with any audio system.
20 */
21
22#ifndef AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
23#define AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
24
25#include <algorithm>
26#include <assert.h>
27#include <cctype>
28#include <math.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <unistd.h>
32
33// Tag for machine readable results as property = value pairs
34#define LOOPBACK_RESULT_TAG      "RESULT: "
35#define LOOPBACK_SAMPLE_RATE     48000
36
37#define MILLIS_PER_SECOND        1000
38
39#define MAX_ZEROTH_PARTIAL_BINS  40
40
41static const float s_Impulse[] = {
42        0.0f, 0.0f, 0.0f, 0.0f, 0.2f, // silence on each side of the impulse
43        0.5f, 0.9999f, 0.0f, -0.9999, -0.5f, // bipolar
44        -0.2f, 0.0f, 0.0f, 0.0f, 0.0f
45};
46
47class PseudoRandom {
48public:
49    PseudoRandom() {}
50    PseudoRandom(int64_t seed)
51            :    mSeed(seed)
52    {}
53
54    /**
55     * Returns the next random double from -1.0 to 1.0
56     *
57     * @return value from -1.0 to 1.0
58     */
59     double nextRandomDouble() {
60        return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
61    }
62
63    /** Calculate random 32 bit number using linear-congruential method. */
64    int32_t nextRandomInteger() {
65        // Use values for 64-bit sequence from MMIX by Donald Knuth.
66        mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
67        return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
68    }
69
70private:
71    int64_t mSeed = 99887766;
72};
73
74static double calculateCorrelation(const float *a,
75                                   const float *b,
76                                   int windowSize)
77{
78    double correlation = 0.0;
79    double sumProducts = 0.0;
80    double sumSquares = 0.0;
81
82    // Correlate a against b.
83    for (int i = 0; i < windowSize; i++) {
84        float s1 = a[i];
85        float s2 = b[i];
86        // Use a normalized cross-correlation.
87        sumProducts += s1 * s2;
88        sumSquares += ((s1 * s1) + (s2 * s2));
89    }
90
91    if (sumSquares >= 0.00000001) {
92        correlation = (float) (2.0 * sumProducts / sumSquares);
93    }
94    return correlation;
95}
96
97static int calculateCorrelations(const float *haystack, int haystackSize,
98                                 const float *needle, int needleSize,
99                                 float *results, int resultSize)
100{
101    int maxCorrelations = haystackSize - needleSize;
102    int numCorrelations = std::min(maxCorrelations, resultSize);
103
104    for (int ic = 0; ic < numCorrelations; ic++) {
105        double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
106        results[ic] = correlation;
107    }
108
109    return numCorrelations;
110}
111
112/*==========================================================================================*/
113/**
114 * Scan until we get a correlation of a single scan that goes over the tolerance level,
115 * peaks then drops back down.
116 */
117static double findFirstMatch(const float *haystack, int haystackSize,
118                             const float *needle, int needleSize, double threshold  )
119{
120    int ic;
121    // How many correlations can we calculate?
122    int numCorrelations = haystackSize - needleSize;
123    double maxCorrelation = 0.0;
124    int peakIndex = -1;
125    double location = -1.0;
126    const double backThresholdScaler = 0.5;
127
128    for (ic = 0; ic < numCorrelations; ic++) {
129        double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
130
131        if( (correlation > maxCorrelation) ) {
132            maxCorrelation = correlation;
133            peakIndex = ic;
134        }
135
136        //printf("PaQa_FindFirstMatch: ic = %4d, correlation = %8f, maxSum = %8f\n",
137        //    ic, correlation, maxSum );
138        // Are we past what we were looking for?
139        if((maxCorrelation > threshold) && (correlation < backThresholdScaler * maxCorrelation)) {
140            location = peakIndex;
141            break;
142        }
143    }
144
145    return location;
146}
147
148typedef struct LatencyReport_s {
149    double latencyInFrames;
150    double confidence;
151} LatencyReport;
152
153// Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
154// Using first echo instead of the original impulse for a better match.
155static int measureLatencyFromEchos(const float *haystack, int haystackSize,
156                            const float *needle, int needleSize,
157                            LatencyReport *report) {
158    const double threshold = 0.1;
159
160    // Find first peak
161    int first = (int) (findFirstMatch(haystack,
162                                      haystackSize,
163                                      needle,
164                                      needleSize,
165                                      threshold) + 0.5);
166
167    // Use first echo as the needle for the other echos because
168    // it will be more similar.
169    needle = &haystack[first];
170    int again = (int) (findFirstMatch(haystack,
171                                      haystackSize,
172                                      needle,
173                                      needleSize,
174                                      threshold) + 0.5);
175
176    printf("first = %d, again at %d\n", first, again);
177    first = again;
178
179    // Allocate results array
180    int remaining = haystackSize - first;
181    const int maxReasonableLatencyFrames = 48000 * 2; // arbitrary but generous value
182    int numCorrelations = std::min(remaining, maxReasonableLatencyFrames);
183    float *correlations = new float[numCorrelations];
184    float *harmonicSums = new float[numCorrelations](); // set to zero
185
186    // Generate correlation for every position.
187    numCorrelations = calculateCorrelations(&haystack[first], remaining,
188                                            needle, needleSize,
189                                            correlations, numCorrelations);
190
191    // Add higher harmonics mapped onto lower harmonics.
192    // This reinforces the "fundamental" echo.
193    const int numEchoes = 10;
194    for (int partial = 1; partial < numEchoes; partial++) {
195        for (int i = 0; i < numCorrelations; i++) {
196            harmonicSums[i / partial] += correlations[i] / partial;
197        }
198    }
199
200    // Find highest peak in correlation array.
201    float maxCorrelation = 0.0;
202    float sumOfPeaks = 0.0;
203    int peakIndex = 0;
204    const int skip = MAX_ZEROTH_PARTIAL_BINS; // skip low bins
205    for (int i = skip; i < numCorrelations; i++) {
206        if (harmonicSums[i] > maxCorrelation) {
207            maxCorrelation = harmonicSums[i];
208            sumOfPeaks += maxCorrelation;
209            peakIndex = i;
210            printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
211        }
212    }
213
214    report->latencyInFrames = peakIndex;
215    if (sumOfPeaks < 0.0001) {
216        report->confidence = 0.0;
217    } else {
218        report->confidence = maxCorrelation / sumOfPeaks;
219    }
220
221    delete[] correlations;
222    delete[] harmonicSums;
223    return 0;
224}
225
226class AudioRecording
227{
228public:
229    AudioRecording() {
230    }
231    ~AudioRecording() {
232        delete[] mData;
233    }
234
235    void allocate(int maxFrames) {
236        delete[] mData;
237        mData = new float[maxFrames];
238        mMaxFrames = maxFrames;
239    }
240
241    // Write SHORT data from the first channel.
242    int write(int16_t *inputData, int inputChannelCount, int numFrames) {
243        // stop at end of buffer
244        if ((mFrameCounter + numFrames) > mMaxFrames) {
245            numFrames = mMaxFrames - mFrameCounter;
246        }
247        for (int i = 0; i < numFrames; i++) {
248            mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
249        }
250        return numFrames;
251    }
252
253    // Write FLOAT data from the first channel.
254    int write(float *inputData, int inputChannelCount, int numFrames) {
255        // stop at end of buffer
256        if ((mFrameCounter + numFrames) > mMaxFrames) {
257            numFrames = mMaxFrames - mFrameCounter;
258        }
259        for (int i = 0; i < numFrames; i++) {
260            mData[mFrameCounter++] = inputData[i * inputChannelCount];
261        }
262        return numFrames;
263    }
264
265    int size() {
266        return mFrameCounter;
267    }
268
269    float *getData() {
270        return mData;
271    }
272
273    int save(const char *fileName, bool writeShorts = true) {
274        int written = 0;
275        const int chunkSize = 64;
276        FILE *fid = fopen(fileName, "wb");
277        if (fid == NULL) {
278            return -errno;
279        }
280
281        if (writeShorts) {
282            int16_t buffer[chunkSize];
283            int32_t framesLeft = mFrameCounter;
284            int32_t cursor = 0;
285            while (framesLeft) {
286                int32_t framesToWrite = framesLeft < chunkSize ? framesLeft : chunkSize;
287                for (int i = 0; i < framesToWrite; i++) {
288                    buffer[i] = (int16_t) (mData[cursor++] * 32767);
289                }
290                written += fwrite(buffer, sizeof(int16_t), framesToWrite, fid);
291                framesLeft -= framesToWrite;
292            }
293        } else {
294            written = (int) fwrite(mData, sizeof(float), mFrameCounter, fid);
295        }
296        fclose(fid);
297        return written;
298    }
299
300private:
301    float  *mData = nullptr;
302    int32_t mFrameCounter = 0;
303    int32_t mMaxFrames = 0;
304};
305
306// ====================================================================================
307class LoopbackProcessor {
308public:
309    virtual ~LoopbackProcessor() = default;
310
311
312    virtual void reset() {}
313
314    virtual void process(float *inputData, int inputChannelCount,
315                 float *outputData, int outputChannelCount,
316                 int numFrames) = 0;
317
318
319    virtual void report() = 0;
320
321    virtual void printStatus() {};
322
323    virtual bool isDone() {
324        return false;
325    }
326
327    void setSampleRate(int32_t sampleRate) {
328        mSampleRate = sampleRate;
329    }
330
331    int32_t getSampleRate() {
332        return mSampleRate;
333    }
334
335    // Measure peak amplitude of buffer.
336    static float measurePeakAmplitude(float *inputData, int inputChannelCount, int numFrames) {
337        float peak = 0.0f;
338        for (int i = 0; i < numFrames; i++) {
339            float pos = fabs(*inputData);
340            if (pos > peak) {
341                peak = pos;
342            }
343            inputData += inputChannelCount;
344        }
345        return peak;
346    }
347
348
349private:
350    int32_t mSampleRate = LOOPBACK_SAMPLE_RATE;
351};
352
353class PeakDetector {
354public:
355    float process(float input) {
356        float output = mPrevious * mDecay;
357        if (input > output) {
358            output = input;
359        }
360        mPrevious = output;
361        return output;
362    }
363
364private:
365    float  mDecay = 0.99f;
366    float  mPrevious = 0.0f;
367};
368
369
370static void printAudioScope(float sample) {
371    const int maxStars = 80
372    ; // arbitrary, fits on one line
373    char c = '*';
374    if (sample < -1.0) {
375        sample = -1.0;
376        c = '$';
377    } else if (sample > 1.0) {
378        sample = 1.0;
379        c = '$';
380    }
381    int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
382    for (int i = 0; i < numSpaces; i++) {
383        putchar(' ');
384    }
385    printf("%c\n", c);
386}
387
388// ====================================================================================
389/**
390 * Measure latency given a loopback stream data.
391 * Uses a state machine to cycle through various stages including:
392 *
393 */
394class EchoAnalyzer : public LoopbackProcessor {
395public:
396
397    EchoAnalyzer() : LoopbackProcessor() {
398        audioRecorder.allocate(2 * LOOPBACK_SAMPLE_RATE);
399    }
400
401    void reset() override {
402        mDownCounter = 200;
403        mLoopCounter = 0;
404        mMeasuredLoopGain = 0.0f;
405        mEchoGain = 1.0f;
406        mState = STATE_INITIAL_SILENCE;
407    }
408
409    virtual bool isDone() {
410        return mState == STATE_DONE;
411    }
412
413    void setGain(float gain) {
414        mEchoGain = gain;
415    }
416
417    float getGain() {
418        return mEchoGain;
419    }
420
421    void report() override {
422
423        printf("EchoAnalyzer ---------------\n");
424        printf(LOOPBACK_RESULT_TAG "measured.gain          = %f\n", mMeasuredLoopGain);
425        printf(LOOPBACK_RESULT_TAG "echo.gain              = %f\n", mEchoGain);
426        printf(LOOPBACK_RESULT_TAG "frame.count            = %d\n", mFrameCounter);
427        printf(LOOPBACK_RESULT_TAG "test.state             = %d\n", mState);
428        if (mMeasuredLoopGain >= 0.9999) {
429            printf("   ERROR - clipping, turn down volume slightly\n");
430        } else {
431            const float *needle = s_Impulse;
432            int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
433            float *haystack = audioRecorder.getData();
434            int haystackSize = audioRecorder.size();
435            measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &latencyReport);
436            if (latencyReport.confidence < 0.01) {
437                printf("   ERROR - confidence too low = %f\n", latencyReport.confidence);
438            } else {
439                double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate();
440                printf(LOOPBACK_RESULT_TAG "latency.frames        = %8.2f\n", latencyReport.latencyInFrames);
441                printf(LOOPBACK_RESULT_TAG "latency.msec          = %8.2f\n", latencyMillis);
442                printf(LOOPBACK_RESULT_TAG "latency.confidence    = %8.6f\n", latencyReport.confidence);
443            }
444        }
445
446        {
447#define ECHO_FILENAME "/data/oboe_echo.raw"
448            int written = audioRecorder.save(ECHO_FILENAME);
449            printf("Echo wrote %d mono samples to %s on Android device\n", written, ECHO_FILENAME);
450        }
451    }
452
453    void printStatus() override {
454        printf("state = %d, echo gain = %f ", mState, mEchoGain);
455    }
456
457    static void sendImpulse(float *outputData, int outputChannelCount) {
458        for (float sample : s_Impulse) {
459            *outputData = sample;
460            outputData += outputChannelCount;
461        }
462    }
463
464    void process(float *inputData, int inputChannelCount,
465                 float *outputData, int outputChannelCount,
466                 int numFrames) override {
467        int channelsValid = std::min(inputChannelCount, outputChannelCount);
468        float peak = 0.0f;
469        int numWritten;
470        int numSamples;
471
472        echo_state_t nextState = mState;
473
474        switch (mState) {
475            case STATE_INITIAL_SILENCE:
476                // Output silence at the beginning.
477                numSamples = numFrames * outputChannelCount;
478                for (int i = 0; i < numSamples; i++) {
479                    outputData[i] = 0;
480                }
481                if (mDownCounter-- <= 0) {
482                    nextState = STATE_MEASURING_GAIN;
483                    //printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
484                    mDownCounter = 8;
485                }
486                break;
487
488            case STATE_MEASURING_GAIN:
489                sendImpulse(outputData, outputChannelCount);
490                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
491                // If we get several in a row then go to next state.
492                if (peak > mPulseThreshold) {
493                    if (mDownCounter-- <= 0) {
494                        nextState = STATE_WAITING_FOR_SILENCE;
495                        //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
496                        //       mLoopCounter, peak);
497                        mDownCounter = 8;
498                        mMeasuredLoopGain = peak;  // assumes original pulse amplitude is one
499                        // Calculate gain that will give us a nice decaying echo.
500                        mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
501                    }
502                } else {
503                    mDownCounter = 8;
504                }
505                break;
506
507            case STATE_WAITING_FOR_SILENCE:
508                // Output silence.
509                numSamples = numFrames * outputChannelCount;
510                for (int i = 0; i < numSamples; i++) {
511                    outputData[i] = 0;
512                }
513                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
514                // If we get several in a row then go to next state.
515                if (peak < mSilenceThreshold) {
516                    if (mDownCounter-- <= 0) {
517                        nextState = STATE_SENDING_PULSE;
518                        //printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
519                        mDownCounter = 8;
520                    }
521                } else {
522                    mDownCounter = 8;
523                }
524                break;
525
526            case STATE_SENDING_PULSE:
527                audioRecorder.write(inputData, inputChannelCount, numFrames);
528                sendImpulse(outputData, outputChannelCount);
529                nextState = STATE_GATHERING_ECHOS;
530                //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
531                break;
532
533            case STATE_GATHERING_ECHOS:
534                numWritten = audioRecorder.write(inputData, inputChannelCount, numFrames);
535                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
536                if (peak > mMeasuredLoopGain) {
537                    mMeasuredLoopGain = peak;  // AGC might be raising gain so adjust it on the fly.
538                    // Recalculate gain that will give us a nice decaying echo.
539                    mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
540                }
541                // Echo input to output.
542                for (int i = 0; i < numFrames; i++) {
543                    int ic;
544                    for (ic = 0; ic < channelsValid; ic++) {
545                        outputData[ic] = inputData[ic] * mEchoGain;
546                    }
547                    for (; ic < outputChannelCount; ic++) {
548                        outputData[ic] = 0;
549                    }
550                    inputData += inputChannelCount;
551                    outputData += outputChannelCount;
552                }
553                if (numWritten  < numFrames) {
554                    nextState = STATE_DONE;
555                    //printf("%5d: switch to STATE_DONE\n", mLoopCounter);
556                }
557                break;
558
559            case STATE_DONE:
560            default:
561                break;
562        }
563
564        mState = nextState;
565        mLoopCounter++;
566    }
567
568private:
569
570    enum echo_state_t {
571        STATE_INITIAL_SILENCE,
572        STATE_MEASURING_GAIN,
573        STATE_WAITING_FOR_SILENCE,
574        STATE_SENDING_PULSE,
575        STATE_GATHERING_ECHOS,
576        STATE_DONE
577    };
578
579    int           mDownCounter = 500;
580    int           mLoopCounter = 0;
581    float         mPulseThreshold = 0.02f;
582    float         mSilenceThreshold = 0.002f;
583    float         mMeasuredLoopGain = 0.0f;
584    float         mDesiredEchoGain = 0.95f;
585    float         mEchoGain = 1.0f;
586    echo_state_t  mState = STATE_INITIAL_SILENCE;
587    int32_t       mFrameCounter = 0;
588
589    AudioRecording     audioRecorder;
590    LatencyReport      latencyReport;
591    PeakDetector       mPeakDetector;
592};
593
594
595// ====================================================================================
596/**
597 * Output a steady sinewave and analyze the return signal.
598 *
599 * Use a cosine transform to measure the predicted magnitude and relative phase of the
600 * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
601 */
602class SineAnalyzer : public LoopbackProcessor {
603public:
604
605    void report() override {
606        printf("SineAnalyzer ------------------\n");
607        printf(LOOPBACK_RESULT_TAG "peak.amplitude     = %7.5f\n", mPeakAmplitude);
608        printf(LOOPBACK_RESULT_TAG "sine.magnitude     = %7.5f\n", mMagnitude);
609        printf(LOOPBACK_RESULT_TAG "phase.offset       = %7.5f\n", mPhaseOffset);
610        printf(LOOPBACK_RESULT_TAG "ref.phase          = %7.5f\n", mPhase);
611        printf(LOOPBACK_RESULT_TAG "frames.accumulated = %6d\n", mFramesAccumulated);
612        printf(LOOPBACK_RESULT_TAG "sine.period        = %6d\n", mPeriod);
613        printf(LOOPBACK_RESULT_TAG "test.state         = %6d\n", mState);
614        printf(LOOPBACK_RESULT_TAG "frame.count        = %6d\n", mFrameCounter);
615        // Did we ever get a lock?
616        bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
617        if (!gotLock) {
618            printf("ERROR - failed to lock on reference sine tone\n");
619        } else {
620            // Only print if meaningful.
621            printf(LOOPBACK_RESULT_TAG "glitch.count       = %6d\n", mGlitchCount);
622        }
623    }
624
625    void printStatus() override {
626        printf("  state = %d, glitches = %d,", mState, mGlitchCount);
627    }
628
629    double calculateMagnitude(double *phasePtr = NULL) {
630        if (mFramesAccumulated == 0) {
631            return 0.0;
632        }
633        double sinMean = mSinAccumulator / mFramesAccumulated;
634        double cosMean = mCosAccumulator / mFramesAccumulated;
635        double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
636        if( phasePtr != NULL )
637        {
638            double phase = M_PI_2 - atan2( sinMean, cosMean );
639            *phasePtr = phase;
640        }
641        return magnitude;
642    }
643
644    /**
645     * @param inputData contains microphone data with sine signal feedback
646     * @param outputData contains the reference sine wave
647     */
648    void process(float *inputData, int inputChannelCount,
649                 float *outputData, int outputChannelCount,
650                 int numFrames) override {
651        float peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
652        if (peak > mPeakAmplitude) {
653            mPeakAmplitude = peak;
654        }
655
656        for (int i = 0; i < numFrames; i++) {
657            float sample = inputData[i * inputChannelCount];
658
659            float sinOut = sinf(mPhase);
660
661            switch (mState) {
662                case STATE_IMMUNE:
663                case STATE_WAITING_FOR_SIGNAL:
664                    break;
665                case STATE_WAITING_FOR_LOCK:
666                    mSinAccumulator += sample * sinOut;
667                    mCosAccumulator += sample * cosf(mPhase);
668                    mFramesAccumulated++;
669                    // Must be a multiple of the period or the calculation will not be accurate.
670                    if (mFramesAccumulated == mPeriod * 4) {
671                        mPhaseOffset = 0.0;
672                        mMagnitude = calculateMagnitude(&mPhaseOffset);
673                        if (mMagnitude > mThreshold) {
674                            if (fabs(mPreviousPhaseOffset - mPhaseOffset) < 0.001) {
675                                mState = STATE_LOCKED;
676                                //printf("%5d: switch to STATE_LOCKED\n", mFrameCounter);
677                            }
678                            mPreviousPhaseOffset = mPhaseOffset;
679                        }
680                        resetAccumulator();
681                    }
682                    break;
683
684                case STATE_LOCKED: {
685                    // Predict next sine value
686                    float predicted = sinf(mPhase + mPhaseOffset) * mMagnitude;
687                    // printf("    predicted = %f, actual = %f\n", predicted, sample);
688
689                    float diff = predicted - sample;
690                    if (fabs(diff) > mTolerance) {
691                        mGlitchCount++;
692                        //printf("%5d: Got a glitch # %d, predicted = %f, actual = %f\n",
693                        //       mFrameCounter, mGlitchCount, predicted, sample);
694                        mState = STATE_IMMUNE;
695                        //printf("%5d: switch to STATE_IMMUNE\n", mFrameCounter);
696                        mDownCounter = mPeriod;  // Set duration of IMMUNE state.
697                    }
698                } break;
699            }
700
701            // Output sine wave so we can measure it.
702            outputData[i * outputChannelCount] = (sinOut * mOutputAmplitude)
703                    + (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
704            // printf("%5d: sin(%f) = %f, %f\n", i, mPhase, sinOut,  mPhaseIncrement);
705
706            // advance and wrap phase
707            mPhase += mPhaseIncrement;
708            if (mPhase > M_PI) {
709                mPhase -= (2.0 * M_PI);
710            }
711
712            mFrameCounter++;
713        }
714
715        // Do these once per buffer.
716        switch (mState) {
717            case STATE_IMMUNE:
718                mDownCounter -= numFrames;
719                if (mDownCounter <= 0) {
720                    mState = STATE_WAITING_FOR_SIGNAL;
721                    //printf("%5d: switch to STATE_WAITING_FOR_SIGNAL\n", mFrameCounter);
722                }
723                break;
724            case STATE_WAITING_FOR_SIGNAL:
725                if (peak > mThreshold) {
726                    mState = STATE_WAITING_FOR_LOCK;
727                    //printf("%5d: switch to STATE_WAITING_FOR_LOCK\n", mFrameCounter);
728                    resetAccumulator();
729                }
730                break;
731            case STATE_WAITING_FOR_LOCK:
732            case STATE_LOCKED:
733                break;
734        }
735
736    }
737
738    void resetAccumulator() {
739        mFramesAccumulated = 0;
740        mSinAccumulator = 0.0;
741        mCosAccumulator = 0.0;
742    }
743
744    void reset() override {
745        mGlitchCount = 0;
746        mState = STATE_IMMUNE;
747        mPhaseIncrement = 2.0 * M_PI / mPeriod;
748        printf("phaseInc = %f for period %d\n", mPhaseIncrement, mPeriod);
749        resetAccumulator();
750    }
751
752private:
753
754    enum sine_state_t {
755        STATE_IMMUNE,
756        STATE_WAITING_FOR_SIGNAL,
757        STATE_WAITING_FOR_LOCK,
758        STATE_LOCKED
759    };
760
761    int     mPeriod = 79;
762    double  mPhaseIncrement = 0.0;
763    double  mPhase = 0.0;
764    double  mPhaseOffset = 0.0;
765    double  mPreviousPhaseOffset = 0.0;
766    double  mMagnitude = 0.0;
767    double  mThreshold = 0.005;
768    double  mTolerance = 0.01;
769    int32_t mFramesAccumulated = 0;
770    double  mSinAccumulator = 0.0;
771    double  mCosAccumulator = 0.0;
772    int32_t mGlitchCount = 0;
773    double  mPeakAmplitude = 0.0;
774    int     mDownCounter = 4000;
775    int32_t mFrameCounter = 0;
776    float   mOutputAmplitude = 0.75;
777
778    PseudoRandom  mWhiteNoise;
779    float   mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
780
781    sine_state_t  mState = STATE_IMMUNE;
782};
783
784
785#undef LOOPBACK_SAMPLE_RATE
786#undef LOOPBACK_RESULT_TAG
787
788#endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
789