diff options
-rw-r--r-- | AndroidManifest.xml | 2 | ||||
-rw-r--r-- | res/drawable/bg.xml | 11 | ||||
-rw-r--r-- | res/layout/main.xml | 11 | ||||
-rw-r--r-- | src/com/pheelicks/visualizer/VisualizerActivity.java | 51 | ||||
-rw-r--r-- | src/com/pheelicks/visualizer/VisualizerView.java | 289 |
5 files changed, 363 insertions, 1 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2adc0e5..f0a66df 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -21,5 +21,7 @@ </intent-filter> </activity> </application> + + <uses-permission android:name="android.permission.RECORD_AUDIO" /> </manifest>
\ No newline at end of file diff --git a/res/drawable/bg.xml b/res/drawable/bg.xml new file mode 100644 index 0000000..3fadd34 --- /dev/null +++ b/res/drawable/bg.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:startColor="#acacac" + android:centerColor="#4f4f4f" + android:endColor="#3f3f3f" + android:angle="-90"> + </gradient> +</shape>
\ No newline at end of file diff --git a/res/layout/main.xml b/res/layout/main.xml index 83899fc..a225cb7 100644 --- a/res/layout/main.xml +++ b/res/layout/main.xml @@ -2,12 +2,21 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:background="@drawable/bg" android:orientation="vertical" > <FrameLayout android:layout_width="fill_parent" android:layout_height="0dp" - android:layout_weight="1" > + android:layout_margin="10dp" + android:layout_weight="1" + android:background="#000" > + + <com.pheelicks.visualizer.VisualizerView + android:id="@+id/visualizerView" + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + </com.pheelicks.visualizer.VisualizerView> </FrameLayout> <LinearLayout diff --git a/src/com/pheelicks/visualizer/VisualizerActivity.java b/src/com/pheelicks/visualizer/VisualizerActivity.java index b56be76..795e90e 100644 --- a/src/com/pheelicks/visualizer/VisualizerActivity.java +++ b/src/com/pheelicks/visualizer/VisualizerActivity.java @@ -2,11 +2,13 @@ package com.pheelicks.visualizer; import android.app.Activity; import android.media.MediaPlayer; +import android.media.audiofx.Visualizer; import android.os.Bundle; import android.view.View; public class VisualizerActivity extends Activity { private MediaPlayer mPlayer; + private Visualizer mVisualizer; /** Called when the activity is first created. */ @Override @@ -16,6 +18,55 @@ public class VisualizerActivity extends Activity { mPlayer = MediaPlayer.create(this, R.raw.test); mPlayer.setLooping(true); + + linkVisualizer(mPlayer); + } + + /** + * Links the visualizer to a player + * TODO Refactor this into visualizer + * @param player + */ + private void linkVisualizer(MediaPlayer player) + { + + final VisualizerView visualizerView = (VisualizerView) findViewById(R.id.visualizerView); + + // Create the Visualizer object and attach it to our media player. + mVisualizer = new Visualizer(player.getAudioSessionId()); + 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) + { + visualizerView.updateVisualizer(bytes); + } + + @Override + public void onFftDataCapture(Visualizer visualizer, byte[] bytes, + int samplingRate) + { + visualizerView.updateVisualizerFFT(bytes); + } + }; + + mVisualizer.setDataCaptureListener(captureListener, + Visualizer.getMaxCaptureRate() / 2, true, true); + + // Enabled Visualizer and disable when we're done with the stream + mVisualizer.setEnabled(true); + mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() + { + @Override + public void onCompletion(MediaPlayer mediaPlayer) + { + mVisualizer.setEnabled(false); + } + }); } public void startPressed(View view) diff --git a/src/com/pheelicks/visualizer/VisualizerView.java b/src/com/pheelicks/visualizer/VisualizerView.java new file mode 100644 index 0000000..94505b6 --- /dev/null +++ b/src/com/pheelicks/visualizer/VisualizerView.java @@ -0,0 +1,289 @@ +package com.pheelicks.visualizer; + +// WARNING!!! This file has more magic numbers in it than you could shake a +// stick at + +import java.util.Random; + +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.os.SystemClock; +import android.util.AttributeSet; +import android.view.View; + +/** + * A class that draws waveform data received from a + * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } + */ +class VisualizerView extends View { + private static final String TAG = "VisualizerView"; + + private byte[] mBytes; + private byte[] mFFTBytes; + private float[] mPoints; + private float[] mFFTPoints; + private Rect mRect = new Rect(); + + private Paint mCirclePaint = new Paint(); + private Paint mLinePaint = new Paint(); + private Paint mFFTLineTopPaint = new Paint(); + private Paint mFFTLineBottomPaint = new Paint(); + private Paint mSpecialLinePaint = new Paint(); + private Paint mProgressLinePaint = new Paint(); + private Paint mFlashPaint = new Paint(); + private Paint mFadePaint = new Paint(); + + final int fft_divis_top = 8; // Set to some factor of 2 to adjust number of FFT bars + final int fft_divis_bottom = 16; // Set to some factor of 2 to adjust number of FFT bars + + // Usual BS of 3 constructors + 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; + + mCirclePaint.setStrokeWidth(3f); + mCirclePaint.setAntiAlias(true); + mCirclePaint.setColor(Color.argb(255, 222, 92, 143)); + + mLinePaint.setStrokeWidth(1f); + mLinePaint.setAntiAlias(true); + mLinePaint.setColor(Color.argb(88, 0, 128, 255)); + + mSpecialLinePaint.setStrokeWidth(5f); + mSpecialLinePaint.setAntiAlias(true); + mSpecialLinePaint.setColor(Color.argb(188, 255, 255, 255)); + + mProgressLinePaint.setStrokeWidth(4f); + mProgressLinePaint.setAntiAlias(true); + mProgressLinePaint.setColor(Color.argb(255, 22, 131, 255)); + + mFFTLineTopPaint.setStrokeWidth(fft_divis_top * 3f); + mFFTLineTopPaint.setAntiAlias(true); + mFFTLineTopPaint.setColor(Color.argb(200, 233, 0, 44)); + + mFFTLineBottomPaint.setStrokeWidth(fft_divis_bottom * 3f); + mFFTLineBottomPaint.setAntiAlias(true); + mFFTLineBottomPaint.setColor(Color.argb(88, 0, 233, 44)); + + 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)); + } + + public void updateVisualizer(byte[] bytes) { + mBytes = bytes; + rotateColours(); + invalidate(); + } + + boolean mFlash = false; + long mFlashTime = 0; + long mFlashPeriod = 4000; + + public void flash() { + mFlash = true; + long now = SystemClock.currentThreadTimeMillis(); + mFlashPeriod = now - mFlashTime; + mFlashTime = now; + invalidate(); + } + + public void updateVisualizerFFT(byte[] bytes) { + mFFTBytes = bytes; + invalidate(); + } + + + float colorCounter = 0; + private void rotateColours() + { + 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)); + mLinePaint.setColor(Color.argb(128, r, g, b)); + mCirclePaint.setColor(Color.argb(255, g, b, r)); + colorCounter += 0.03; + } + + Bitmap mCanvasBitmap; + Canvas mCanvas; + Random mRandom = new Random(); + float amplitude = 0; + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mBytes == null) { + return; + } + + if (mPoints == null || mPoints.length < mBytes.length * 4) { + mPoints = new float[mBytes.length * 4]; + } + + mRect.set(0, 0, getWidth(), getHeight()); + + for (int i = 0; i < mBytes.length - 1; i++) { + float[] cartPoint = { + (float)i / (mBytes.length - 1), + mRect.height() / 2 + ((byte) (mBytes[i] + 128)) * (mRect.height() / 2) / 128 + }; + + float[] polarPoint = toPolar(cartPoint); + mPoints[i * 4] = polarPoint[0]; + mPoints[i * 4 + 1] = polarPoint[1]; + + float[] cartPoint2 = { + (float)(i + 1) / (mBytes.length - 1), + mRect.height() / 2 + ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 2) / 128 + }; + + float[] polarPoint2 = toPolar(cartPoint2); + mPoints[i * 4 + 2] = polarPoint2[0]; + mPoints[i * 4 + 3] = polarPoint2[1]; + } + + if(mCanvasBitmap == null) + { + mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888); + } + if(mCanvas == null) + { + mCanvas = new Canvas(mCanvasBitmap); + } + + mCanvas.drawLines(mPoints, mCirclePaint); + + + // Draw normal line - offset by amplitude + for (int i = 0; i < mBytes.length - 1; i++) { + mPoints[i * 4] = mRect.width() * i / (mBytes.length - 1); + mPoints[i * 4 + 1] = mRect.height() / 2 + + ((byte) (mBytes[i] + 128)) * (mRect.height() / 3) / 128; + mPoints[i * 4 + 2] = mRect.width() * (i + 1) / (mBytes.length - 1); + mPoints[i * 4 + 3] = mRect.height() / 2 + + ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 3) / 128; + } + + // Calc amplitude for this waveform + float accumulator = 0; + for (int i = 0; i < mBytes.length - 1; i++) { + accumulator += Math.abs(mBytes[i]); + } + + float amp = accumulator/(128 * mBytes.length); + if(amp > amplitude) + { + amplitude = amp; + // Occassionally, make a prominent line + mCanvas.drawLines(mPoints, mSpecialLinePaint); + } + else + { + amplitude *= 0.99; + mCanvas.drawLines(mPoints, mLinePaint); + } + + // FFT time!!!! + if (mFFTBytes == null) { + return; + } + + if (mFFTPoints == null || mFFTPoints.length < mBytes.length * 4) { + mFFTPoints = new float[mFFTBytes.length * 4]; + } + + // Equalizer top + if(mFFTBytes != null) + { + for (int i = 0; i < mFFTBytes.length / fft_divis_top; i++) { + mFFTPoints[i * 4] = i * 4 * fft_divis_top; + mFFTPoints[i * 4 + 1] = 0; + mFFTPoints[i * 4 + 2] = i * 4 * fft_divis_top; + byte rfk = mFFTBytes[fft_divis_top * i]; + byte ifk = mFFTBytes[fft_divis_top * i + 1]; + float magnitude = (rfk * rfk + ifk * ifk); + int dbValue = (int) (10 * Math.log10(magnitude)); + mFFTPoints[i * 4 + 3] = (dbValue * 2 - 10); + } + + mCanvas.drawLines(mFFTPoints, mFFTLineTopPaint); + } + + // Equalizer bottom + if(mFFTBytes != null) + { + for (int i = 0; i < mFFTBytes.length / fft_divis_bottom; i++) { + mFFTPoints[i * 4] = i * 4 * fft_divis_bottom; + mFFTPoints[i * 4 + 1] = mRect.height() - 2; + mFFTPoints[i * 4 + 2] = i * 4 * fft_divis_bottom; + byte rfk = mFFTBytes[fft_divis_bottom * i]; + byte ifk = mFFTBytes[fft_divis_bottom * i + 1]; + float magnitude = (rfk * rfk + ifk * ifk); + int dbValue = (int) (10 * Math.log10(magnitude)); + mFFTPoints[i * 4 + 3] = mRect.height() - (dbValue * 4) - 2; + } + + mCanvas.drawLines(mFFTPoints, mFFTLineBottomPaint); + } + + // We totally need a thing moving along the bottom + float cX = mRect.width()*(SystemClock.currentThreadTimeMillis() - mFlashTime)/mFlashPeriod; + + mCanvas.drawLine(cX - 35, mRect.height(), cX, mRect.height(), mProgressLinePaint); + + // Fade out old contents + mCanvas.drawPaint(mFadePaint); + + if(mFlash) + { + mFlash = false; + mCanvas.drawPaint(mFlashPaint); + } + + canvas.drawBitmap(mCanvasBitmap, new Matrix(), null); + modulation += 0.04; + } + + float modulation = 0; + float aggresive = 0.33f; + private float[] toPolar(float[] cartesian) + { + double cX = mRect.width()/2; + double cY = mRect.height()/2; + double angle = (cartesian[0]) * 2 * Math.PI; + double radius = ((mRect.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; + } + + +}
\ No newline at end of file |