summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSteve Kondik <shade@chemlab.org>2014-04-14 22:25:23 -0700
committerSteve Kondik <shade@chemlab.org>2014-04-14 22:25:23 -0700
commit26d038e35dad05dec7c95e87cdaaa1d29455f8f6 (patch)
tree4b55d9e5cb5efee68d3b060188a1546d894c7fc8 /src
parent9c482684e390b1b085948434248b9c110a75e0ef (diff)
downloadandroid_packages_apps_AudioFX-26d038e35dad05dec7c95e87cdaaa1d29455f8f6.tar.gz
android_packages_apps_AudioFX-26d038e35dad05dec7c95e87cdaaa1d29455f8f6.tar.bz2
android_packages_apps_AudioFX-26d038e35dad05dec7c95e87cdaaa1d29455f8f6.zip
Add a visualizer!
Diffstat (limited to 'src')
-rw-r--r--src/com/pheelicks/visualizer/AudioData.java19
-rw-r--r--src/com/pheelicks/visualizer/FFTData.java19
-rw-r--r--src/com/pheelicks/visualizer/VisualizerView.java304
-rw-r--r--src/com/pheelicks/visualizer/renderer/BarGraphRenderer.java72
-rw-r--r--src/com/pheelicks/visualizer/renderer/CircleBarRenderer.java127
-rw-r--r--src/com/pheelicks/visualizer/renderer/CircleRenderer.java116
-rw-r--r--src/com/pheelicks/visualizer/renderer/LineRenderer.java110
-rw-r--r--src/com/pheelicks/visualizer/renderer/Renderer.java78
-rw-r--r--src/org/cyanogenmod/audiofx/ActivityMusic.java36
9 files changed, 865 insertions, 16 deletions
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<Renderer> 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<Renderer>();
+ }
+
+ /**
+ * 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);
}