From 26d038e35dad05dec7c95e87cdaaa1d29455f8f6 Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Mon, 14 Apr 2014 22:25:23 -0700 Subject: Add a visualizer! --- src/com/pheelicks/visualizer/AudioData.java | 19 ++ src/com/pheelicks/visualizer/FFTData.java | 19 ++ src/com/pheelicks/visualizer/VisualizerView.java | 304 +++++++++++++++++++++ .../visualizer/renderer/BarGraphRenderer.java | 72 +++++ .../visualizer/renderer/CircleBarRenderer.java | 127 +++++++++ .../visualizer/renderer/CircleRenderer.java | 116 ++++++++ .../visualizer/renderer/LineRenderer.java | 110 ++++++++ .../pheelicks/visualizer/renderer/Renderer.java | 78 ++++++ src/org/cyanogenmod/audiofx/ActivityMusic.java | 36 +-- 9 files changed, 865 insertions(+), 16 deletions(-) create mode 100644 src/com/pheelicks/visualizer/AudioData.java create mode 100644 src/com/pheelicks/visualizer/FFTData.java create mode 100644 src/com/pheelicks/visualizer/VisualizerView.java create mode 100644 src/com/pheelicks/visualizer/renderer/BarGraphRenderer.java create mode 100644 src/com/pheelicks/visualizer/renderer/CircleBarRenderer.java create mode 100644 src/com/pheelicks/visualizer/renderer/CircleRenderer.java create mode 100644 src/com/pheelicks/visualizer/renderer/LineRenderer.java create mode 100644 src/com/pheelicks/visualizer/renderer/Renderer.java (limited to 'src') diff --git a/src/com/pheelicks/visualizer/AudioData.java b/src/com/pheelicks/visualizer/AudioData.java new file mode 100644 index 0000000..6c1a5f8 --- /dev/null +++ b/src/com/pheelicks/visualizer/AudioData.java @@ -0,0 +1,19 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer; + +// Data class to explicitly indicate that these bytes are raw audio data +public class AudioData +{ + public AudioData(byte[] bytes) + { + this.bytes = bytes; + } + + public byte[] bytes; +} diff --git a/src/com/pheelicks/visualizer/FFTData.java b/src/com/pheelicks/visualizer/FFTData.java new file mode 100644 index 0000000..66aac74 --- /dev/null +++ b/src/com/pheelicks/visualizer/FFTData.java @@ -0,0 +1,19 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer; + +// Data class to explicitly indicate that these bytes are the FFT of audio data +public class FFTData +{ + public FFTData(byte[] bytes) + { + this.bytes = bytes; + } + + public byte[] bytes; +} diff --git a/src/com/pheelicks/visualizer/VisualizerView.java b/src/com/pheelicks/visualizer/VisualizerView.java new file mode 100644 index 0000000..7da1cd2 --- /dev/null +++ b/src/com/pheelicks/visualizer/VisualizerView.java @@ -0,0 +1,304 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.media.audiofx.Visualizer; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.pheelicks.visualizer.renderer.BarGraphRenderer; +import com.pheelicks.visualizer.renderer.CircleBarRenderer; +import com.pheelicks.visualizer.renderer.CircleRenderer; +import com.pheelicks.visualizer.renderer.LineRenderer; +import com.pheelicks.visualizer.renderer.Renderer; + +import java.util.HashSet; +import java.util.Set; + +/** + * A class that draws visualizations of data received from a + * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } and + * {@link Visualizer.OnDataCaptureListener#onFftDataCapture } + */ +public class VisualizerView extends View { + + private static final String TAG = "VisualizerView"; + + private byte[] mBytes; + private byte[] mFFTBytes; + private Rect mRect = new Rect(); + private Visualizer mVisualizer; + private int mAudioSessionId; + + private Set mRenderers; + + private Paint mFlashPaint = new Paint(); + private Paint mFadePaint = new Paint(); + + public VisualizerView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs); + init(); + } + + public VisualizerView(Context context, AttributeSet attrs) + { + this(context, attrs, 0); + } + + public VisualizerView(Context context) + { + this(context, null, 0); + } + + private void init() { + mBytes = null; + mFFTBytes = null; + + mFlashPaint.setColor(Color.argb(122, 255, 255, 255)); + mFadePaint.setColor(Color.argb(238, 255, 255, 255)); // Adjust alpha to + // change how + // quickly the + // image fades + mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY)); + + mRenderers = new HashSet(); + } + + /** + * Links the visualizer to a player + * + * @param player - MediaPlayer instance to link to + */ + public void link(int audioSessionId) + { + if (mVisualizer != null && audioSessionId != mAudioSessionId) { + mVisualizer.setEnabled(false); + mVisualizer.release(); + mVisualizer = null; + } + + Log.i(TAG, "session=" + audioSessionId); + mAudioSessionId = audioSessionId; + + if (mVisualizer == null) { + + // Create the Visualizer object and attach it to our media player. + mVisualizer = new Visualizer(audioSessionId); + mVisualizer.setEnabled(false); + mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]); + + // Pass through Visualizer data to VisualizerView + Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener() + { + @Override + public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, + int samplingRate) + { + updateVisualizer(bytes); + } + + @Override + public void onFftDataCapture(Visualizer visualizer, byte[] bytes, + int samplingRate) + { + updateVisualizerFFT(bytes); + } + }; + + mVisualizer.setDataCaptureListener(captureListener, + (int) (Visualizer.getMaxCaptureRate() * 0.75), true, true); + + } + mVisualizer.setEnabled(true); + + } + + public void unlink() { + if (mVisualizer != null) { + mVisualizer.setEnabled(false); + mVisualizer.release(); + mVisualizer = null; + } + } + + public void addRenderer(Renderer renderer) + { + if (renderer != null) + { + mRenderers.add(renderer); + } + } + + public void clearRenderers() + { + mRenderers.clear(); + } + + /** + * Call to release the resources used by VisualizerView. Like with the + * MediaPlayer it is good practice to call this method + */ + public void release() + { + mVisualizer.release(); + } + + /** + * Pass data to the visualizer. Typically this will be obtained from the + * Android Visualizer.OnDataCaptureListener call back. See + * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } + * + * @param bytes + */ + public void updateVisualizer(byte[] bytes) { + mBytes = bytes; + invalidate(); + } + + /** + * Pass FFT data to the visualizer. Typically this will be obtained from the + * Android Visualizer.OnDataCaptureListener call back. See + * {@link Visualizer.OnDataCaptureListener#onFftDataCapture } + * + * @param bytes + */ + public void updateVisualizerFFT(byte[] bytes) { + mFFTBytes = bytes; + invalidate(); + } + + boolean mFlash = false; + + /** + * Call this to make the visualizer flash. Useful for flashing at the start + * of a song/loop etc... + */ + public void flash() { + mFlash = true; + invalidate(); + } + + Bitmap mCanvasBitmap; + Canvas mCanvas; + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Create canvas once we're ready to draw + mRect.set(0, 0, getWidth(), getHeight()); + + if (mCanvasBitmap == null) + { + mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), + Config.ARGB_8888); + } + if (mCanvas == null) + { + mCanvas = new Canvas(mCanvasBitmap); + } + + if (mBytes != null) { + // Render all audio renderers + AudioData audioData = new AudioData(mBytes); + for (Renderer r : mRenderers) + { + r.render(mCanvas, audioData, mRect); + } + } + + if (mFFTBytes != null) { + // Render all FFT renderers + FFTData fftData = new FFTData(mFFTBytes); + for (Renderer r : mRenderers) + { + r.render(mCanvas, fftData, mRect); + } + } + + // Fade out old contents + mCanvas.drawPaint(mFadePaint); + + if (mFlash) + { + mFlash = false; + mCanvas.drawPaint(mFlashPaint); + } + + canvas.drawBitmap(mCanvasBitmap, new Matrix(), null); + } + + // Methods for adding renderers to visualizer + public void addBarGraphRendererBottom() + { + Paint paint = new Paint(); + paint.setStrokeWidth(50f); + paint.setAntiAlias(true); + paint.setColor(Color.argb(200, 56, 138, 252)); + BarGraphRenderer barGraphRendererBottom = new BarGraphRenderer(16, paint, false); + addRenderer(barGraphRendererBottom); + } + + public void addBarGraphRendererTop() { + Paint paint2 = new Paint(); + paint2.setStrokeWidth(12f); + paint2.setAntiAlias(true); + paint2.setColor(Color.argb(200, 181, 111, 233)); + BarGraphRenderer barGraphRendererTop = new BarGraphRenderer(4, paint2, true); + addRenderer(barGraphRendererTop); + } + + public void addCircleBarRenderer() + { + Paint paint = new Paint(); + paint.setStrokeWidth(8f); + paint.setAntiAlias(true); + paint.setXfermode(new PorterDuffXfermode(Mode.LIGHTEN)); + paint.setColor(Color.argb(255, 222, 92, 143)); + CircleBarRenderer circleBarRenderer = new CircleBarRenderer(paint, 32, true); + addRenderer(circleBarRenderer); + } + + public void addCircleRenderer() + { + Paint paint = new Paint(); + paint.setStrokeWidth(3f); + paint.setAntiAlias(true); + paint.setColor(Color.argb(255, 222, 92, 143)); + CircleRenderer circleRenderer = new CircleRenderer(paint, true); + addRenderer(circleRenderer); + } + + public void addLineRenderer() + { + Paint linePaint = new Paint(); + linePaint.setStrokeWidth(1f); + linePaint.setAntiAlias(true); + linePaint.setColor(Color.argb(88, 0, 128, 255)); + + Paint lineFlashPaint = new Paint(); + lineFlashPaint.setStrokeWidth(5f); + lineFlashPaint.setAntiAlias(true); + lineFlashPaint.setColor(Color.argb(188, 255, 255, 255)); + LineRenderer lineRenderer = new LineRenderer(linePaint, lineFlashPaint, true); + addRenderer(lineRenderer); + } + +} diff --git a/src/com/pheelicks/visualizer/renderer/BarGraphRenderer.java b/src/com/pheelicks/visualizer/renderer/BarGraphRenderer.java new file mode 100644 index 0000000..fefd36c --- /dev/null +++ b/src/com/pheelicks/visualizer/renderer/BarGraphRenderer.java @@ -0,0 +1,72 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer.renderer; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.pheelicks.visualizer.AudioData; +import com.pheelicks.visualizer.FFTData; + +public class BarGraphRenderer extends Renderer +{ + private int mDivisions; + private Paint mPaint; + private boolean mTop; + + /** + * Renders the FFT data as a series of lines, in histogram form + * + * @param divisions - must be a power of 2. Controls how many lines to draw + * @param paint - Paint to draw lines with + * @param top - whether to draw the lines at the top of the canvas, or the + * bottom + */ + public BarGraphRenderer(int divisions, + Paint paint, + boolean top) + { + super(); + mDivisions = divisions; + mPaint = paint; + mTop = top; + } + + @Override + public void onRender(Canvas canvas, AudioData data, Rect rect) + { + // Do nothing, we only display FFT data + } + + @Override + public void onRender(Canvas canvas, FFTData data, Rect rect) + { + for (int i = 0; i < data.bytes.length / mDivisions; i++) { + mFFTPoints[i * 4] = i * 4 * mDivisions; + mFFTPoints[i * 4 + 2] = i * 4 * mDivisions; + byte rfk = data.bytes[mDivisions * i]; + byte ifk = data.bytes[mDivisions * i + 1]; + float magnitude = (rfk * rfk + ifk * ifk); + int dbValue = (int) (10 * Math.log10(magnitude)); + + if (mTop) + { + mFFTPoints[i * 4 + 1] = 0; + mFFTPoints[i * 4 + 3] = (dbValue * 2 - 10); + } + else + { + mFFTPoints[i * 4 + 1] = rect.height(); + mFFTPoints[i * 4 + 3] = rect.height() - (dbValue * 2 - 10); + } + } + + canvas.drawLines(mFFTPoints, mPaint); + } +} diff --git a/src/com/pheelicks/visualizer/renderer/CircleBarRenderer.java b/src/com/pheelicks/visualizer/renderer/CircleBarRenderer.java new file mode 100644 index 0000000..6d4e47f --- /dev/null +++ b/src/com/pheelicks/visualizer/renderer/CircleBarRenderer.java @@ -0,0 +1,127 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer.renderer; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.pheelicks.visualizer.AudioData; +import com.pheelicks.visualizer.FFTData; + +public class CircleBarRenderer extends Renderer +{ + private int mDivisions; + private Paint mPaint; + private boolean mCycleColor; + + /** + * Renders the FFT data onto a pulsing, rotating circle + * + * @param canvas + * @param paint - Paint to draw lines with + */ + public CircleBarRenderer(Paint paint, int divisions) + { + this(paint, divisions, false); + } + + /** + * Renders the audio data onto a pulsing circle + * + * @param canvas + * @param paint - Paint to draw lines with + * @param divisions - must be a power of 2. Controls how many lines to draw + * @param cycleColor - If true the color will change on each frame + */ + public CircleBarRenderer(Paint paint, int divisions, boolean cycleColor) + { + super(); + mPaint = paint; + mDivisions = divisions; + mCycleColor = cycleColor; + } + + @Override + public void onRender(Canvas canvas, AudioData data, Rect rect) + { + // Do nothing, we only display FFT data + } + + @Override + public void onRender(Canvas canvas, FFTData data, Rect rect) + { + if (mCycleColor) + { + cycleColor(); + } + + for (int i = 0; i < data.bytes.length / mDivisions; i++) { + // Calculate dbValue + byte rfk = data.bytes[mDivisions * i]; + byte ifk = data.bytes[mDivisions * i + 1]; + float magnitude = (rfk * rfk + ifk * ifk); + float dbValue = 75 * (float) Math.log10(magnitude); + + float[] cartPoint = { + (float) (i * mDivisions) / (data.bytes.length - 1), + rect.height() / 2 - dbValue / 4 + }; + + float[] polarPoint = toPolar(cartPoint, rect); + mFFTPoints[i * 4] = polarPoint[0]; + mFFTPoints[i * 4 + 1] = polarPoint[1]; + + float[] cartPoint2 = { + (float) (i * mDivisions) / (data.bytes.length - 1), + rect.height() / 2 + dbValue + }; + + float[] polarPoint2 = toPolar(cartPoint2, rect); + mFFTPoints[i * 4 + 2] = polarPoint2[0]; + mFFTPoints[i * 4 + 3] = polarPoint2[1]; + } + + canvas.drawLines(mFFTPoints, mPaint); + + // Controls the pulsing rate + modulation += 0.13; + angleModulation += 0.28; + } + + float modulation = 0; + float modulationStrength = 0.4f; // 0-1 + float angleModulation = 0; + float aggresive = 0.4f; + + private float[] toPolar(float[] cartesian, Rect rect) + { + double cX = rect.width() / 2; + double cY = rect.height() / 2; + double angle = (cartesian[0]) * 2 * Math.PI; + double radius = ((rect.width() / 2) * (1 - aggresive) + aggresive * cartesian[1] / 2) + * ((1 - modulationStrength) + modulationStrength * (1 + Math.sin(modulation)) / 2); + float[] out = { + (float) (cX + radius * Math.sin(angle + angleModulation)), + (float) (cY + radius * Math.cos(angle + angleModulation)) + }; + return out; + } + + private float colorCounter = 0; + + private void cycleColor() + { + int r = (int) Math.floor(128 * (Math.sin(colorCounter) + 1)); + int g = (int) Math.floor(128 * (Math.sin(colorCounter + 2) + 1)); + int b = (int) Math.floor(128 * (Math.sin(colorCounter + 4) + 1)); + mPaint.setColor(Color.argb(128, r, g, b)); + colorCounter += 0.03; + } +} diff --git a/src/com/pheelicks/visualizer/renderer/CircleRenderer.java b/src/com/pheelicks/visualizer/renderer/CircleRenderer.java new file mode 100644 index 0000000..722ce09 --- /dev/null +++ b/src/com/pheelicks/visualizer/renderer/CircleRenderer.java @@ -0,0 +1,116 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer.renderer; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.pheelicks.visualizer.AudioData; +import com.pheelicks.visualizer.FFTData; + +public class CircleRenderer extends Renderer +{ + private Paint mPaint; + private boolean mCycleColor; + + /** + * Renders the audio data onto a pulsing circle + * + * @param canvas + * @param paint - Paint to draw lines with + */ + public CircleRenderer(Paint paint) + { + this(paint, false); + } + + /** + * Renders the audio data onto a pulsing circle + * + * @param canvas + * @param paint - Paint to draw lines with + * @param cycleColor - If true the color will change on each frame + */ + public CircleRenderer(Paint paint, boolean cycleColor) + { + super(); + mPaint = paint; + mCycleColor = cycleColor; + } + + @Override + public void onRender(Canvas canvas, AudioData data, Rect rect) + { + if (mCycleColor) + { + cycleColor(); + } + + for (int i = 0; i < data.bytes.length - 1; i++) { + float[] cartPoint = { + (float) i / (data.bytes.length - 1), + rect.height() / 2 + ((byte) (data.bytes[i] + 128)) * (rect.height() / 2) / 128 + }; + + float[] polarPoint = toPolar(cartPoint, rect); + mPoints[i * 4] = polarPoint[0]; + mPoints[i * 4 + 1] = polarPoint[1]; + + float[] cartPoint2 = { + (float) (i + 1) / (data.bytes.length - 1), + rect.height() / 2 + ((byte) (data.bytes[i + 1] + 128)) * (rect.height() / 2) + / 128 + }; + + float[] polarPoint2 = toPolar(cartPoint2, rect); + mPoints[i * 4 + 2] = polarPoint2[0]; + mPoints[i * 4 + 3] = polarPoint2[1]; + } + + canvas.drawLines(mPoints, mPaint); + + // Controls the pulsing rate + modulation += 0.04; + } + + @Override + public void onRender(Canvas canvas, FFTData data, Rect rect) + { + // Do nothing, we only display audio data + } + + float modulation = 0; + float aggresive = 0.33f; + + private float[] toPolar(float[] cartesian, Rect rect) + { + double cX = rect.width() / 2; + double cY = rect.height() / 2; + double angle = (cartesian[0]) * 2 * Math.PI; + double radius = ((rect.width() / 2) * (1 - aggresive) + aggresive * cartesian[1] / 2) + * (1.2 + Math.sin(modulation)) / 2.2; + float[] out = { + (float) (cX + radius * Math.sin(angle)), + (float) (cY + radius * Math.cos(angle)) + }; + return out; + } + + private float colorCounter = 0; + + private void cycleColor() + { + int r = (int) Math.floor(128 * (Math.sin(colorCounter) + 1)); + int g = (int) Math.floor(128 * (Math.sin(colorCounter + 2) + 1)); + int b = (int) Math.floor(128 * (Math.sin(colorCounter + 4) + 1)); + mPaint.setColor(Color.argb(128, r, g, b)); + colorCounter += 0.03; + } +} diff --git a/src/com/pheelicks/visualizer/renderer/LineRenderer.java b/src/com/pheelicks/visualizer/renderer/LineRenderer.java new file mode 100644 index 0000000..0645a84 --- /dev/null +++ b/src/com/pheelicks/visualizer/renderer/LineRenderer.java @@ -0,0 +1,110 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer.renderer; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.pheelicks.visualizer.AudioData; +import com.pheelicks.visualizer.FFTData; + +public class LineRenderer extends Renderer +{ + private Paint mPaint; + private Paint mFlashPaint; + private boolean mCycleColor; + private float amplitude = 0; + + /** + * Renders the audio data onto a line. The line flashes on prominent beats + * + * @param canvas + * @param paint - Paint to draw lines with + * @param paint - Paint to draw flash with + */ + public LineRenderer(Paint paint, Paint flashPaint) + { + this(paint, flashPaint, false); + } + + /** + * Renders the audio data onto a line. The line flashes on prominent beats + * + * @param canvas + * @param paint - Paint to draw lines with + * @param paint - Paint to draw flash with + * @param cycleColor - If true the color will change on each frame + */ + public LineRenderer(Paint paint, + Paint flashPaint, + boolean cycleColor) + { + super(); + mPaint = paint; + mFlashPaint = flashPaint; + mCycleColor = cycleColor; + } + + @Override + public void onRender(Canvas canvas, AudioData data, Rect rect) + { + if (mCycleColor) + { + cycleColor(); + } + + // Calculate points for line + for (int i = 0; i < data.bytes.length - 1; i++) { + mPoints[i * 4] = rect.width() * i / (data.bytes.length - 1); + mPoints[i * 4 + 1] = rect.height() / 2 + + ((byte) (data.bytes[i] + 128)) * (rect.height() / 3) / 128; + mPoints[i * 4 + 2] = rect.width() * (i + 1) / (data.bytes.length - 1); + mPoints[i * 4 + 3] = rect.height() / 2 + + ((byte) (data.bytes[i + 1] + 128)) * (rect.height() / 3) / 128; + } + + // Calc amplitude for this waveform + float accumulator = 0; + for (int i = 0; i < data.bytes.length - 1; i++) { + accumulator += Math.abs(data.bytes[i]); + } + + float amp = accumulator / (128 * data.bytes.length); + if (amp > amplitude) + { + // Amplitude is bigger than normal, make a prominent line + amplitude = amp; + canvas.drawLines(mPoints, mFlashPaint); + } + else + { + // Amplitude is nothing special, reduce the amplitude + amplitude *= 0.99; + canvas.drawLines(mPoints, mPaint); + } + } + + @Override + public void onRender(Canvas canvas, FFTData data, Rect rect) + { + // Do nothing, we only display audio data + } + + private float colorCounter = 0; + + private void cycleColor() + { + int r = (int) Math.floor(128 * (Math.sin(colorCounter) + 3)); + int g = (int) Math.floor(128 * (Math.sin(colorCounter + 1) + 1)); + int b = (int) Math.floor(128 * (Math.sin(colorCounter + 7) + 1)); + mPaint.setColor(Color.argb(128, r, g, b)); + colorCounter += 0.03; + } +} diff --git a/src/com/pheelicks/visualizer/renderer/Renderer.java b/src/com/pheelicks/visualizer/renderer/Renderer.java new file mode 100644 index 0000000..2d18d55 --- /dev/null +++ b/src/com/pheelicks/visualizer/renderer/Renderer.java @@ -0,0 +1,78 @@ +/** + * Copyright 2011, Felix Palmer + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + */ + +package com.pheelicks.visualizer.renderer; + +import android.graphics.Canvas; +import android.graphics.Rect; + +import com.pheelicks.visualizer.AudioData; +import com.pheelicks.visualizer.FFTData; + +abstract public class Renderer +{ + // Have these as members, so we don't have to re-create them each time + protected float[] mPoints; + protected float[] mFFTPoints; + + public Renderer() + { + } + + // As the display of raw/FFT audio will usually look different, subclasses + // will typically only implement one of the below methods + /** + * Implement this method to render the audio data onto the canvas + * + * @param canvas - Canvas to draw on + * @param data - Data to render + * @param rect - Rect to render into + */ + abstract public void onRender(Canvas canvas, AudioData data, Rect rect); + + /** + * Implement this method to render the FFT audio data onto the canvas + * + * @param canvas - Canvas to draw on + * @param data - Data to render + * @param rect - Rect to render into + */ + abstract public void onRender(Canvas canvas, FFTData data, Rect rect); + + // These methods should actually be called for rendering + /** + * Render the audio data onto the canvas + * + * @param canvas - Canvas to draw on + * @param data - Data to render + * @param rect - Rect to render into + */ + final public void render(Canvas canvas, AudioData data, Rect rect) + { + if (mPoints == null || mPoints.length < data.bytes.length * 4) { + mPoints = new float[data.bytes.length * 4]; + } + + onRender(canvas, data, rect); + } + + /** + * Render the FFT data onto the canvas + * + * @param canvas - Canvas to draw on + * @param data - Data to render + * @param rect - Rect to render into + */ + final public void render(Canvas canvas, FFTData data, Rect rect) + { + if (mFFTPoints == null || mFFTPoints.length < data.bytes.length * 4) { + mFFTPoints = new float[data.bytes.length * 4]; + } + + onRender(canvas, data, rect); + } +} diff --git a/src/org/cyanogenmod/audiofx/ActivityMusic.java b/src/org/cyanogenmod/audiofx/ActivityMusic.java index d343186..a2d1e8e 100644 --- a/src/org/cyanogenmod/audiofx/ActivityMusic.java +++ b/src/org/cyanogenmod/audiofx/ActivityMusic.java @@ -16,36 +16,25 @@ package org.cyanogenmod.audiofx; -import org.cyanogenmod.audiofx.widget.EqualizerSurface; -import org.cyanogenmod.audiofx.widget.Gallery; -import org.cyanogenmod.audiofx.widget.InterceptableLinearLayout; -import org.cyanogenmod.audiofx.widget.Knob; -import org.cyanogenmod.audiofx.widget.Knob.OnKnobChangeListener; - import android.app.ActionBar; import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Color; +import android.graphics.Paint; import android.media.AudioManager; import android.media.audiofx.AudioEffect; import android.media.audiofx.AudioEffect.Descriptor; import android.os.Bundle; import android.util.Log; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; -import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.Window; import android.widget.AdapterView; @@ -54,14 +43,20 @@ import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; -import android.widget.ToggleButton; -import android.util.DisplayMetrics; + +import com.pheelicks.visualizer.VisualizerView; +import com.pheelicks.visualizer.renderer.LineRenderer; + +import org.cyanogenmod.audiofx.widget.EqualizerSurface; +import org.cyanogenmod.audiofx.widget.Gallery; +import org.cyanogenmod.audiofx.widget.InterceptableLinearLayout; +import org.cyanogenmod.audiofx.widget.Knob; +import org.cyanogenmod.audiofx.widget.Knob.OnKnobChangeListener; import java.util.Formatter; import java.util.Locale; @@ -118,6 +113,8 @@ public class ActivityMusic extends Activity { private StringBuilder mFormatBuilder = new StringBuilder(); private Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); + private VisualizerView mVisualizer; + // Preset Reverb fields /** * Array containing RSid of preset reverb names. @@ -261,6 +258,9 @@ public class ActivityMusic extends Activity { mReverbPresetNames[i] = getString(mReverbPresetRSids[i]); } + mVisualizer = (VisualizerView) findViewById(R.id.visualizerView); + mVisualizer.addLineRenderer(); + // Watch for button clicks and initialization. if ((mVirtualizerSupported) || (mBassBoostSupported) || (mEqualizerSupported) || (mPresetReverbSupported)) { @@ -451,6 +451,8 @@ public class ActivityMusic extends Activity { // Unregister for broadcast intents. (These affect the visible UI, // so we only care about them while we're in the foreground.) unregisterReceiver(mReceiver); + + mVisualizer.unlink(); } private void reverbSpinnerInit(Spinner spinner) { @@ -550,6 +552,8 @@ public class ActivityMusic extends Activity { ((Spinner)findViewById(R.id.prSpinner)).setSelection(reverb); } + mVisualizer.link(mAudioSession); + setInterception(isEnabled); } -- cgit v1.2.3