xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright (C) 2020 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 #ifndef ANALYZER_BASE_SINE_ANALYZER_H
18 #define ANALYZER_BASE_SINE_ANALYZER_H
19 
20 #include <algorithm>
21 #include <cctype>
22 #include <iomanip>
23 #include <iostream>
24 
25 #include "InfiniteRecording.h"
26 #include "LatencyAnalyzer.h"
27 
28 /**
29  * Output a steady sine wave and analyze the return signal.
30  *
31  * Use a cosine transform to measure the predicted magnitude and relative phase of the
32  * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
33  */
34 class BaseSineAnalyzer : public LoopbackProcessor {
35 public:
36 
BaseSineAnalyzer()37     BaseSineAnalyzer()
38             : LoopbackProcessor()
39             , mInfiniteRecording(64 * 1024) {}
40 
isOutputEnabled()41     virtual bool isOutputEnabled() { return true; }
42 
setMagnitude(double magnitude)43     void setMagnitude(double magnitude) {
44         mMagnitude = magnitude;
45         mScaledTolerance = mMagnitude * getTolerance();
46     }
47 
48     /**
49      *
50      * @return valid phase or kPhaseInvalid=-999
51      */
getPhaseOffset()52     double getPhaseOffset() {
53         ALOGD("%s(), mPhaseOffset = %f\n", __func__, mPhaseOffset);
54         return mPhaseOffset;
55     }
56 
getMagnitude()57     double getMagnitude() const {
58         return mMagnitude;
59     }
60 
setInputChannel(int inputChannel)61     void setInputChannel(int inputChannel) {
62         mInputChannel = inputChannel;
63     }
64 
getInputChannel()65     int getInputChannel() const {
66         return mInputChannel;
67     }
68 
setOutputChannel(int outputChannel)69     void setOutputChannel(int outputChannel) {
70         mOutputChannel = outputChannel;
71     }
72 
getOutputChannel()73     int getOutputChannel() const {
74         return mOutputChannel;
75     }
76 
setNoiseAmplitude(double noiseAmplitude)77     void setNoiseAmplitude(double noiseAmplitude) {
78         mNoiseAmplitude = noiseAmplitude;
79     }
80 
getNoiseAmplitude()81     double getNoiseAmplitude() const {
82         return mNoiseAmplitude;
83     }
84 
getTolerance()85     double getTolerance() {
86         return mTolerance;
87     }
88 
setTolerance(double tolerance)89     void setTolerance(double tolerance) {
90         mTolerance = tolerance;
91     }
92 
93     // advance and wrap phase
incrementOutputPhase()94     void incrementOutputPhase() {
95         mOutputPhase += mPhaseIncrement;
96         if (mOutputPhase > M_PI) {
97             mOutputPhase -= (2.0 * M_PI);
98         }
99     }
100 
101     /**
102      * @param frameData upon return, contains the reference sine wave
103      * @param channelCount
104      */
processOutputFrame(float * frameData,int channelCount)105     result_code processOutputFrame(float *frameData, int channelCount) override {
106         float output = 0.0f;
107         // Output sine wave so we can measure it.
108         if (isOutputEnabled()) {
109             float sinOut = sinf(mOutputPhase);
110             incrementOutputPhase();
111             output = (sinOut * mOutputAmplitude)
112                      + (mWhiteNoise.nextRandomDouble() * getNoiseAmplitude());
113             // ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut,  kPhaseIncrement);
114         }
115         for (int i = 0; i < channelCount; i++) {
116             frameData[i] = (i == mOutputChannel) ? output : 0.0f;
117         }
118         return RESULT_OK;
119     }
120 
121     /**
122      * Calculate the magnitude of the component of the input signal
123      * that matches the analysis frequency.
124      * Also calculate the phase that we can use to create a
125      * signal that matches that component.
126      * The phase will be between -PI and +PI.
127      */
128     double calculateMagnitudePhase(double *phasePtr = nullptr) {
129         if (mFramesAccumulated == 0) {
130             return 0.0;
131         }
132         double sinMean = mSinAccumulator / mFramesAccumulated;
133         double cosMean = mCosAccumulator / mFramesAccumulated;
134         double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean));
135         if (phasePtr != nullptr) {
136             double phase;
137             if (magnitude < kMinValidMagnitude) {
138                 phase = kPhaseInvalid;
139                 ALOGD("%s() mag very low! sinMean = %7.5f, cosMean = %7.5f",
140                       __func__, sinMean, cosMean);
141             } else {
142                 phase = atan2(cosMean, sinMean);
143                 if (phase == 0.0) {
144                     ALOGD("%s() phase zero! sinMean = %7.5f, cosMean = %7.5f",
145                           __func__, sinMean, cosMean);
146                 }
147             }
148             *phasePtr = phase;
149         }
150         return magnitude;
151     }
152 
153     /**
154      * Perform sin/cos analysis on each sample.
155      * Measure magnitude and phase on every period.
156      * Updates mPhaseOffset
157      * @param sample
158      * @param referencePhase
159      * @return true if magnitude and phase updated
160      */
transformSample(float sample,float referencePhase)161     bool transformSample(float sample, float referencePhase) {
162         // Track incoming signal and slowly adjust magnitude to account
163         // for drift in the DRC or AGC.
164         mSinAccumulator += static_cast<double>(sample) * sinf(referencePhase);
165         mCosAccumulator += static_cast<double>(sample) * cosf(referencePhase);
166         mFramesAccumulated++;
167         // Must be a multiple of the period or the calculation will not be accurate.
168         if (mFramesAccumulated == mSinePeriod) {
169             const double coefficient = 0.1;
170             double magnitude = calculateMagnitudePhase(&mPhaseOffset);
171 
172             ALOGD("%s(), phaseOffset = %f\n", __func__, mPhaseOffset);
173             if (mPhaseOffset != kPhaseInvalid) {
174                 // One pole averaging filter.
175                 setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
176             }
177             resetAccumulator();
178             return true;
179         } else {
180             return false;
181         }
182     }
183 
184     // reset the sine wave detector
resetAccumulator()185     virtual void resetAccumulator() {
186         mFramesAccumulated = 0;
187         mSinAccumulator = 0.0;
188         mCosAccumulator = 0.0;
189     }
190 
reset()191     void reset() override {
192         LoopbackProcessor::reset();
193         resetAccumulator();
194         mMagnitude = 0.0;
195     }
196 
prepareToTest()197     void prepareToTest() override {
198         LoopbackProcessor::prepareToTest();
199         mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
200         mOutputPhase = 0.0f;
201         mInverseSinePeriod = 1.0 / mSinePeriod;
202         mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
203     }
204 
205 protected:
206     // Try to get a prime period so the waveform plot changes every time.
207     static constexpr int32_t kTargetGlitchFrequency = 48000 / 113;
208 
209     int32_t mSinePeriod = 1; // this will be set before use
210     double  mInverseSinePeriod = 1.0;
211     double  mPhaseIncrement = 0.0;
212     double  mOutputPhase = 0.0;
213     double  mOutputAmplitude = 0.75;
214     // This is the phase offset between the output sine wave and the recorded
215     // signal at the tuned frequency.
216     // If this jumps around then we are probably just hearing noise.
217     // Noise can cause the magnitude to be high but mPhaseOffset will be pretty random.
218     // If we are tracking a sine wave then mPhaseOffset should be consistent.
219     double  mPhaseOffset = 0.0;
220     // kPhaseInvalid indicates that the phase measurement cannot be used.
221     // We were seeing times when a magnitude of zero was causing atan2(s,c) to
222     // return a phase of zero, which looked valid to Java. This is a way of passing
223     // an error code back to Java as a single value to avoid race conditions.
224     static constexpr double kPhaseInvalid = -999.0;
225     double  mMagnitude = 0.0;
226     static constexpr double kMinValidMagnitude = 2.0 / (1 << 16);
227     int32_t mFramesAccumulated = 0;
228     double  mSinAccumulator = 0.0;
229     double  mCosAccumulator = 0.0;
230     double  mScaledTolerance = 0.0;
231 
232     InfiniteRecording<float> mInfiniteRecording;
233 
234 private:
235     int32_t mInputChannel = 0;
236     int32_t mOutputChannel = 0;
237     float   mTolerance = 0.10; // scaled from 0.0 to 1.0
238 
239     float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
240     PseudoRandom  mWhiteNoise;
241 };
242 
243 #endif //ANALYZER_BASE_SINE_ANALYZER_H
244