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 
41 
isOutputEnabled()42     virtual bool isOutputEnabled() { return true; }
43 
setMagnitude(double magnitude)44     void setMagnitude(double magnitude) {
45         mMagnitude = magnitude;
46         mScaledTolerance = mMagnitude * mTolerance;
47     }
48 
getPhaseOffset()49     double getPhaseOffset() {
50         return mPhaseOffset;
51     }
52 
getMagnitude()53     double getMagnitude() const {
54         return mMagnitude;
55     }
56 
setInputChannel(int inputChannel)57     void setInputChannel(int inputChannel) {
58         mInputChannel = inputChannel;
59     }
60 
getInputChannel()61     int getInputChannel() const {
62         return mInputChannel;
63     }
64 
setOutputChannel(int outputChannel)65     void setOutputChannel(int outputChannel) {
66         mOutputChannel = outputChannel;
67     }
68 
getOutputChannel()69     int getOutputChannel() const {
70         return mOutputChannel;
71     }
72 
setNoiseAmplitude(double noiseAmplitude)73     void setNoiseAmplitude(double noiseAmplitude) {
74         mNoiseAmplitude = noiseAmplitude;
75     }
76 
getNoiseAmplitude()77     double getNoiseAmplitude() const {
78         return mNoiseAmplitude;
79     }
80 
getTolerance()81     double getTolerance() {
82         return mTolerance;
83     }
84 
setTolerance(double tolerance)85     void setTolerance(double tolerance) {
86         mTolerance = tolerance;
87     }
88 
89     // advance and wrap phase
incrementOutputPhase()90     void incrementOutputPhase() {
91         mOutputPhase += mPhaseIncrement;
92         if (mOutputPhase > M_PI) {
93             mOutputPhase -= (2.0 * M_PI);
94         }
95     }
96 
97     /**
98      * @param frameData upon return, contains the reference sine wave
99      * @param channelCount
100      */
processOutputFrame(float * frameData,int channelCount)101     result_code processOutputFrame(float *frameData, int channelCount) override {
102         float output = 0.0f;
103         // Output sine wave so we can measure it.
104         if (isOutputEnabled()) {
105             float sinOut = sinf(mOutputPhase);
106             incrementOutputPhase();
107             output = (sinOut * mOutputAmplitude)
108                      + (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
109             // ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut,  mPhaseIncrement);
110         }
111         for (int i = 0; i < channelCount; i++) {
112             frameData[i] = (i == mOutputChannel) ? output : 0.0f;
113         }
114         return RESULT_OK;
115     }
116 
117     /**
118      * Calculate the magnitude of the component of the input signal
119      * that matches the analysis frequency.
120      * Also calculate the phase that we can use to create a
121      * signal that matches that component.
122      * The phase will be between -PI and +PI.
123      */
124     double calculateMagnitudePhase(double *phasePtr = nullptr) {
125         if (mFramesAccumulated == 0) {
126             return 0.0;
127         }
128         double sinMean = mSinAccumulator / mFramesAccumulated;
129         double cosMean = mCosAccumulator / mFramesAccumulated;
130         double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean));
131         if (phasePtr != nullptr) {
132             double phase = M_PI_2 - atan2(sinMean, cosMean);
133             *phasePtr = phase;
134         }
135         return magnitude;
136     }
137 
transformSample(float sample,float referencePhase)138     bool transformSample(float sample, float referencePhase) {
139         // Track incoming signal and slowly adjust magnitude to account
140         // for drift in the DRC or AGC.
141         mSinAccumulator += sample * sinf(referencePhase);
142         mCosAccumulator += sample * cosf(referencePhase);
143         mFramesAccumulated++;
144         // Must be a multiple of the period or the calculation will not be accurate.
145         if (mFramesAccumulated == mSinePeriod) {
146             const double coefficient = 0.1;
147             double magnitude = calculateMagnitudePhase(&mPhaseOffset);
148             // One pole averaging filter.
149             setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
150             return true;
151         } else {
152             return false;
153         }
154     }
155 
156     // reset the sine wave detector
resetAccumulator()157     virtual void resetAccumulator() {
158         mFramesAccumulated = 0;
159         mSinAccumulator = 0.0;
160         mCosAccumulator = 0.0;
161     }
162 
reset()163     void reset() override {
164         LoopbackProcessor::reset();
165         resetAccumulator();
166     }
167 
prepareToTest()168     void prepareToTest() override {
169         LoopbackProcessor::prepareToTest();
170         mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
171         mOutputPhase = 0.0f;
172         mInverseSinePeriod = 1.0 / mSinePeriod;
173         mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
174     }
175 
176 protected:
177     static constexpr int32_t kTargetGlitchFrequency = 1000;
178 
179     int32_t mSinePeriod = 1; // this will be set before use
180     double  mInverseSinePeriod = 1.0;
181     double  mPhaseIncrement = 0.0;
182     double  mOutputPhase = 0.0;
183     double  mOutputAmplitude = 0.75;
184     // If this jumps around then we are probably just hearing noise.
185     double  mPhaseOffset = 0.0;
186     double  mMagnitude = 0.0;
187     int32_t mFramesAccumulated = 0;
188     double  mSinAccumulator = 0.0;
189     double  mCosAccumulator = 0.0;
190     double  mScaledTolerance = 0.0;
191 
192     InfiniteRecording<float> mInfiniteRecording;
193 
194 private:
195     int32_t mInputChannel = 0;
196     int32_t mOutputChannel = 0;
197     float   mTolerance = 0.10; // scaled from 0.0 to 1.0
198 
199     float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
200     PseudoRandom  mWhiteNoise;
201 };
202 
203 #endif //ANALYZER_BASE_SINE_ANALYZER_H
204