├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── README.md ├── demo ├── demo-1.gif ├── demo-2.gif ├── demo-3.gif ├── demo-4.gif └── demo.m4v ├── proguard.cfg ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable │ └── bg.xml ├── layout │ └── main.xml ├── raw │ ├── test.mp3 │ └── workaround_1min.mp3 └── values │ └── strings.xml └── src └── com └── pheelicks ├── app └── MainActivity.java ├── utils ├── SystemPropertiesProxy.java └── TunnelPlayerWorkaround.java └── visualizer ├── AudioData.java ├── FFTData.java ├── VisualizerView.java └── renderer ├── BarGraphRenderer.java ├── CircleBarRenderer.java ├── CircleRenderer.java ├── LineRenderer.java └── Renderer.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore file from: https://github.com/github/gitignore 2 | 3 | # built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # files for the dex VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # generated files 14 | bin/ 15 | gen/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | .DS_Store 21 | .settings/ 22 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Visualizer 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Visualizer 2 | 3 | A View subclass that Takes the input from the Android MediaPlayer and displays visualizations, like in iTunes or WinAmp 4 | 5 | The Visualizer is designed to be modular, so it is very easy to combine visualizations to create more complex effects. 6 | 7 | ## Examples 8 | 9 | It's easy to create your own custom visualizations or combine/edit existing ones. Below are a few examples: 10 | 11 | ### A pair of frequency distributions. 12 | ![](https://github.com/felixpalmer/android-visualizer/raw/master/demo/demo-1.gif) 13 | This is an example which uses the FFT data rather than the raw audio waveform 14 | ### Line visualization 15 | ![](https://github.com/felixpalmer/android-visualizer/raw/master/demo/demo-2.gif) 16 | Draws the audio waveform on screen, pulsing on prominent beats 17 | ### Circle visualization 18 | ![](https://github.com/felixpalmer/android-visualizer/raw/master/demo/demo-3.gif) 19 | Draws the audio waveform in a circle, with the radius changing over time 20 | ### Combined visualization 21 | ![](https://github.com/felixpalmer/android-visualizer/raw/master/demo/demo-4.gif) 22 | A combination of all the above 23 | 24 | ## License 25 | Released under the [MIT license](http://creativecommons.org/licenses/MIT/). 26 | 27 | -------------------------------------------------------------------------------- /demo/demo-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/demo/demo-1.gif -------------------------------------------------------------------------------- /demo/demo-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/demo/demo-2.gif -------------------------------------------------------------------------------- /demo/demo-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/demo/demo-3.gif -------------------------------------------------------------------------------- /demo/demo-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/demo/demo-4.gif -------------------------------------------------------------------------------- /demo/demo.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/demo/demo.m4v -------------------------------------------------------------------------------- /proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-9 12 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable/bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 19 | 20 | 21 | 22 | 26 | 27 | 35 | 36 | 44 | 45 | 53 | 54 | 62 | 63 | 71 | 72 | 73 | 77 | 78 | 86 | 87 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /res/raw/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/res/raw/test.mp3 -------------------------------------------------------------------------------- /res/raw/workaround_1min.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixpalmer/android-visualizer/57e96262c3d8d33a0b2ec26189ea1f06e690f1c3/res/raw/workaround_1min.mp3 -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World, VisualizerActivity! 5 | Visualizer 6 | 7 | -------------------------------------------------------------------------------- /src/com/pheelicks/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.app; 8 | 9 | import java.io.IOException; 10 | 11 | import android.app.Activity; 12 | import android.graphics.Color; 13 | import android.graphics.Paint; 14 | import android.graphics.PorterDuff.Mode; 15 | import android.graphics.PorterDuffXfermode; 16 | import android.media.MediaPlayer; 17 | import android.os.Bundle; 18 | import android.view.View; 19 | 20 | import com.pheelicks.utils.TunnelPlayerWorkaround; 21 | import com.pheelicks.visualizer.R; 22 | import com.pheelicks.visualizer.VisualizerView; 23 | import com.pheelicks.visualizer.renderer.BarGraphRenderer; 24 | import com.pheelicks.visualizer.renderer.CircleBarRenderer; 25 | import com.pheelicks.visualizer.renderer.CircleRenderer; 26 | import com.pheelicks.visualizer.renderer.LineRenderer; 27 | 28 | /** 29 | * Demo to show how to use VisualizerView 30 | */ 31 | public class MainActivity extends Activity { 32 | private MediaPlayer mPlayer; 33 | private MediaPlayer mSilentPlayer; /* to avoid tunnel player issue */ 34 | private VisualizerView mVisualizerView; 35 | 36 | /** Called when the activity is first created. */ 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.main); 41 | } 42 | 43 | @Override 44 | protected void onResume() 45 | { 46 | super.onResume(); 47 | initTunnelPlayerWorkaround(); 48 | init(); 49 | } 50 | 51 | @Override 52 | protected void onPause() 53 | { 54 | cleanUp(); 55 | super.onPause(); 56 | } 57 | 58 | @Override 59 | protected void onDestroy() 60 | { 61 | cleanUp(); 62 | super.onDestroy(); 63 | } 64 | 65 | private void init() 66 | { 67 | mPlayer = MediaPlayer.create(this, R.raw.test); 68 | mPlayer.setLooping(true); 69 | mPlayer.start(); 70 | 71 | // We need to link the visualizer view to the media player so that 72 | // it displays something 73 | mVisualizerView = (VisualizerView) findViewById(R.id.visualizerView); 74 | mVisualizerView.link(mPlayer); 75 | 76 | // Start with just line renderer 77 | addLineRenderer(); 78 | } 79 | 80 | private void cleanUp() 81 | { 82 | if (mPlayer != null) 83 | { 84 | mVisualizerView.release(); 85 | mPlayer.release(); 86 | mPlayer = null; 87 | } 88 | 89 | if (mSilentPlayer != null) 90 | { 91 | mSilentPlayer.release(); 92 | mSilentPlayer = null; 93 | } 94 | } 95 | 96 | // Workaround (for Galaxy S4) 97 | // 98 | // "Visualization does not work on the new Galaxy devices" 99 | // https://github.com/felixpalmer/android-visualizer/issues/5 100 | // 101 | // NOTE: 102 | // This code is not required for visualizing default "test.mp3" file, 103 | // because tunnel player is used when duration is longer than 1 minute. 104 | // (default "test.mp3" file: 8 seconds) 105 | // 106 | private void initTunnelPlayerWorkaround() { 107 | // Read "tunnel.decode" system property to determine 108 | // the workaround is needed 109 | if (TunnelPlayerWorkaround.isTunnelDecodeEnabled(this)) { 110 | mSilentPlayer = TunnelPlayerWorkaround.createSilentMediaPlayer(this); 111 | } 112 | } 113 | 114 | // Methods for adding renderers to visualizer 115 | private void addBarGraphRenderers() 116 | { 117 | Paint paint = new Paint(); 118 | paint.setStrokeWidth(50f); 119 | paint.setAntiAlias(true); 120 | paint.setColor(Color.argb(200, 56, 138, 252)); 121 | BarGraphRenderer barGraphRendererBottom = new BarGraphRenderer(16, paint, false); 122 | mVisualizerView.addRenderer(barGraphRendererBottom); 123 | 124 | Paint paint2 = new Paint(); 125 | paint2.setStrokeWidth(12f); 126 | paint2.setAntiAlias(true); 127 | paint2.setColor(Color.argb(200, 181, 111, 233)); 128 | BarGraphRenderer barGraphRendererTop = new BarGraphRenderer(4, paint2, true); 129 | mVisualizerView.addRenderer(barGraphRendererTop); 130 | } 131 | 132 | private void addCircleBarRenderer() 133 | { 134 | Paint paint = new Paint(); 135 | paint.setStrokeWidth(8f); 136 | paint.setAntiAlias(true); 137 | paint.setXfermode(new PorterDuffXfermode(Mode.LIGHTEN)); 138 | paint.setColor(Color.argb(255, 222, 92, 143)); 139 | CircleBarRenderer circleBarRenderer = new CircleBarRenderer(paint, 32, true); 140 | mVisualizerView.addRenderer(circleBarRenderer); 141 | } 142 | 143 | private void addCircleRenderer() 144 | { 145 | Paint paint = new Paint(); 146 | paint.setStrokeWidth(3f); 147 | paint.setAntiAlias(true); 148 | paint.setColor(Color.argb(255, 222, 92, 143)); 149 | CircleRenderer circleRenderer = new CircleRenderer(paint, true); 150 | mVisualizerView.addRenderer(circleRenderer); 151 | } 152 | 153 | private void addLineRenderer() 154 | { 155 | Paint linePaint = new Paint(); 156 | linePaint.setStrokeWidth(1f); 157 | linePaint.setAntiAlias(true); 158 | linePaint.setColor(Color.argb(88, 0, 128, 255)); 159 | 160 | Paint lineFlashPaint = new Paint(); 161 | lineFlashPaint.setStrokeWidth(5f); 162 | lineFlashPaint.setAntiAlias(true); 163 | lineFlashPaint.setColor(Color.argb(188, 255, 255, 255)); 164 | LineRenderer lineRenderer = new LineRenderer(linePaint, lineFlashPaint, true); 165 | mVisualizerView.addRenderer(lineRenderer); 166 | } 167 | 168 | // Actions for buttons defined in xml 169 | public void startPressed(View view) throws IllegalStateException, IOException 170 | { 171 | if(mPlayer.isPlaying()) 172 | { 173 | return; 174 | } 175 | mPlayer.prepare(); 176 | mPlayer.start(); 177 | } 178 | 179 | public void stopPressed(View view) 180 | { 181 | mPlayer.stop(); 182 | } 183 | 184 | public void barPressed(View view) 185 | { 186 | addBarGraphRenderers(); 187 | } 188 | 189 | public void circlePressed(View view) 190 | { 191 | addCircleRenderer(); 192 | } 193 | 194 | public void circleBarPressed(View view) 195 | { 196 | addCircleBarRenderer(); 197 | } 198 | 199 | public void linePressed(View view) 200 | { 201 | addLineRenderer(); 202 | } 203 | 204 | public void clearPressed(View view) 205 | { 206 | mVisualizerView.clearRenderers(); 207 | } 208 | } -------------------------------------------------------------------------------- /src/com/pheelicks/utils/SystemPropertiesProxy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013, Haruki Hasegawa 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | 8 | /** 9 | * from http://stackoverflow.com/questions/2641111/where-is-android-os-systemproperties 10 | */ 11 | 12 | package com.pheelicks.utils; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | import android.content.Context; 17 | import android.util.Log; 18 | 19 | public class SystemPropertiesProxy { 20 | private static final String TAG = "SystemPropertiesProxy"; 21 | 22 | /** 23 | * Get the value for the given key, returned as a boolean. Values 'n', 'no', 24 | * '0', 'false' or 'off' are considered false. Values 'y', 'yes', '1', 'true' 25 | * or 'on' are considered true. (case insensitive). If the key does not exist, 26 | * or has any other value, then the default result is returned. 27 | * 28 | * @param key the key to lookup 29 | * @param def a default value to return 30 | * @return the key parsed as a boolean, or def if the key isn't found or is 31 | * not able to be parsed as a boolean. 32 | * @throws IllegalArgumentException if the key exceeds 32 characters 33 | */ 34 | public static Boolean getBoolean(Context context, String key, boolean def) 35 | throws IllegalArgumentException { 36 | return getBoolean(context.getClassLoader(), key, def); 37 | } 38 | 39 | public static Boolean getBoolean(ClassLoader cl, String key, boolean def) 40 | throws IllegalArgumentException { 41 | 42 | Boolean ret = def; 43 | 44 | try { 45 | @SuppressWarnings("rawtypes") 46 | Class SystemProperties = cl.loadClass("android.os.SystemProperties"); 47 | 48 | // Parameters Types 49 | @SuppressWarnings("rawtypes") 50 | Class[] paramTypes = new Class[2]; 51 | paramTypes[0] = String.class; 52 | paramTypes[1] = boolean.class; 53 | 54 | @SuppressWarnings("unchecked") 55 | Method getBoolean = SystemProperties.getMethod("getBoolean", paramTypes); 56 | 57 | // Parameters 58 | Object[] params = new Object[2]; 59 | params[0] = new String(key); 60 | params[1] = Boolean.valueOf(def); 61 | 62 | ret = (Boolean) getBoolean.invoke(SystemProperties, params); 63 | 64 | } catch (IllegalArgumentException iAE) { 65 | throw iAE; 66 | } catch (Exception e) { 67 | Log.e(TAG, "getBoolean(context, key: " + key + ", def:" + def + ")", e); 68 | ret = def; 69 | } 70 | 71 | return ret; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/com/pheelicks/utils/TunnelPlayerWorkaround.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013, Haruki Hasegawa 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | 8 | package com.pheelicks.utils; 9 | 10 | import java.io.IOException; 11 | 12 | import android.content.Context; 13 | import android.media.AudioManager; 14 | import android.media.MediaPlayer; 15 | import android.util.Log; 16 | 17 | import com.pheelicks.visualizer.R; 18 | 19 | public class TunnelPlayerWorkaround { 20 | private static final String TAG = "TunnelPlayerWorkaround"; 21 | 22 | private static final String SYSTEM_PROP_TUNNEL_DECODE_ENABLED = "tunnel.decode"; 23 | 24 | private TunnelPlayerWorkaround() 25 | { 26 | } 27 | 28 | /** 29 | * Obtain "tunnel.decode" system property value 30 | * 31 | * @param context Context 32 | * @return Whether tunnel player is enabled 33 | */ 34 | public static boolean isTunnelDecodeEnabled(Context context) 35 | { 36 | return SystemPropertiesProxy.getBoolean( 37 | context, SYSTEM_PROP_TUNNEL_DECODE_ENABLED, false); 38 | } 39 | 40 | /** 41 | * Create silent MediaPlayer instance to avoid tunnel player issue 42 | * 43 | * @param context Context 44 | * @return MediaPlayer instance 45 | */ 46 | public static MediaPlayer createSilentMediaPlayer(Context context) 47 | { 48 | boolean result = false; 49 | 50 | MediaPlayer mp = null; 51 | try { 52 | mp = MediaPlayer.create(context, R.raw.workaround_1min); 53 | mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 54 | 55 | // NOTE: start() is no needed 56 | // mp.start(); 57 | 58 | result = true; 59 | } catch (RuntimeException e) { 60 | Log.e(TAG, "createSilentMediaPlayer()", e); 61 | } finally { 62 | if (!result && mp != null) { 63 | try { 64 | mp.release(); 65 | } catch (IllegalStateException e) { 66 | } 67 | } 68 | } 69 | 70 | return mp; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/AudioData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer; 8 | 9 | // Data class to explicitly indicate that these bytes are raw audio data 10 | public class AudioData 11 | { 12 | public AudioData(byte[] bytes) 13 | { 14 | this.bytes = bytes; 15 | } 16 | 17 | public byte[] bytes; 18 | } 19 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/FFTData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer; 8 | 9 | // Data class to explicitly indicate that these bytes are the FFT of audio data 10 | public class FFTData 11 | { 12 | public FFTData(byte[] bytes) 13 | { 14 | this.bytes = bytes; 15 | } 16 | 17 | public byte[] bytes; 18 | } 19 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/VisualizerView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | import android.content.Context; 13 | import android.graphics.Bitmap; 14 | import android.graphics.Bitmap.Config; 15 | import android.graphics.Canvas; 16 | import android.graphics.Color; 17 | import android.graphics.Matrix; 18 | import android.graphics.Paint; 19 | import android.graphics.PorterDuff.Mode; 20 | import android.graphics.PorterDuffXfermode; 21 | import android.graphics.Rect; 22 | import android.media.MediaPlayer; 23 | import android.media.audiofx.Visualizer; 24 | import android.util.AttributeSet; 25 | import android.view.View; 26 | 27 | import com.pheelicks.visualizer.renderer.Renderer; 28 | 29 | /** 30 | * A class that draws visualizations of data received from a 31 | * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } and 32 | * {@link Visualizer.OnDataCaptureListener#onFftDataCapture } 33 | */ 34 | public class VisualizerView extends View { 35 | private static final String TAG = "VisualizerView"; 36 | 37 | private byte[] mBytes; 38 | private byte[] mFFTBytes; 39 | private Rect mRect = new Rect(); 40 | private Visualizer mVisualizer; 41 | 42 | private Set mRenderers; 43 | 44 | private Paint mFlashPaint = new Paint(); 45 | private Paint mFadePaint = new Paint(); 46 | 47 | public VisualizerView(Context context, AttributeSet attrs, int defStyle) 48 | { 49 | super(context, attrs); 50 | init(); 51 | } 52 | 53 | public VisualizerView(Context context, AttributeSet attrs) 54 | { 55 | this(context, attrs, 0); 56 | } 57 | 58 | public VisualizerView(Context context) 59 | { 60 | this(context, null, 0); 61 | } 62 | 63 | private void init() { 64 | mBytes = null; 65 | mFFTBytes = null; 66 | 67 | mFlashPaint.setColor(Color.argb(122, 255, 255, 255)); 68 | mFadePaint.setColor(Color.argb(238, 255, 255, 255)); // Adjust alpha to change how quickly the image fades 69 | mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY)); 70 | 71 | mRenderers = new HashSet(); 72 | } 73 | 74 | /** 75 | * Links the visualizer to a player 76 | * @param player - MediaPlayer instance to link to 77 | */ 78 | public void link(MediaPlayer player) 79 | { 80 | if(player == null) 81 | { 82 | throw new NullPointerException("Cannot link to null MediaPlayer"); 83 | } 84 | 85 | // Create the Visualizer object and attach it to our media player. 86 | mVisualizer = new Visualizer(player.getAudioSessionId()); 87 | mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]); 88 | 89 | // Pass through Visualizer data to VisualizerView 90 | Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener() 91 | { 92 | @Override 93 | public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, 94 | int samplingRate) 95 | { 96 | updateVisualizer(bytes); 97 | } 98 | 99 | @Override 100 | public void onFftDataCapture(Visualizer visualizer, byte[] bytes, 101 | int samplingRate) 102 | { 103 | updateVisualizerFFT(bytes); 104 | } 105 | }; 106 | 107 | mVisualizer.setDataCaptureListener(captureListener, 108 | Visualizer.getMaxCaptureRate() / 2, true, true); 109 | 110 | // Enabled Visualizer and disable when we're done with the stream 111 | mVisualizer.setEnabled(true); 112 | player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() 113 | { 114 | @Override 115 | public void onCompletion(MediaPlayer mediaPlayer) 116 | { 117 | mVisualizer.setEnabled(false); 118 | } 119 | }); 120 | } 121 | 122 | public void addRenderer(Renderer renderer) 123 | { 124 | if(renderer != null) 125 | { 126 | mRenderers.add(renderer); 127 | } 128 | } 129 | 130 | public void clearRenderers() 131 | { 132 | mRenderers.clear(); 133 | } 134 | 135 | /** 136 | * Call to release the resources used by VisualizerView. Like with the 137 | * MediaPlayer it is good practice to call this method 138 | */ 139 | public void release() 140 | { 141 | mVisualizer.release(); 142 | } 143 | 144 | /** 145 | * Pass data to the visualizer. Typically this will be obtained from the 146 | * Android Visualizer.OnDataCaptureListener call back. See 147 | * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } 148 | * @param bytes 149 | */ 150 | public void updateVisualizer(byte[] bytes) { 151 | mBytes = bytes; 152 | invalidate(); 153 | } 154 | 155 | /** 156 | * Pass FFT data to the visualizer. Typically this will be obtained from the 157 | * Android Visualizer.OnDataCaptureListener call back. See 158 | * {@link Visualizer.OnDataCaptureListener#onFftDataCapture } 159 | * @param bytes 160 | */ 161 | public void updateVisualizerFFT(byte[] bytes) { 162 | mFFTBytes = bytes; 163 | invalidate(); 164 | } 165 | 166 | boolean mFlash = false; 167 | 168 | /** 169 | * Call this to make the visualizer flash. Useful for flashing at the start 170 | * of a song/loop etc... 171 | */ 172 | public void flash() { 173 | mFlash = true; 174 | invalidate(); 175 | } 176 | 177 | Bitmap mCanvasBitmap; 178 | Canvas mCanvas; 179 | 180 | 181 | @Override 182 | protected void onDraw(Canvas canvas) { 183 | super.onDraw(canvas); 184 | 185 | // Create canvas once we're ready to draw 186 | mRect.set(0, 0, getWidth(), getHeight()); 187 | 188 | if(mCanvasBitmap == null) 189 | { 190 | mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888); 191 | } 192 | if(mCanvas == null) 193 | { 194 | mCanvas = new Canvas(mCanvasBitmap); 195 | } 196 | 197 | if (mBytes != null) { 198 | // Render all audio renderers 199 | AudioData audioData = new AudioData(mBytes); 200 | for(Renderer r : mRenderers) 201 | { 202 | r.render(mCanvas, audioData, mRect); 203 | } 204 | } 205 | 206 | if (mFFTBytes != null) { 207 | // Render all FFT renderers 208 | FFTData fftData = new FFTData(mFFTBytes); 209 | for(Renderer r : mRenderers) 210 | { 211 | r.render(mCanvas, fftData, mRect); 212 | } 213 | } 214 | 215 | // Fade out old contents 216 | mCanvas.drawPaint(mFadePaint); 217 | 218 | if(mFlash) 219 | { 220 | mFlash = false; 221 | mCanvas.drawPaint(mFlashPaint); 222 | } 223 | 224 | canvas.drawBitmap(mCanvasBitmap, new Matrix(), null); 225 | } 226 | } -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/renderer/BarGraphRenderer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer.renderer; 8 | 9 | import android.graphics.Canvas; 10 | import android.graphics.Paint; 11 | import android.graphics.Rect; 12 | 13 | import com.pheelicks.visualizer.AudioData; 14 | import com.pheelicks.visualizer.FFTData; 15 | 16 | public class BarGraphRenderer extends Renderer 17 | { 18 | private int mDivisions; 19 | private Paint mPaint; 20 | private boolean mTop; 21 | 22 | /** 23 | * Renders the FFT data as a series of lines, in histogram form 24 | * @param divisions - must be a power of 2. Controls how many lines to draw 25 | * @param paint - Paint to draw lines with 26 | * @param top - whether to draw the lines at the top of the canvas, or the bottom 27 | */ 28 | public BarGraphRenderer(int divisions, 29 | Paint paint, 30 | boolean top) 31 | { 32 | super(); 33 | mDivisions = divisions; 34 | mPaint = paint; 35 | mTop = top; 36 | } 37 | 38 | @Override 39 | public void onRender(Canvas canvas, AudioData data, Rect rect) 40 | { 41 | // Do nothing, we only display FFT data 42 | } 43 | 44 | @Override 45 | public void onRender(Canvas canvas, FFTData data, Rect rect) 46 | { 47 | for (int i = 0; i < data.bytes.length / mDivisions; i++) { 48 | mFFTPoints[i * 4] = i * 4 * mDivisions; 49 | mFFTPoints[i * 4 + 2] = i * 4 * mDivisions; 50 | byte rfk = data.bytes[mDivisions * i]; 51 | byte ifk = data.bytes[mDivisions * i + 1]; 52 | float magnitude = (rfk * rfk + ifk * ifk); 53 | int dbValue = (int) (10 * Math.log10(magnitude)); 54 | 55 | if(mTop) 56 | { 57 | mFFTPoints[i * 4 + 1] = 0; 58 | mFFTPoints[i * 4 + 3] = (dbValue * 2 - 10); 59 | } 60 | else 61 | { 62 | mFFTPoints[i * 4 + 1] = rect.height(); 63 | mFFTPoints[i * 4 + 3] = rect.height() - (dbValue * 2 - 10); 64 | } 65 | } 66 | 67 | canvas.drawLines(mFFTPoints, mPaint); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/renderer/CircleBarRenderer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer.renderer; 8 | 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Paint; 12 | import android.graphics.Rect; 13 | 14 | import com.pheelicks.visualizer.AudioData; 15 | import com.pheelicks.visualizer.FFTData; 16 | 17 | public class CircleBarRenderer extends Renderer 18 | { 19 | private int mDivisions; 20 | private Paint mPaint; 21 | private boolean mCycleColor; 22 | 23 | /** 24 | * Renders the FFT data onto a pulsing, rotating circle 25 | * @param canvas 26 | * @param paint - Paint to draw lines with 27 | */ 28 | public CircleBarRenderer(Paint paint, int divisions) 29 | { 30 | this(paint, divisions, false); 31 | } 32 | 33 | /** 34 | * Renders the audio data onto a pulsing circle 35 | * @param canvas 36 | * @param paint - Paint to draw lines with 37 | * @param divisions - must be a power of 2. Controls how many lines to draw 38 | * @param cycleColor - If true the color will change on each frame 39 | */ 40 | public CircleBarRenderer(Paint paint, int divisions, boolean cycleColor) 41 | { 42 | super(); 43 | mPaint = paint; 44 | mDivisions = divisions; 45 | mCycleColor = cycleColor; 46 | } 47 | 48 | @Override 49 | public void onRender(Canvas canvas, AudioData data, Rect rect) 50 | { 51 | // Do nothing, we only display FFT data 52 | } 53 | 54 | @Override 55 | public void onRender(Canvas canvas, FFTData data, Rect rect) 56 | { 57 | if(mCycleColor) 58 | { 59 | cycleColor(); 60 | } 61 | 62 | for (int i = 0; i < data.bytes.length / mDivisions; i++) { 63 | // Calculate dbValue 64 | byte rfk = data.bytes[mDivisions * i]; 65 | byte ifk = data.bytes[mDivisions * i + 1]; 66 | float magnitude = (rfk * rfk + ifk * ifk); 67 | float dbValue = 75 * (float)Math.log10(magnitude); 68 | 69 | float[] cartPoint = { 70 | (float)(i * mDivisions) / (data.bytes.length - 1), 71 | rect.height() / 2 - dbValue / 4 72 | }; 73 | 74 | float[] polarPoint = toPolar(cartPoint, rect); 75 | mFFTPoints[i * 4] = polarPoint[0]; 76 | mFFTPoints[i * 4 + 1] = polarPoint[1]; 77 | 78 | float[] cartPoint2 = { 79 | (float)(i * mDivisions) / (data.bytes.length - 1), 80 | rect.height() / 2 + dbValue 81 | }; 82 | 83 | float[] polarPoint2 = toPolar(cartPoint2, rect); 84 | mFFTPoints[i * 4 + 2] = polarPoint2[0]; 85 | mFFTPoints[i * 4 + 3] = polarPoint2[1]; 86 | } 87 | 88 | canvas.drawLines(mFFTPoints, mPaint); 89 | 90 | // Controls the pulsing rate 91 | modulation += 0.13; 92 | angleModulation += 0.28; 93 | } 94 | 95 | float modulation = 0; 96 | float modulationStrength = 0.4f; // 0-1 97 | float angleModulation = 0; 98 | float aggresive = 0.4f; 99 | private float[] toPolar(float[] cartesian, Rect rect) 100 | { 101 | double cX = rect.width()/2; 102 | double cY = rect.height()/2; 103 | double angle = (cartesian[0]) * 2 * Math.PI; 104 | double radius = ((rect.width()/2) * (1 - aggresive) + aggresive * cartesian[1]/2) * ((1 - modulationStrength) + modulationStrength * (1 + Math.sin(modulation)) / 2); 105 | float[] out = { 106 | (float)(cX + radius * Math.sin(angle + angleModulation)), 107 | (float)(cY + radius * Math.cos(angle + angleModulation)) 108 | }; 109 | return out; 110 | } 111 | 112 | private float colorCounter = 0; 113 | private void cycleColor() 114 | { 115 | int r = (int)Math.floor(128*(Math.sin(colorCounter) + 1)); 116 | int g = (int)Math.floor(128*(Math.sin(colorCounter + 2) + 1)); 117 | int b = (int)Math.floor(128*(Math.sin(colorCounter + 4) + 1)); 118 | mPaint.setColor(Color.argb(128, r, g, b)); 119 | colorCounter += 0.03; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/renderer/CircleRenderer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer.renderer; 8 | 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Paint; 12 | import android.graphics.Rect; 13 | 14 | import com.pheelicks.visualizer.AudioData; 15 | import com.pheelicks.visualizer.FFTData; 16 | 17 | public class CircleRenderer extends Renderer 18 | { 19 | private Paint mPaint; 20 | private boolean mCycleColor; 21 | 22 | /** 23 | * Renders the audio data onto a pulsing circle 24 | * @param canvas 25 | * @param paint - Paint to draw lines with 26 | */ 27 | public CircleRenderer(Paint paint) 28 | { 29 | this(paint, false); 30 | } 31 | 32 | /** 33 | * Renders the audio data onto a pulsing circle 34 | * @param canvas 35 | * @param paint - Paint to draw lines with 36 | * @param cycleColor - If true the color will change on each frame 37 | */ 38 | public CircleRenderer(Paint paint, boolean cycleColor) 39 | { 40 | super(); 41 | mPaint = paint; 42 | mCycleColor = cycleColor; 43 | } 44 | 45 | @Override 46 | public void onRender(Canvas canvas, AudioData data, Rect rect) 47 | { 48 | if(mCycleColor) 49 | { 50 | cycleColor(); 51 | } 52 | 53 | for (int i = 0; i < data.bytes.length - 1; i++) { 54 | float[] cartPoint = { 55 | (float)i / (data.bytes.length - 1), 56 | rect.height() / 2 + ((byte) (data.bytes[i] + 128)) * (rect.height() / 2) / 128 57 | }; 58 | 59 | float[] polarPoint = toPolar(cartPoint, rect); 60 | mPoints[i * 4] = polarPoint[0]; 61 | mPoints[i * 4 + 1] = polarPoint[1]; 62 | 63 | float[] cartPoint2 = { 64 | (float)(i + 1) / (data.bytes.length - 1), 65 | rect.height() / 2 + ((byte) (data.bytes[i + 1] + 128)) * (rect.height() / 2) / 128 66 | }; 67 | 68 | float[] polarPoint2 = toPolar(cartPoint2, rect); 69 | mPoints[i * 4 + 2] = polarPoint2[0]; 70 | mPoints[i * 4 + 3] = polarPoint2[1]; 71 | } 72 | 73 | canvas.drawLines(mPoints, mPaint); 74 | 75 | // Controls the pulsing rate 76 | modulation += 0.04; 77 | } 78 | 79 | @Override 80 | public void onRender(Canvas canvas, FFTData data, Rect rect) 81 | { 82 | // Do nothing, we only display audio data 83 | } 84 | 85 | float modulation = 0; 86 | float aggresive = 0.33f; 87 | private float[] toPolar(float[] cartesian, Rect rect) 88 | { 89 | double cX = rect.width()/2; 90 | double cY = rect.height()/2; 91 | double angle = (cartesian[0]) * 2 * Math.PI; 92 | double radius = ((rect.width()/2) * (1 - aggresive) + aggresive * cartesian[1]/2) * (1.2 + Math.sin(modulation))/2.2; 93 | float[] out = { 94 | (float)(cX + radius * Math.sin(angle)), 95 | (float)(cY + radius * Math.cos(angle)) 96 | }; 97 | return out; 98 | } 99 | 100 | private float colorCounter = 0; 101 | private void cycleColor() 102 | { 103 | int r = (int)Math.floor(128*(Math.sin(colorCounter) + 1)); 104 | int g = (int)Math.floor(128*(Math.sin(colorCounter + 2) + 1)); 105 | int b = (int)Math.floor(128*(Math.sin(colorCounter + 4) + 1)); 106 | mPaint.setColor(Color.argb(128, r, g, b)); 107 | colorCounter += 0.03; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/renderer/LineRenderer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer.renderer; 8 | 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Paint; 12 | import android.graphics.Rect; 13 | 14 | import com.pheelicks.visualizer.AudioData; 15 | import com.pheelicks.visualizer.FFTData; 16 | 17 | public class LineRenderer extends Renderer 18 | { 19 | private Paint mPaint; 20 | private Paint mFlashPaint; 21 | private boolean mCycleColor; 22 | private float amplitude = 0; 23 | 24 | 25 | /** 26 | * Renders the audio data onto a line. The line flashes on prominent beats 27 | * @param canvas 28 | * @param paint - Paint to draw lines with 29 | * @param paint - Paint to draw flash with 30 | */ 31 | public LineRenderer(Paint paint, Paint flashPaint) 32 | { 33 | this(paint, flashPaint, false); 34 | } 35 | 36 | /** 37 | * Renders the audio data onto a line. The line flashes on prominent beats 38 | * @param canvas 39 | * @param paint - Paint to draw lines with 40 | * @param paint - Paint to draw flash with 41 | * @param cycleColor - If true the color will change on each frame 42 | */ 43 | public LineRenderer(Paint paint, 44 | Paint flashPaint, 45 | boolean cycleColor) 46 | { 47 | super(); 48 | mPaint = paint; 49 | mFlashPaint = flashPaint; 50 | mCycleColor = cycleColor; 51 | } 52 | 53 | @Override 54 | public void onRender(Canvas canvas, AudioData data, Rect rect) 55 | { 56 | if(mCycleColor) 57 | { 58 | cycleColor(); 59 | } 60 | 61 | // Calculate points for line 62 | for (int i = 0; i < data.bytes.length - 1; i++) { 63 | mPoints[i * 4] = rect.width() * i / (data.bytes.length - 1); 64 | mPoints[i * 4 + 1] = rect.height() / 2 65 | + ((byte) (data.bytes[i] + 128)) * (rect.height() / 3) / 128; 66 | mPoints[i * 4 + 2] = rect.width() * (i + 1) / (data.bytes.length - 1); 67 | mPoints[i * 4 + 3] = rect.height() / 2 68 | + ((byte) (data.bytes[i + 1] + 128)) * (rect.height() / 3) / 128; 69 | } 70 | 71 | // Calc amplitude for this waveform 72 | float accumulator = 0; 73 | for (int i = 0; i < data.bytes.length - 1; i++) { 74 | accumulator += Math.abs(data.bytes[i]); 75 | } 76 | 77 | float amp = accumulator/(128 * data.bytes.length); 78 | if(amp > amplitude) 79 | { 80 | // Amplitude is bigger than normal, make a prominent line 81 | amplitude = amp; 82 | canvas.drawLines(mPoints, mFlashPaint); 83 | } 84 | else 85 | { 86 | // Amplitude is nothing special, reduce the amplitude 87 | amplitude *= 0.99; 88 | canvas.drawLines(mPoints, mPaint); 89 | } 90 | } 91 | 92 | @Override 93 | public void onRender(Canvas canvas, FFTData data, Rect rect) 94 | { 95 | // Do nothing, we only display audio data 96 | } 97 | 98 | private float colorCounter = 0; 99 | private void cycleColor() 100 | { 101 | int r = (int)Math.floor(128*(Math.sin(colorCounter) + 3)); 102 | int g = (int)Math.floor(128*(Math.sin(colorCounter + 1) + 1)); 103 | int b = (int)Math.floor(128*(Math.sin(colorCounter + 7) + 1)); 104 | mPaint.setColor(Color.argb(128, r, g, b)); 105 | colorCounter += 0.03; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/com/pheelicks/visualizer/renderer/Renderer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011, Felix Palmer 3 | * 4 | * Licensed under the MIT license: 5 | * http://creativecommons.org/licenses/MIT/ 6 | */ 7 | package com.pheelicks.visualizer.renderer; 8 | 9 | import android.graphics.Canvas; 10 | import android.graphics.Rect; 11 | 12 | import com.pheelicks.visualizer.AudioData; 13 | import com.pheelicks.visualizer.FFTData; 14 | 15 | abstract public class Renderer 16 | { 17 | // Have these as members, so we don't have to re-create them each time 18 | protected float[] mPoints; 19 | protected float[] mFFTPoints; 20 | public Renderer() 21 | { 22 | } 23 | 24 | // As the display of raw/FFT audio will usually look different, subclasses 25 | // will typically only implement one of the below methods 26 | /** 27 | * Implement this method to render the audio data onto the canvas 28 | * @param canvas - Canvas to draw on 29 | * @param data - Data to render 30 | * @param rect - Rect to render into 31 | */ 32 | abstract public void onRender(Canvas canvas, AudioData data, Rect rect); 33 | 34 | /** 35 | * Implement this method to render the FFT audio data onto the canvas 36 | * @param canvas - Canvas to draw on 37 | * @param data - Data to render 38 | * @param rect - Rect to render into 39 | */ 40 | abstract public void onRender(Canvas canvas, FFTData data, Rect rect); 41 | 42 | 43 | // These methods should actually be called for rendering 44 | /** 45 | * Render the audio data onto the canvas 46 | * @param canvas - Canvas to draw on 47 | * @param data - Data to render 48 | * @param rect - Rect to render into 49 | */ 50 | final public void render(Canvas canvas, AudioData data, Rect rect) 51 | { 52 | if (mPoints == null || mPoints.length < data.bytes.length * 4) { 53 | mPoints = new float[data.bytes.length * 4]; 54 | } 55 | 56 | onRender(canvas, data, rect); 57 | } 58 | 59 | /** 60 | * Render the FFT data onto the canvas 61 | * @param canvas - Canvas to draw on 62 | * @param data - Data to render 63 | * @param rect - Rect to render into 64 | */ 65 | final public void render(Canvas canvas, FFTData data, Rect rect) 66 | { 67 | if (mFFTPoints == null || mFFTPoints.length < data.bytes.length * 4) { 68 | mFFTPoints = new float[data.bytes.length * 4]; 69 | } 70 | 71 | onRender(canvas, data, rect); 72 | } 73 | } 74 | --------------------------------------------------------------------------------