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