• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 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 package com.mobileer.oboetester;
17 
18 import java.util.ArrayList;
19 
20 /**
21  * Analyze a recording and extract edges for latency analysis.
22  */
23 public class TapLatencyAnalyser {
24     public static final int TYPE_TAP = 0;
25     float[] mHighPassBuffer;
26 
27     private float mDroop = 0.995f;
28     private static final float EDGE_THRESHOLD = 0.01f;
29     private static final float LOW_FRACTION = 0.5f;
30 
31     public static class TapLatencyEvent {
32         public int type;
33         public int sampleIndex;
TapLatencyEvent(int type, int sampleIndex)34         public TapLatencyEvent(int type, int sampleIndex) {
35             this.type = type;
36             this.sampleIndex = sampleIndex;
37         }
38     }
39 
analyze(float[] buffer, int offset, int numSamples)40     public TapLatencyEvent[] analyze(float[] buffer, int offset, int numSamples) {
41         // Use high pass filter to remove rumble from air conditioners.
42         mHighPassBuffer = new float[numSamples];
43         highPassFilter(buffer, offset, numSamples, mHighPassBuffer);
44         // Apply envelope follower.
45         float[] peakBuffer = new float[numSamples];
46         fillPeakBuffer(mHighPassBuffer, 0, numSamples, peakBuffer);
47         // Look for two attacks.
48         return scanForEdges(peakBuffer, numSamples);
49     }
50 
getFilteredBuffer()51     public float[] getFilteredBuffer() {
52         return mHighPassBuffer;
53     }
54 
55     // Based on https://en.wikipedia.org/wiki/High-pass_filter
highPassFilter(float[] buffer, int offset, int numSamples, float[] highPassBuffer)56     private void highPassFilter(float[] buffer, int offset, int numSamples, float[] highPassBuffer) {
57         float xn1 = 0.0f;
58         float yn1 = 0.0f;
59         final float alpha = 0.8f;
60         for (int i = 0; i < numSamples; i++) {
61             float xn = buffer[i + offset];
62             float yn = alpha * (yn1 + xn - xn1);
63             highPassBuffer[i] = yn;
64             xn1 = xn;
65             yn1 = yn;
66         }
67     }
68 
scanForEdges(float[] peakBuffer, int numSamples)69     private TapLatencyEvent[] scanForEdges(float[] peakBuffer, int numSamples) {
70         ArrayList<TapLatencyEvent> events = new ArrayList<TapLatencyEvent>();
71         float slow = 0.0f;
72         float fast = 0.0f;
73         final float slowCoefficient = 0.01f;
74         final float fastCoefficient = 0.10f;
75         float lowThreshold = EDGE_THRESHOLD;
76         boolean armed = true;
77         int sampleIndex = 0;
78         for (float level : peakBuffer) {
79             slow = slow + (level - slow) * slowCoefficient; // low pass filter
80             fast = fast + (level - fast) * fastCoefficient; // low pass filter
81             if (armed && (fast > EDGE_THRESHOLD) && (fast > (2.0 * slow))) {
82                 //System.out.println("edge at " + sampleIndex + ", slow " + slow + ", fast " + fast);
83                 events.add(new TapLatencyEvent(TYPE_TAP, sampleIndex));
84                 armed = false;
85                 lowThreshold = fast * LOW_FRACTION;
86             }
87             // Use hysteresis when rearming.
88             if (!armed && (fast < lowThreshold)) {
89                 armed = true;
90                 // slow = fast; // This seems unnecessary.
91                 //events.add(new TapLatencyEvent(TYPE_TAP, sampleIndex));
92             }
93             sampleIndex++;
94         }
95         return events.toArray(new TapLatencyEvent[0]);
96     }
97 
98     /**
99      * Envelope follower that rides along the peaks of the waveforms
100      * and then decays exponentially.
101      *
102      * @param buffer
103      * @param offset
104      * @param numSamples
105      */
fillPeakBuffer(float[] buffer, int offset, int numSamples, float[] peakBuffer)106     private void fillPeakBuffer(float[] buffer, int offset, int numSamples, float[] peakBuffer) {
107         float previous = 0.0f;
108         for (int i = 0; i < numSamples; i++) {
109             float input = Math.abs(buffer[i + offset]);
110             float output = previous * mDroop;
111             if (input > output) {
112                 output = input;
113             }
114             previous = output;
115             peakBuffer[i] = output;
116         }
117     }
118 }
119