/* * Copyright (C) 2014 Haruki Hasegawa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.h6ah4i.android.example.openslmediaplayer.app.visualizer; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.util.AttributeSet; public class HQFftVisualizerSurfaceView extends BaseAudioVisualizerSurfaceView { public HQFftVisualizerSurfaceView(Context context) { super(context); } public HQFftVisualizerSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); } private final class RenderThreadWork { float[] mWorkFloatBuffer; FloatColor mLchColor; FloatColor mRchColor; int prevCanvasWidth; public FloatBuffer mNativeFloatBuffer; } @Override protected Object onCreateRenderThreadWorkingObj() { RenderThreadWork work = new RenderThreadWork(); work.mLchColor = new FloatColor(0.0f, 1.0f, 0.0f, 1.0f); work.mRchColor = new FloatColor(0.0f, 1.0f, 1.0f, 1.0f); return work; } @Override protected void onRenderAudioData( GL10 gl, int width, int height, Object workObj, CapturedDataHolder data) { final RenderThreadWork work = (RenderThreadWork) workObj; final int canvasWidth = width; final int canvasHeight = height; final float[] fft = data.mFloatData; // -2, +2: (DC + Fs/2), /2: (Re + Im) final int N = ((fft.length / 2) - 2) / 2 + 2; // 2: (x, y) final int workBufferSize = N * 2; boolean needToUpdateX = false; // prepare working buffer if (work.mWorkFloatBuffer == null || work.mWorkFloatBuffer.length < workBufferSize) { work.mWorkFloatBuffer = new float[workBufferSize]; work.mNativeFloatBuffer = allocateNativeFloatBuffer(workBufferSize); needToUpdateX |= true; } final float[] points = work.mWorkFloatBuffer; final FloatBuffer pointsBuffer = work.mNativeFloatBuffer; // prepare points info buffer for drawing needToUpdateX |= (width != work.prevCanvasWidth); work.prevCanvasWidth = width; if (needToUpdateX) { makeXPointPositionData(N, points); } final int data_range = (N - 1) / 2; final float yrange = data_range * 1.05f; // Lch makeYPointPositionData(fft, N, 0, points); converToFloatBuffer(pointsBuffer, points, (2 * N)); drawFFT(gl, canvasWidth, canvasHeight, pointsBuffer, N, 0, yrange, work.mLchColor); // Rch makeYPointPositionData(fft, N, (N - 1) * 2, points); converToFloatBuffer(pointsBuffer, points, (2 * N)); drawFFT(gl, canvasWidth, canvasHeight, pointsBuffer, N, 1, yrange, work.mRchColor); } // http://forum.processing.org/topic/super-fast-square-root private static final float fastSqrt(float x) { return Float.intBitsToFloat(532483686 + (Float.floatToRawIntBits(x) >> 1)); } private void makeXPointPositionData(int N, float[] points) { final float x_diff = 1.0f / (N - 1); for (int i = 0; i < N; i++) { points[2 * i + 0] = (x_diff * i); } } private static void makeYPointPositionData( float[] fft, int n, int offset, float[] points) { // DC points[2 * 0 + 1] = Math.abs(fft[offset + 0]); // f_1 .. f_(N-1) for (int i = 1; i < (n - 1); i++) { final float re = fft[offset + 2 * i + 0]; final float im = fft[offset + 2 * i + 1]; final float y = fastSqrt((re * re) + (im * im)); points[2 * i + 1] = y; } // fs / 2 points[2 * (n - 1) + 1] = Math.abs(fft[offset + 1]); } private static void drawFFT( GL10 gl, int width, int height, FloatBuffer vertices, int n, int vposition, float yrange, FloatColor color) { gl.glPushMatrix(); // viewport gl.glViewport(0, (height / 2) * (1 - vposition), width, (height / 2)); // X: [0:1], Y: [0:1] gl.glOrthof(0.0f, 1.0f, 0.0f, yrange, -1.0f, 1.0f); gl.glColor4f(color.red, color.green, color.blue, color.alpha); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(2, GL10.GL_FLOAT, (2 * Float.SIZE / 8), vertices); gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, n); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glPopMatrix(); } }