35 | * Created by gk 36 | */ 37 | 38 | abstract public class BaseVisualizer extends View { 39 | 40 | protected byte[] mRawAudioBytes; 41 | protected Paint mPaint; 42 | protected Visualizer mVisualizer; 43 | protected int mColor = AVConstants.DEFAULT_COLOR; 44 | 45 | protected PaintStyle mPaintStyle = PaintStyle.FILL; 46 | protected PositionGravity mPositionGravity = PositionGravity.BOTTOM; 47 | 48 | protected float mStrokeWidth = AVConstants.DEFAULT_STROKE_WIDTH; 49 | protected float mDensity = AVConstants.DEFAULT_DENSITY; 50 | 51 | protected AnimSpeed mAnimSpeed = AnimSpeed.MEDIUM; 52 | protected boolean isVisualizationEnabled = true; 53 | 54 | public BaseVisualizer(Context context) { 55 | super(context); 56 | init(context, null); 57 | init(); 58 | } 59 | 60 | public BaseVisualizer(Context context, @Nullable AttributeSet attrs) { 61 | super(context, attrs); 62 | init(context, attrs); 63 | init(); 64 | } 65 | 66 | public BaseVisualizer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 67 | super(context, attrs, defStyleAttr); 68 | init(context, attrs); 69 | init(); 70 | } 71 | 72 | private void init(Context context, AttributeSet attrs) { 73 | 74 | //get the attributes specified in attrs.xml using the name we included 75 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, 76 | R.styleable.BaseVisualizer, 0, 0); 77 | if (typedArray != null && typedArray.length() > 0) { 78 | try { 79 | //get the text and colors specified using the names in attrs.xml 80 | this.mDensity = typedArray.getFloat(R.styleable.BaseVisualizer_avDensity, AVConstants.DEFAULT_DENSITY); 81 | this.mColor = typedArray.getColor(R.styleable.BaseVisualizer_avColor, AVConstants.DEFAULT_COLOR); 82 | this.mStrokeWidth = typedArray.getDimension(R.styleable.BaseVisualizer_avWidth, AVConstants.DEFAULT_STROKE_WIDTH); 83 | 84 | String paintType = typedArray.getString(R.styleable.BaseVisualizer_avType); 85 | if (paintType != null && !paintType.equals("")) 86 | this.mPaintStyle = paintType.toLowerCase().equals("outline") ? PaintStyle.OUTLINE : PaintStyle.FILL; 87 | 88 | String gravityType = typedArray.getString(R.styleable.BaseVisualizer_avGravity); 89 | if (gravityType != null && !gravityType.equals("")) 90 | this.mPositionGravity = gravityType.toLowerCase().equals("top") ? PositionGravity.TOP : PositionGravity.BOTTOM; 91 | 92 | String speedType = typedArray.getString(R.styleable.BaseVisualizer_avSpeed); 93 | if (speedType != null && !speedType.equals("")) { 94 | this.mAnimSpeed = AnimSpeed.MEDIUM; 95 | if (speedType.toLowerCase().equals("slow")) 96 | this.mAnimSpeed = AnimSpeed.SLOW; 97 | else if (speedType.toLowerCase().equals("fast")) 98 | this.mAnimSpeed = AnimSpeed.FAST; 99 | } 100 | 101 | } finally { 102 | typedArray.recycle(); 103 | } 104 | } 105 | 106 | mPaint = new Paint(); 107 | mPaint.setColor(mColor); 108 | mPaint.setStrokeWidth(mStrokeWidth); 109 | if (mPaintStyle == PaintStyle.FILL) 110 | mPaint.setStyle(Paint.Style.FILL); 111 | else { 112 | mPaint.setStyle(Paint.Style.STROKE); 113 | } 114 | } 115 | 116 | /** 117 | * Set color to visualizer with color resource id. 118 | * 119 | * @param color color resource id. 120 | */ 121 | public void setColor(int color) { 122 | this.mColor = color; 123 | this.mPaint.setColor(this.mColor); 124 | } 125 | 126 | /** 127 | * Set the density of the visualizer 128 | * 129 | * @param density density for visualization 130 | */ 131 | public void setDensity(float density) { 132 | //TODO: Check dynamic density change, may cause crash 133 | synchronized (this) { 134 | this.mDensity = density; 135 | init(); 136 | } 137 | } 138 | 139 | /** 140 | * Sets the paint style of the visualizer 141 | * 142 | * @param paintStyle style of the visualizer. 143 | */ 144 | public void setPaintStyle(PaintStyle paintStyle) { 145 | this.mPaintStyle = paintStyle; 146 | this.mPaint.setStyle(paintStyle == PaintStyle.FILL ? Paint.Style.FILL : Paint.Style.STROKE); 147 | } 148 | 149 | /** 150 | * Sets the position of the Visualization{@link PositionGravity} 151 | * 152 | * @param positionGravity position of the Visualization 153 | */ 154 | public void setPositionGravity(PositionGravity positionGravity) { 155 | this.mPositionGravity = positionGravity; 156 | } 157 | 158 | /** 159 | * Sets the Animation speed of the visualization{@link AnimSpeed} 160 | * 161 | * @param animSpeed speed of the animation 162 | */ 163 | public void setAnimationSpeed(AnimSpeed animSpeed) { 164 | this.mAnimSpeed = animSpeed; 165 | } 166 | 167 | /** 168 | * Sets the width of the outline {@link PaintStyle} 169 | * 170 | * @param width style of the visualizer. 171 | */ 172 | public void setStrokeWidth(float width) { 173 | this.mStrokeWidth = width; 174 | this.mPaint.setStrokeWidth(width); 175 | } 176 | 177 | /** 178 | * Sets the audio bytes to be visualized form {@link Visualizer} or other sources 179 | * 180 | * @param bytes of the raw bytes of music 181 | */ 182 | public void setRawAudioBytes(byte[] bytes) { 183 | this.mRawAudioBytes = bytes; 184 | this.invalidate(); 185 | } 186 | 187 | /** 188 | * Sets the audio session id for the currently playing audio 189 | * 190 | * @param audioSessionId of the media to be visualised 191 | */ 192 | public void setAudioSessionId(int audioSessionId) { 193 | if (mVisualizer != null) 194 | release(); 195 | 196 | mVisualizer = new Visualizer(audioSessionId); 197 | mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]); 198 | 199 | mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() { 200 | @Override 201 | public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, 202 | int samplingRate) { 203 | BaseVisualizer.this.mRawAudioBytes = bytes; 204 | invalidate(); 205 | } 206 | 207 | @Override 208 | public void onFftDataCapture(Visualizer visualizer, byte[] bytes, 209 | int samplingRate) { 210 | } 211 | }, Visualizer.getMaxCaptureRate() / 2, true, false); 212 | 213 | mVisualizer.setEnabled(true); 214 | } 215 | 216 | /** 217 | * Releases the visualizer 218 | */ 219 | public void release() { 220 | if (mVisualizer != null) 221 | mVisualizer.release(); 222 | } 223 | 224 | /** 225 | * Enable Visualization 226 | */ 227 | public void show() { 228 | this.isVisualizationEnabled = true; 229 | } 230 | 231 | /** 232 | * Disable Visualization 233 | */ 234 | public void hide() { 235 | this.isVisualizationEnabled = false; 236 | } 237 | 238 | protected abstract void init(); 239 | 240 | } -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/model/AnimSpeed.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.model; 17 | 18 | public enum AnimSpeed { 19 | SLOW, 20 | MEDIUM, 21 | FAST 22 | } 23 | -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/model/PaintStyle.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.model; 17 | 18 | public enum PaintStyle { 19 | OUTLINE, 20 | FILL 21 | } 22 | -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/model/PositionGravity.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.model; 17 | 18 | public enum PositionGravity { 19 | TOP, 20 | BOTTOM 21 | } 22 | -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/utils/AVConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.utils; 17 | 18 | import android.graphics.Color; 19 | 20 | public class AVConstants { 21 | public static final float DEFAULT_DENSITY = 0.25f; 22 | public static final int DEFAULT_COLOR = Color.BLACK; 23 | public static final float DEFAULT_STROKE_WIDTH = 6.0f; 24 | public static final int MAX_ANIM_BATCH_COUNT = 4; 25 | } 26 | -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/utils/BezierSpline.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.utils; 17 | 18 | import android.graphics.PointF; 19 | 20 | public class BezierSpline { 21 | 22 | private final int nSize; 23 | private final PointF[] firstControlPoints, secondControlPoints; 24 | 25 | public BezierSpline(int size) { 26 | this.nSize = size - 1; 27 | firstControlPoints = new PointF[nSize]; 28 | secondControlPoints = new PointF[nSize]; 29 | for (int i = 0; i < nSize; i++) { 30 | firstControlPoints[i] = new PointF(); 31 | secondControlPoints[i] = new PointF(); 32 | } 33 | } 34 | 35 | /** 36 | * Get open-ended bezier spline control points. 37 | * 38 | * @param knots bezier spline points 39 | * @throws IllegalArgumentException if less than two knots are passed. 40 | */ 41 | public void updateCurveControlPoints(PointF[] knots) { 42 | if (knots == null || knots.length < 2) { 43 | throw new IllegalArgumentException("At least two knot points are required"); 44 | } 45 | 46 | final int n = knots.length - 1; 47 | 48 | // Special case: bezier curve should be a straight line 49 | if (n == 1) { 50 | // 3P1 = 2P0 + P3 51 | float x = (2 * knots[0].x + knots[1].x) / 3; 52 | float y = (2 * knots[0].y + knots[1].y) / 3; 53 | 54 | firstControlPoints[0].x = x; 55 | firstControlPoints[0].y = y; 56 | 57 | // P2 = 2P1 - P0 58 | x = 2 * firstControlPoints[0].x - knots[0].x; 59 | y = 2 * firstControlPoints[0].y - knots[0].y; 60 | 61 | secondControlPoints[0].x = x; 62 | secondControlPoints[0].y = y; 63 | 64 | } else { 65 | 66 | // Calculate first bezier control points 67 | // Right hand side vector 68 | float[] rhs = new float[n]; 69 | 70 | // Set right hand side X values 71 | for (int i = 1; i < n - 1; i++) { 72 | rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; 73 | } 74 | rhs[0] = knots[0].x + 2 * knots[1].x; 75 | rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2f; 76 | 77 | // Get first control points X-values 78 | float[] x = getFirstControlPoints(rhs); 79 | 80 | // Set right hand side Y values 81 | for (int i = 1; i < n - 1; i++) { 82 | rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; 83 | } 84 | rhs[0] = knots[0].y + 2 * knots[1].y; 85 | rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2f; 86 | 87 | // Get first control points Y-values 88 | float[] y = getFirstControlPoints(rhs); 89 | 90 | for (int i = 0; i < n; i++) { 91 | // First control point 92 | firstControlPoints[i].x = x[i]; 93 | firstControlPoints[i].y = y[i]; 94 | 95 | // Second control point 96 | if (i < n - 1) { 97 | float xx = 2 * knots[i + 1].x - x[i + 1]; 98 | float yy = 2 * knots[i + 1].y - y[i + 1]; 99 | secondControlPoints[i].x = xx; 100 | secondControlPoints[i].y = yy; 101 | } else { 102 | float xx = (knots[n].x + x[n - 1]) / 2; 103 | float yy = (knots[n].y + y[n - 1]) / 2; 104 | secondControlPoints[i].x = xx; 105 | secondControlPoints[i].y = yy; 106 | } 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Solves a tridiagonal system for one of coordinates (x or y) of first 113 | * bezier control points. 114 | * 115 | * @param rhs right hand side vector. 116 | * @return Solution vector. 117 | */ 118 | private float[] getFirstControlPoints(float[] rhs) { 119 | int n = rhs.length; 120 | float[] x = new float[n]; // Solution vector 121 | float[] tmp = new float[n]; // Temp workspace 122 | 123 | float b = 2.0f; 124 | x[0] = rhs[0] / b; 125 | 126 | // Decomposition and forward substitution 127 | for (int i = 1; i < n; i++) { 128 | tmp[i] = 1 / b; 129 | b = (i < n - 1 ? 4.0f : 3.5f) - tmp[i]; 130 | x[i] = (rhs[i] - x[i - 1]) / b; 131 | } 132 | 133 | // Backsubstitution 134 | for (int i = 1; i < n; i++) { 135 | x[n - i - 1] -= tmp[n - i] * x[n - i]; 136 | } 137 | 138 | return x; 139 | } 140 | 141 | public PointF[] getFirstControlPoints() { 142 | return firstControlPoints; 143 | } 144 | 145 | public PointF[] getSecondControlPoints() { 146 | return secondControlPoints; 147 | } 148 | } -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/visualizer/BarVisualizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.visualizer; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.Rect; 21 | import android.support.annotation.Nullable; 22 | import android.util.AttributeSet; 23 | 24 | import com.gauravk.audiovisualizer.base.BaseVisualizer; 25 | import com.gauravk.audiovisualizer.model.AnimSpeed; 26 | import com.gauravk.audiovisualizer.model.PositionGravity; 27 | import com.gauravk.audiovisualizer.utils.AVConstants; 28 | 29 | import java.util.Random; 30 | 31 | /** 32 | * Custom view to create bar visualizer 33 | *
34 | * Created by gk 35 | */ 36 | 37 | public class BarVisualizer extends BaseVisualizer { 38 | 39 | private static final int BAR_MAX_POINTS = 120; 40 | private static final int BAR_MIN_POINTS = 3; 41 | 42 | private int mMaxBatchCount; 43 | 44 | private int nPoints; 45 | 46 | private float[] mSrcY, mDestY; 47 | 48 | private float mBarWidth; 49 | private Rect mClipBounds; 50 | 51 | private int nBatchCount; 52 | 53 | private Random mRandom; 54 | 55 | public BarVisualizer(Context context) { 56 | super(context); 57 | } 58 | 59 | public BarVisualizer(Context context, 60 | @Nullable AttributeSet attrs) { 61 | super(context, attrs); 62 | } 63 | 64 | public BarVisualizer(Context context, 65 | @Nullable AttributeSet attrs, 66 | int defStyleAttr) { 67 | super(context, attrs, defStyleAttr); 68 | } 69 | 70 | @Override 71 | protected void init() { 72 | nPoints = (int) (BAR_MAX_POINTS * mDensity); 73 | if (nPoints < BAR_MIN_POINTS) 74 | nPoints = BAR_MIN_POINTS; 75 | 76 | mBarWidth = -1; 77 | nBatchCount = 0; 78 | 79 | setAnimationSpeed(mAnimSpeed); 80 | 81 | mRandom = new Random(); 82 | 83 | mClipBounds = new Rect(); 84 | 85 | mSrcY = new float[nPoints]; 86 | mDestY = new float[nPoints]; 87 | 88 | } 89 | 90 | @Override 91 | public void setAnimationSpeed(AnimSpeed animSpeed) { 92 | super.setAnimationSpeed(animSpeed); 93 | mMaxBatchCount = AVConstants.MAX_ANIM_BATCH_COUNT - mAnimSpeed.ordinal(); 94 | } 95 | 96 | @Override 97 | protected void onDraw(Canvas canvas) { 98 | 99 | if (mBarWidth == -1) { 100 | 101 | canvas.getClipBounds(mClipBounds); 102 | 103 | mBarWidth = canvas.getWidth() / nPoints; 104 | 105 | //initialize points 106 | for (int i = 0; i < mSrcY.length; i++) { 107 | float posY; 108 | if (mPositionGravity == PositionGravity.TOP) 109 | posY = mClipBounds.top; 110 | else 111 | posY = mClipBounds.bottom; 112 | 113 | mSrcY[i] = posY; 114 | mDestY[i] = posY; 115 | } 116 | } 117 | 118 | //create the path and draw 119 | if (isVisualizationEnabled && mRawAudioBytes != null) { 120 | 121 | if (mRawAudioBytes.length == 0) { 122 | return; 123 | } 124 | 125 | //find the destination bezier point for a batch 126 | if (nBatchCount == 0) { 127 | float randPosY = mDestY[mRandom.nextInt(nPoints)]; 128 | for (int i = 0; i < mSrcY.length; i++) { 129 | 130 | int x = (int) Math.ceil((i + 1) * (mRawAudioBytes.length / nPoints)); 131 | int t = 0; 132 | if (x < 1024) 133 | t = canvas.getHeight() + 134 | ((byte) (Math.abs(mRawAudioBytes[x]) + 128)) * canvas.getHeight() / 128; 135 | 136 | float posY; 137 | if (mPositionGravity == PositionGravity.TOP) 138 | posY = mClipBounds.bottom - t; 139 | else 140 | posY = mClipBounds.top + t; 141 | 142 | //change the source and destination y 143 | mSrcY[i] = mDestY[i]; 144 | mDestY[i] = posY; 145 | } 146 | 147 | mDestY[mSrcY.length - 1] = randPosY; 148 | } 149 | 150 | //increment batch count 151 | nBatchCount++; 152 | 153 | //calculate bar position and draw 154 | for (int i = 0; i < mSrcY.length; i++) { 155 | float barY = mSrcY[i] + (((float) (nBatchCount) / mMaxBatchCount) * (mDestY[i] - mSrcY[i])); 156 | float barX = (i * mBarWidth) + (mBarWidth / 2); 157 | canvas.drawLine(barX, canvas.getHeight(), barX, barY, mPaint); 158 | } 159 | 160 | //reset the batch count 161 | if (nBatchCount == mMaxBatchCount) 162 | nBatchCount = 0; 163 | 164 | } 165 | 166 | super.onDraw(canvas); 167 | } 168 | } -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/visualizer/BlastVisualizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.visualizer; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.Path; 21 | import android.support.annotation.Nullable; 22 | import android.util.AttributeSet; 23 | 24 | import com.gauravk.audiovisualizer.base.BaseVisualizer; 25 | 26 | /** 27 | * Custom view to create blast visualizer 28 | *
29 | * Created by gk 30 | */ 31 | 32 | public class BlastVisualizer extends BaseVisualizer { 33 | 34 | private static final int BLAST_MAX_POINTS = 1000; 35 | private static final int BLAST_MIN_POINTS = 3; 36 | 37 | private Path mSpikePath; 38 | private int mRadius; 39 | private int nPoints; 40 | 41 | public BlastVisualizer(Context context) { 42 | super(context); 43 | } 44 | 45 | public BlastVisualizer(Context context, 46 | @Nullable AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | public BlastVisualizer(Context context, 51 | @Nullable AttributeSet attrs, 52 | int defStyleAttr) { 53 | super(context, attrs, defStyleAttr); 54 | } 55 | 56 | @Override 57 | protected void init() { 58 | mRadius = -1; 59 | nPoints = (int) (BLAST_MAX_POINTS * mDensity); 60 | if (nPoints < BLAST_MIN_POINTS) 61 | nPoints = BLAST_MIN_POINTS; 62 | 63 | mSpikePath = new Path(); 64 | } 65 | 66 | @Override 67 | protected void onDraw(Canvas canvas) { 68 | 69 | //first time initialization 70 | if (mRadius == -1) { 71 | mRadius = getHeight() < getWidth() ? getHeight() : getWidth(); 72 | mRadius = (int) (mRadius * 0.65 / 2); 73 | } 74 | 75 | //create the path and draw 76 | if (isVisualizationEnabled && mRawAudioBytes != null) { 77 | 78 | if (mRawAudioBytes.length == 0) { 79 | return; 80 | } 81 | 82 | mSpikePath.rewind(); 83 | 84 | double angle = 0; 85 | for (int i = 0; i < nPoints; i++, angle += (360.0f / nPoints)) { 86 | int x = (int) Math.ceil(i * (mRawAudioBytes.length / nPoints)); 87 | int t = 0; 88 | if (x < 1024) 89 | t = ((byte) (-Math.abs(mRawAudioBytes[x]) + 128)) * (canvas.getHeight() / 4) / 128; 90 | 91 | float posX = (float) (getWidth() / 2 92 | + (mRadius + t) 93 | * Math.cos(Math.toRadians(angle))); 94 | 95 | float posY = (float) (getHeight() / 2 96 | + (mRadius + t) 97 | * Math.sin(Math.toRadians(angle))); 98 | 99 | if (i == 0) 100 | mSpikePath.moveTo(posX, posY); 101 | else 102 | mSpikePath.lineTo(posX, posY); 103 | 104 | } 105 | mSpikePath.close(); 106 | 107 | canvas.drawPath(mSpikePath, mPaint); 108 | 109 | } 110 | 111 | super.onDraw(canvas); 112 | } 113 | } -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/visualizer/BlobVisualizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.visualizer; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.Path; 21 | import android.graphics.PointF; 22 | import android.support.annotation.Nullable; 23 | import android.util.AttributeSet; 24 | 25 | import com.gauravk.audiovisualizer.base.BaseVisualizer; 26 | import com.gauravk.audiovisualizer.model.AnimSpeed; 27 | import com.gauravk.audiovisualizer.model.PaintStyle; 28 | import com.gauravk.audiovisualizer.utils.BezierSpline; 29 | 30 | /** 31 | * Custom view to create blob visualizer 32 | *
33 | * Created by gk 34 | */ 35 | 36 | public class BlobVisualizer extends BaseVisualizer { 37 | 38 | private static final int BLOB_MAX_POINTS = 60; 39 | private static final int BLOB_MIN_POINTS = 3; 40 | 41 | private Path mBlobPath; 42 | private int mRadius; 43 | 44 | private int nPoints; 45 | 46 | private PointF[] mBezierPoints; 47 | private BezierSpline mBezierSpline; 48 | 49 | private float mAngleOffset; 50 | private float mChangeFactor; 51 | 52 | public BlobVisualizer(Context context) { 53 | super(context); 54 | } 55 | 56 | public BlobVisualizer(Context context, 57 | @Nullable AttributeSet attrs) { 58 | super(context, attrs); 59 | } 60 | 61 | public BlobVisualizer(Context context, 62 | @Nullable AttributeSet attrs, 63 | int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | } 66 | 67 | @Override 68 | protected void init() { 69 | mRadius = -1; 70 | nPoints = (int) (mDensity * BLOB_MAX_POINTS); 71 | if (nPoints < BLOB_MIN_POINTS) 72 | nPoints = BLOB_MIN_POINTS; 73 | 74 | mAngleOffset = (360.0f / nPoints); 75 | 76 | updateChangeFactor(mAnimSpeed, false); 77 | 78 | mBlobPath = new Path(); 79 | 80 | //initialize mBezierPoints, 2 extra for the smoothing first and last point 81 | mBezierPoints = new PointF[nPoints + 2]; 82 | for (int i = 0; i < mBezierPoints.length; i++) { 83 | mBezierPoints[i] = new PointF(); 84 | } 85 | 86 | mBezierSpline = new BezierSpline(mBezierPoints.length); 87 | } 88 | 89 | @Override 90 | public void setAnimationSpeed(AnimSpeed animSpeed) { 91 | super.setAnimationSpeed(animSpeed); 92 | updateChangeFactor(animSpeed, true); 93 | } 94 | 95 | private void updateChangeFactor(AnimSpeed animSpeed, boolean useHeight) { 96 | int height = 1; 97 | if (useHeight) 98 | height = getHeight() > 0 ? getHeight() : 1000; 99 | 100 | if (animSpeed == AnimSpeed.SLOW) 101 | mChangeFactor = height * 0.003f; 102 | else if (animSpeed == AnimSpeed.MEDIUM) 103 | mChangeFactor = height * 0.006f; 104 | else 105 | mChangeFactor = height * 0.01f; 106 | } 107 | 108 | @Override 109 | protected void onDraw(Canvas canvas) { 110 | 111 | double angle = 0; 112 | //first time initialization 113 | if (mRadius == -1) { 114 | mRadius = getHeight() < getWidth() ? getHeight() : getWidth(); 115 | mRadius = (int) (mRadius * 0.65 / 2); 116 | 117 | mChangeFactor = getHeight() * mChangeFactor; 118 | 119 | //initialize bezier points 120 | for (int i = 0; i < nPoints; i++, angle += mAngleOffset) { 121 | float posX = (float) (getWidth() / 2 122 | + (mRadius) 123 | * Math.cos(Math.toRadians(angle))); 124 | 125 | float posY = (float) (getHeight() / 2 126 | + (mRadius) 127 | * Math.sin(Math.toRadians(angle))); 128 | 129 | mBezierPoints[i].set(posX, posY); 130 | } 131 | } 132 | 133 | //create the path and draw 134 | if (isVisualizationEnabled && mRawAudioBytes != null) { 135 | 136 | if (mRawAudioBytes.length == 0) { 137 | return; 138 | } 139 | 140 | mBlobPath.rewind(); 141 | 142 | //find the destination bezier point for a batch 143 | for (int i = 0; i < nPoints; i++, angle += mAngleOffset) { 144 | 145 | int x = (int) Math.ceil((i + 1) * (mRawAudioBytes.length / nPoints)); 146 | int t = 0; 147 | if (x < 1024) 148 | t = ((byte) (-Math.abs(mRawAudioBytes[x]) + 128)) * (canvas.getHeight() / 4) / 128; 149 | 150 | float posX = (float) (getWidth() / 2 151 | + (mRadius + t) 152 | * Math.cos(Math.toRadians(angle))); 153 | 154 | float posY = (float) (getHeight() / 2 155 | + (mRadius + t) 156 | * Math.sin(Math.toRadians(angle))); 157 | 158 | //calculate the new x based on change 159 | if (posX - mBezierPoints[i].x > 0) { 160 | mBezierPoints[i].x += mChangeFactor; 161 | } else { 162 | mBezierPoints[i].x -= mChangeFactor; 163 | } 164 | 165 | //calculate the new y based on change 166 | if (posY - mBezierPoints[i].y > 0) { 167 | mBezierPoints[i].y += mChangeFactor; 168 | } else { 169 | mBezierPoints[i].y -= mChangeFactor; 170 | } 171 | } 172 | //set the first and last point as first 173 | mBezierPoints[nPoints].set(mBezierPoints[0].x, mBezierPoints[0].y); 174 | mBezierPoints[nPoints + 1].set(mBezierPoints[0].x, mBezierPoints[0].y); 175 | 176 | //update the control points 177 | mBezierSpline.updateCurveControlPoints(mBezierPoints); 178 | PointF[] firstCP = mBezierSpline.getFirstControlPoints(); 179 | PointF[] secondCP = mBezierSpline.getSecondControlPoints(); 180 | 181 | //create the path 182 | mBlobPath.moveTo(mBezierPoints[0].x, mBezierPoints[0].y); 183 | for (int i = 0; i < firstCP.length; i++) { 184 | mBlobPath.cubicTo(firstCP[i].x, firstCP[i].y, 185 | secondCP[i].x, secondCP[i].y, 186 | mBezierPoints[i + 1].x, mBezierPoints[i + 1].y); 187 | } 188 | //add an extra line to center cover the gap generated by last cubicTo 189 | if (mPaintStyle == PaintStyle.FILL) 190 | mBlobPath.lineTo(getWidth() / 2, getHeight() / 2); 191 | 192 | canvas.drawPath(mBlobPath, mPaint); 193 | 194 | } 195 | 196 | super.onDraw(canvas); 197 | } 198 | } -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/visualizer/CircleLineVisualizer.java: -------------------------------------------------------------------------------- 1 | package com.gauravk.audiovisualizer.visualizer; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.LinearGradient; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.graphics.Shader; 11 | import android.support.annotation.Nullable; 12 | import android.util.AttributeSet; 13 | 14 | import com.gauravk.audiovisualizer.base.BaseVisualizer; 15 | 16 | /** 17 | * @author maple on 2019/4/24 15:17. 18 | * @version v1.0 19 | * @see 1040441325@qq.com 20 | */ 21 | public class CircleLineVisualizer extends BaseVisualizer { 22 | private static final int BAR_MAX_POINTS = 240; 23 | private static final int BAR_MIN_POINTS = 30; 24 | private Rect mClipBounds; 25 | private int mPoints; 26 | private int mPointRadius; 27 | private float[] mSrcY; 28 | private int mRadius; 29 | private Paint mGPaint; 30 | private boolean drawLine; 31 | 32 | public CircleLineVisualizer(Context context) { 33 | super(context); 34 | } 35 | 36 | public CircleLineVisualizer(Context context, @Nullable AttributeSet attrs) { 37 | super(context, attrs); 38 | } 39 | 40 | public CircleLineVisualizer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | } 43 | 44 | public boolean isDrawLine() { 45 | return drawLine; 46 | } 47 | 48 | /** 49 | * control the display of drawLine 50 | * 51 | * @param drawLine is show drawLine 52 | */ 53 | public void setDrawLine(boolean drawLine) { 54 | this.drawLine = drawLine; 55 | } 56 | 57 | @Override 58 | protected void init() { 59 | mPoints = (int) (BAR_MAX_POINTS * mDensity); 60 | if (mPoints < BAR_MIN_POINTS) 61 | mPoints = BAR_MIN_POINTS; 62 | mSrcY = new float[mPoints]; 63 | mClipBounds = new Rect(); 64 | setAnimationSpeed(mAnimSpeed); 65 | mPaint.setAntiAlias(true); 66 | mGPaint = new Paint(); 67 | mGPaint.setAntiAlias(true); 68 | } 69 | 70 | @Override 71 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 72 | super.onSizeChanged(w, h, oldw, oldh); 73 | mRadius = Math.min(w, h) / 4; 74 | mPointRadius = Math.abs((int) (2 * mRadius * Math.sin(Math.PI / mPoints / 3))); 75 | LinearGradient lg = new LinearGradient(getWidth() / 2 + mRadius, getHeight() / 2, getWidth() / 2 + mRadius + mPointRadius * 5, getHeight() / 2 76 | , Color.parseColor("#77FF5722"), Color.parseColor("#10FF5722"), Shader.TileMode.CLAMP); 77 | mGPaint.setShader(lg); 78 | } 79 | 80 | @Override 81 | protected void onDraw(Canvas canvas) { 82 | super.onDraw(canvas); 83 | canvas.getClipBounds(mClipBounds); 84 | updateData(); 85 | // draw circle's points 86 | for (int i = 0; i < 360; i = i + 360 / mPoints) { 87 | float cx = (float) (getWidth() / 2 + Math.cos(i * Math.PI / 180) * mRadius); 88 | float cy = (float) (getHeight() / 2 - Math.sin(i * Math.PI / 180) * mRadius); 89 | canvas.drawCircle(cx, cy, mPointRadius, mPaint); 90 | } 91 | // draw lines 92 | if (drawLine) drawLines(canvas); 93 | // draw bar 94 | for (int i = 0; i < 360; i = i + 360 / mPoints) { 95 | if (mSrcY[i * mPoints / 360] == 0) continue; 96 | canvas.save(); 97 | canvas.rotate(-i, getWidth() / 2, getHeight() / 2); 98 | float cx = (float) (getWidth() / 2 + mRadius); 99 | float cy = (float) (getHeight() / 2); 100 | canvas.drawRect(cx, cy - mPointRadius, cx + mSrcY[i * mPoints / 360], 101 | cy + mPointRadius, mPaint); 102 | canvas.drawCircle(cx + mSrcY[i * mPoints / 360], cy, mPointRadius, mPaint); 103 | canvas.restore(); 104 | } 105 | } 106 | 107 | /** 108 | * Draw a translucent ray 109 | * 110 | * @param canvas target canvas 111 | */ 112 | private void drawLines(Canvas canvas) { 113 | int lineLen = 14 * mPointRadius;//default len, 114 | for (int i = 0; i < 360; i = i + 360 / mPoints) { 115 | canvas.save(); 116 | canvas.rotate(-i, getWidth() / 2, getHeight() / 2); 117 | float cx = (float) (getWidth() / 2 + mRadius) + mSrcY[i * mPoints / 360]; 118 | float cy = (float) (getHeight() / 2); 119 | Path path = new Path(); 120 | path.moveTo(cx, cy + mPointRadius); 121 | path.lineTo(cx, cy - mPointRadius); 122 | path.lineTo(cx + lineLen, cy); 123 | canvas.drawPath(path, mGPaint); 124 | canvas.restore(); 125 | } 126 | } 127 | 128 | private void updateData() { 129 | if (isVisualizationEnabled && mRawAudioBytes != null) { 130 | if (mRawAudioBytes.length == 0) return; 131 | for (int i = 0; i < mSrcY.length; i++) { 132 | int x = (int) Math.ceil((i + 1) * (mRawAudioBytes.length / mPoints)); 133 | int t = 0; 134 | if (x < 1024) { 135 | t = ((byte) (Math.abs(mRawAudioBytes[x]) + 128)) * mRadius / 128; 136 | } 137 | mSrcY[i] = -t; 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/visualizer/HiFiVisualizer.java: -------------------------------------------------------------------------------- 1 | package com.gauravk.audiovisualizer.visualizer; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.support.annotation.Nullable; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.MotionEvent; 11 | 12 | import com.gauravk.audiovisualizer.base.BaseVisualizer; 13 | import com.gauravk.audiovisualizer.model.PaintStyle; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | 18 | /** 19 | * @author maple on 2019/4/25 10:17. 20 | * @version v1.0 21 | * @see 1040441325@qq.com 22 | */ 23 | public class HiFiVisualizer extends BaseVisualizer { 24 | private static final int BAR_MAX_POINTS = 240; 25 | private static final int BAR_MIN_POINTS = 30; 26 | private static final float PER_RADIUS = .65f; 27 | private int mRadius; 28 | private int mPoints; 29 | private int[] mHeights; 30 | private Path mPath;//outward path 31 | private Path mPath1;//inward path 32 | /** 33 | * This is the distance from center to bezier control point. 34 | * We can calculate the bezier control points of each segment this distance and its angle; 35 | */ 36 | private int mBezierControlPointLen; 37 | 38 | public HiFiVisualizer(Context context) { 39 | super(context); 40 | } 41 | 42 | public HiFiVisualizer(Context context, @Nullable AttributeSet attrs) { 43 | super(context, attrs); 44 | } 45 | 46 | public HiFiVisualizer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 47 | super(context, attrs, defStyleAttr); 48 | } 49 | 50 | @Override 51 | protected void init() { 52 | mRadius = -1; 53 | mPath = new Path(); 54 | mPath1 = new Path(); 55 | mPaint.setStyle(Paint.Style.STROKE); 56 | mPaint.setAntiAlias(true); 57 | mPaint.setStrokeWidth(1.0f); 58 | mPoints = (int) (BAR_MAX_POINTS * mDensity); 59 | if (mPoints < BAR_MIN_POINTS) mPoints = BAR_MIN_POINTS; 60 | mHeights = new int[mPoints]; 61 | } 62 | 63 | /** 64 | * you cannot change the style of paint; 65 | * the paintStyle fixed at Paint.Style.STROKE: 66 | * 67 | * @param paintStyle style of the visualizer. 68 | */ 69 | @Override 70 | @Deprecated() 71 | public void setPaintStyle(PaintStyle paintStyle) { 72 | 73 | } 74 | 75 | @Override 76 | protected void onDraw(Canvas canvas) { 77 | if (mRadius == -1) { 78 | mRadius = (int) (Math.min(getWidth(), getHeight()) / 2 * PER_RADIUS); 79 | mBezierControlPointLen = (int) (mRadius / Math.cos(Math.PI / mPoints)); 80 | } 81 | updateData(); 82 | mPath.reset(); 83 | mPath1.reset(); 84 | // start the outward path from the last point 85 | float cxL = (float) (getWidth() / 2 + Math.cos((360 - 360 / mPoints) * Math.PI / 180) * (mRadius + mHeights[mPoints - 1])); 86 | float cyL = (float) (getHeight() / 2 - Math.sin((360 - 360 / mPoints) * Math.PI / 180) * (mRadius + mHeights[mPoints - 1])); 87 | mPath.moveTo(cxL, cyL); 88 | // start the inward path from the last point 89 | float cxL1 = (float) (getWidth() / 2 + Math.cos((360 - 360 / mPoints) * Math.PI / 180) * (mRadius - mHeights[mPoints - 1])); 90 | float cyL1 = (float) (getHeight() / 2 - Math.sin((360 - 360 / mPoints) * Math.PI / 180) * (mRadius - mHeights[mPoints - 1])); 91 | mPath1.moveTo(cxL1, cyL1); 92 | for (int i = 0; i < 360; i = i + 360 / mPoints) { 93 | // outward 94 | // the next point of path 95 | float cx = (float) (getWidth() / 2 + Math.cos(i * Math.PI / 180) * (mRadius + mHeights[i * mPoints / 360])); 96 | float cy = (float) (getHeight() / 2 - Math.sin(i * Math.PI / 180) * (mRadius + mHeights[i * mPoints / 360])); 97 | //second bezier control point 98 | float bx = (float) (getWidth() / 2 + Math.cos((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen + mHeights[i * mPoints / 360])); 99 | float by = (float) (getHeight() / 2 - Math.sin((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen + mHeights[i * mPoints / 360])); 100 | int lastPoint = i == 0 ? mPoints - 1 : i * mPoints / 360 - 1; 101 | //fist bezier control point 102 | float ax = (float) (getWidth() / 2 + Math.cos((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen + mHeights[lastPoint])); 103 | float ay = (float) (getHeight() / 2 - Math.sin((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen + mHeights[lastPoint])); 104 | mPath.cubicTo(ax, ay, bx, by, cx, cy); 105 | // inward 106 | float cx1 = (float) (getWidth() / 2 + Math.cos(i * Math.PI / 180) * (mRadius - mHeights[i * mPoints / 360])); 107 | float cy1 = (float) (getHeight() / 2 - Math.sin(i * Math.PI / 180) * (mRadius - mHeights[i * mPoints / 360])); 108 | float bx1 = (float) (getWidth() / 2 + Math.cos((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen - mHeights[i * mPoints / 360])); 109 | float by1 = (float) (getHeight() / 2 - Math.sin((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen - mHeights[i * mPoints / 360])); 110 | float ax1 = (float) (getWidth() / 2 + Math.cos((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen - mHeights[lastPoint])); 111 | float ay1 = (float) (getHeight() / 2 - Math.sin((i - (180 / mPoints)) * Math.PI / 180) * (mBezierControlPointLen - mHeights[lastPoint])); 112 | mPath1.cubicTo(ax1, ay1, bx1, by1, cx1, cy1); 113 | canvas.drawLine(cx, cy, cx1, cy1, mPaint); 114 | } 115 | canvas.drawPath(mPath, mPaint); 116 | canvas.drawPath(mPath1, mPaint); 117 | } 118 | 119 | private void updateData() { 120 | if (isVisualizationEnabled && mRawAudioBytes != null) { 121 | if (mRawAudioBytes.length == 0) return; 122 | for (int i = 0; i < mHeights.length; i++) { 123 | int x = (int) Math.ceil((i + 1) * (mRawAudioBytes.length / mPoints)); 124 | int t = 0; 125 | if (x < 1024) 126 | t = ((byte) (Math.abs(mRawAudioBytes[x]) + 128)) * mRadius / 128; 127 | mHeights[i] = -t; 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /audiovisualizer/src/main/java/com/gauravk/audiovisualizer/visualizer/WaveVisualizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Gaurav Kumar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.gauravk.audiovisualizer.visualizer; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.Path; 21 | import android.graphics.PointF; 22 | import android.graphics.Rect; 23 | import android.support.annotation.Nullable; 24 | import android.util.AttributeSet; 25 | 26 | import com.gauravk.audiovisualizer.base.BaseVisualizer; 27 | import com.gauravk.audiovisualizer.model.AnimSpeed; 28 | import com.gauravk.audiovisualizer.model.PaintStyle; 29 | import com.gauravk.audiovisualizer.model.PositionGravity; 30 | import com.gauravk.audiovisualizer.utils.AVConstants; 31 | 32 | import java.util.Random; 33 | 34 | /** 35 | * Custom view to create wave visualizer 36 | *
37 | * Created by gk
38 | */
39 |
40 | public class WaveVisualizer extends BaseVisualizer {
41 |
42 | private static final int WAVE_MAX_POINTS = 54;
43 | private static final int WAVE_MIN_POINTS = 3;
44 |
45 | private int mMaxBatchCount;
46 |
47 | private Path mWavePath;
48 |
49 | private int nPoints;
50 |
51 | private PointF[] mBezierPoints, mBezierControlPoints1, mBezierControlPoints2;
52 |
53 | private float[] mSrcY, mDestY;
54 |
55 | private float mWidthOffset;
56 | private Rect mClipBounds;
57 |
58 | private int nBatchCount;
59 |
60 | private Random mRandom;
61 |
62 | public WaveVisualizer(Context context) {
63 | super(context);
64 | }
65 |
66 | public WaveVisualizer(Context context,
67 | @Nullable AttributeSet attrs) {
68 | super(context, attrs);
69 | }
70 |
71 | public WaveVisualizer(Context context,
72 | @Nullable AttributeSet attrs,
73 | int defStyleAttr) {
74 | super(context, attrs, defStyleAttr);
75 | }
76 |
77 | @Override
78 | protected void init() {
79 | nPoints = (int) (WAVE_MAX_POINTS * mDensity);
80 | if (nPoints < WAVE_MIN_POINTS)
81 | nPoints = WAVE_MIN_POINTS;
82 |
83 | mWidthOffset = -1;
84 | nBatchCount = 0;
85 |
86 | setAnimationSpeed(mAnimSpeed);
87 |
88 | mRandom = new Random();
89 |
90 | mClipBounds = new Rect();
91 |
92 | mWavePath = new Path();
93 |
94 | mSrcY = new float[nPoints + 1];
95 | mDestY = new float[nPoints + 1];
96 |
97 | //initialize mBezierPoints
98 | mBezierPoints = new PointF[nPoints + 1];
99 | mBezierControlPoints1 = new PointF[nPoints + 1];
100 | mBezierControlPoints2 = new PointF[nPoints + 1];
101 | for (int i = 0; i < mBezierPoints.length; i++) {
102 | mBezierPoints[i] = new PointF();
103 | mBezierControlPoints1[i] = new PointF();
104 | mBezierControlPoints2[i] = new PointF();
105 | }
106 |
107 | }
108 |
109 | @Override
110 | public void setAnimationSpeed(AnimSpeed animSpeed) {
111 | super.setAnimationSpeed(animSpeed);
112 | this.mMaxBatchCount = AVConstants.MAX_ANIM_BATCH_COUNT - mAnimSpeed.ordinal();
113 | }
114 |
115 | @Override
116 | protected void onDraw(Canvas canvas) {
117 |
118 | if (mWidthOffset == -1) {
119 |
120 | canvas.getClipBounds(mClipBounds);
121 |
122 | mWidthOffset = canvas.getWidth() / nPoints;
123 |
124 | //initialize bezier points
125 | for (int i = 0; i < mBezierPoints.length; i++) {
126 | float posX = mClipBounds.left + (i * mWidthOffset);
127 |
128 | float posY;
129 | if (mPositionGravity == PositionGravity.TOP)
130 | posY = mClipBounds.top;
131 | else
132 | posY = mClipBounds.bottom;
133 |
134 | mSrcY[i] = posY;
135 | mDestY[i] = posY;
136 | mBezierPoints[i].set(posX, posY);
137 | }
138 | }
139 |
140 | //create the path and draw
141 | if (isVisualizationEnabled && mRawAudioBytes != null) {
142 |
143 | if (mRawAudioBytes.length == 0) {
144 | return;
145 | }
146 |
147 | mWavePath.rewind();
148 |
149 | //find the destination bezier point for a batch
150 | if (nBatchCount == 0) {
151 |
152 | float randPosY = mDestY[mRandom.nextInt(nPoints)];
153 | for (int i = 0; i < mBezierPoints.length; i++) {
154 |
155 | int x = (int) Math.ceil((i + 1) * (mRawAudioBytes.length / nPoints));
156 |
157 | int t = 0;
158 | if (x < 1024)
159 | t = canvas.getHeight() +
160 | ((byte) (Math.abs(mRawAudioBytes[x]) + 128)) * canvas.getHeight() / 128;
161 |
162 | float posY;
163 | if (mPositionGravity == PositionGravity.TOP)
164 | posY = mClipBounds.bottom - t;
165 | else
166 | posY = mClipBounds.top + t;
167 |
168 | //change the source and destination y
169 | mSrcY[i] = mDestY[i];
170 | mDestY[i] = posY;
171 | }
172 |
173 | mDestY[mBezierPoints.length - 1] = randPosY;
174 | }
175 |
176 | //increment batch count
177 | nBatchCount++;
178 |
179 | //for smoothing animation
180 | for (int i = 0; i < mBezierPoints.length; i++) {
181 | mBezierPoints[i].y = mSrcY[i] + (((float) (nBatchCount) / mMaxBatchCount) * (mDestY[i] - mSrcY[i]));
182 | }
183 |
184 | //reset the batch count
185 | if (nBatchCount == mMaxBatchCount)
186 | nBatchCount = 0;
187 |
188 | //calculate the bezier curve control points
189 | for (int i = 1; i < mBezierPoints.length; i++) {
190 | mBezierControlPoints1[i].set((mBezierPoints[i].x + mBezierPoints[i - 1].x) / 2, mBezierPoints[i - 1].y);
191 | mBezierControlPoints2[i].set((mBezierPoints[i].x + mBezierPoints[i - 1].x) / 2, mBezierPoints[i].y);
192 | }
193 |
194 | //create the path
195 | mWavePath.moveTo(mBezierPoints[0].x, mBezierPoints[0].y);
196 | for (int i = 1; i < mBezierPoints.length; i++) {
197 | mWavePath.cubicTo(mBezierControlPoints1[i].x, mBezierControlPoints1[i].y,
198 | mBezierControlPoints2[i].x, mBezierControlPoints2[i].y,
199 | mBezierPoints[i].x, mBezierPoints[i].y);
200 | }
201 |
202 | //add last 3 line to close the view
203 | //mWavePath.lineTo(mClipBounds.right, mBezierPoints[0].y);
204 | if (mPaintStyle == PaintStyle.FILL) {
205 | mWavePath.lineTo(mClipBounds.right, mClipBounds.bottom);
206 | mWavePath.lineTo(mClipBounds.left, mClipBounds.bottom);
207 | mWavePath.close();
208 | }
209 |
210 | canvas.drawPath(mWavePath, mPaint);
211 | }
212 |
213 | super.onDraw(canvas);
214 | }
215 | }
--------------------------------------------------------------------------------
/audiovisualizer/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |