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.google.sample.oboe.manualtest; 17 18 import java.util.ArrayList; 19 20 public class TapLatencyAnalyser { 21 public static final int TYPE_TAP = 0; 22 float[] mHighPassBuffer; 23 24 private float mDroop = 0.995f; 25 private static float LOW_THRESHOLD = 0.01f; 26 private static float HIGH_THRESHOLD = 0.03f; 27 28 public static class TapLatencyEvent { 29 public int type; 30 public int sampleIndex; TapLatencyEvent(int type, int sampleIndex)31 public TapLatencyEvent(int type, int sampleIndex) { 32 this.type = type; 33 this.sampleIndex = sampleIndex; 34 } 35 } 36 analyze(float[] buffer, int offset, int numSamples)37 public TapLatencyEvent[] analyze(float[] buffer, int offset, int numSamples) { 38 // Use high pass filter to remove rumble from air conditioners. 39 mHighPassBuffer = new float[numSamples]; 40 highPassFilter(buffer, offset, numSamples, mHighPassBuffer); 41 float[] peakBuffer = new float[numSamples]; 42 fillPeakBuffer(mHighPassBuffer, 0, numSamples, peakBuffer); 43 return scanForEdges(peakBuffer, numSamples); 44 } 45 getFilteredBuffer()46 public float[] getFilteredBuffer() { 47 return mHighPassBuffer; 48 } 49 highPassFilter(float[] buffer, int offset, int numSamples, float[] highPassBuffer)50 private void highPassFilter(float[] buffer, int offset, int numSamples, float[] highPassBuffer) { 51 float xn1 = 0.0f; 52 float yn1 = 0.0f; 53 final float alpha = 0.05f; 54 for (int i = 0; i < numSamples; i++) { 55 float xn = buffer[i + offset]; 56 float yn = alpha * yn1 + ((1.0f - alpha) * (xn - xn1)); 57 highPassBuffer[i] = yn; 58 xn1 = xn; 59 yn1 = yn; 60 } 61 } 62 scanForEdges(float[] peakBuffer, int numSamples)63 private TapLatencyEvent[] scanForEdges(float[] peakBuffer, int numSamples) { 64 ArrayList<TapLatencyEvent> events = new ArrayList<TapLatencyEvent>(); 65 float slow = 0.0f; 66 float fast = 0.0f; 67 float slowCoefficient = 0.01f; 68 float fastCoefficient = 0.10f; 69 boolean armed = true; 70 int sampleIndex = 0; 71 for (float level : peakBuffer) { 72 slow = slow + (level - slow) * slowCoefficient; // low pass filter 73 fast = fast + (level - fast) * fastCoefficient; 74 if (armed && (fast > HIGH_THRESHOLD) && (fast > (2.0 * slow))) { 75 //System.out.println("edge at " + sampleIndex + ", slow " + slow + ", fast " + fast); 76 events.add(new TapLatencyEvent(TYPE_TAP, sampleIndex)); 77 armed = false; 78 } 79 // Use hysteresis when rearming. 80 if (!armed && (fast < LOW_THRESHOLD)) { 81 armed = true; 82 } 83 sampleIndex++; 84 } 85 return events.toArray(new TapLatencyEvent[0]); 86 } 87 fillPeakBuffer(float[] buffer, int offset, int numSamples, float[] peakBuffer)88 private void fillPeakBuffer(float[] buffer, int offset, int numSamples, float[] peakBuffer) { 89 float previous = 0.0f; 90 for (int i = 0; i < numSamples; i++) { 91 float input = buffer[i + offset]; 92 float output = previous * mDroop; 93 if (input > output) { 94 output = input; 95 } 96 previous = output; 97 peakBuffer[i] = output; 98 } 99 } 100 } 101