├── .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 | 
13 | This is an example which uses the FFT data rather than the raw audio waveform
14 | ### Line visualization
15 | 
16 | Draws the audio waveform on screen, pulsing on prominent beats
17 | ### Circle visualization
18 | 
19 | Draws the audio waveform in a circle, with the radius changing over time
20 | ### Combined visualization
21 | 
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 |
--------------------------------------------------------------------------------