• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
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 
17 package com.android.musicvis.vis5;
18 
19 import com.android.musicvis.R;
20 import com.android.musicvis.RenderScriptScene;
21 import com.android.musicvis.AudioCapture;
22 
23 import android.os.Handler;
24 import android.renderscript.Allocation;
25 import android.renderscript.Element;
26 import android.renderscript.Primitive;
27 import android.renderscript.ProgramFragment;
28 import android.renderscript.ProgramStore;
29 import android.renderscript.ProgramVertex;
30 import android.renderscript.Sampler;
31 import android.renderscript.ScriptC;
32 import android.renderscript.SimpleMesh;
33 import android.renderscript.Type;
34 import android.renderscript.Element.Builder;
35 import android.renderscript.ProgramStore.BlendDstFunc;
36 import android.renderscript.ProgramStore.BlendSrcFunc;
37 import android.renderscript.Sampler.Value;
38 import android.util.Log;
39 import android.view.MotionEvent;
40 
41 import java.util.TimeZone;
42 
43 class Visualization5RS extends RenderScriptScene {
44 
45     private final Handler mHandler = new Handler();
46     private final Runnable mDrawCube = new Runnable() {
47         public void run() {
48             updateWave();
49         }
50     };
51     private boolean mVisible;
52 
53     private int mNeedlePos = 0;
54     private int mNeedleSpeed = 0;
55     // tweak this to get quicker/slower response
56     private int mNeedleMass = 10;
57     private int mSpringForceAtOrigin = 200;
58 
59     static class WorldState {
60         public float mAngle;
61         public int   mPeak;
62         public float mRotate;
63         public float mTilt;
64         public int   mIdle;
65         public int   mWaveCounter;
66     }
67     WorldState mWorldState = new WorldState();
68     private Type mStateType;
69     private Allocation mState;
70 
71     private ProgramStore mPfsBackground;
72     private ProgramFragment mPfBackgroundMip;
73     private ProgramFragment mPfBackgroundNoMip;
74     private Sampler mSamplerMip;
75     private Sampler mSamplerNoMip;
76     private Allocation[] mTextures;
77 
78     private ProgramVertex mPVBackground;
79     private ProgramVertex.MatrixAllocation mPVAlloc;
80 
81     private SimpleMesh mCubeMesh;
82 
83     protected Allocation mPointAlloc;
84     // 256 lines, with 4 points per line (2 space, 2 texture) each consisting of x and y,
85     // so 8 floats per line.
86     protected float [] mPointData = new float[256*8];
87 
88     private Allocation mLineIdxAlloc;
89     // 2 indices per line
90     private short [] mIndexData = new short[256*2];
91 
92     private AudioCapture mAudioCapture = null;
93     private int [] mVizData = new int[1024];
94 
95     private static final int RSID_STATE = 0;
96     private static final int RSID_POINTS = 1;
97     private static final int RSID_LINES = 2;
98     private static final int RSID_PROGRAMVERTEX = 3;
99 
100     private float mTouchY;
101 
Visualization5RS(int width, int height)102     Visualization5RS(int width, int height) {
103         super(width, height);
104         mWidth = width;
105         mHeight = height;
106         // the x, s and t coordinates don't change, so set those now
107         int outlen = mPointData.length / 8;
108         int half = outlen / 2;
109         for(int i = 0; i < outlen; i++) {
110             mPointData[i*8]   = i - half;          // start point X (Y set later)
111             mPointData[i*8+2] = 0;                 // start point S
112             mPointData[i*8+3] = 0;                 // start point T
113             mPointData[i*8+4] = i - half;          // end point X (Y set later)
114             mPointData[i*8+6] = 1.0f;              // end point S
115             mPointData[i*8+7] = 0f;                // end point T
116         }
117     }
118 
119     @Override
resize(int width, int height)120     public void resize(int width, int height) {
121         super.resize(width, height);
122         if (mPVAlloc != null) {
123             mPVAlloc.setupProjectionNormalized(width, height);
124         }
125         mWorldState.mTilt = -20;
126     }
127 
128     @Override
onTouchEvent(MotionEvent event)129     public void onTouchEvent(MotionEvent event) {
130         switch(event.getAction()) {
131             case MotionEvent.ACTION_DOWN:
132                 mTouchY = event.getY();
133                 break;
134             case MotionEvent.ACTION_MOVE:
135                 float dy = event.getY() - mTouchY;
136                 mTouchY += dy;
137                 dy /= 10;
138                 dy += mWorldState.mTilt;
139                 if (dy > 0) {
140                     dy = 0;
141                 } else if (dy < -45) {
142                     dy = -45;
143                 }
144                 mWorldState.mTilt = dy;
145                 mState.data(mWorldState);
146         }
147     }
148 
149     @Override
setOffset(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels)150     public void setOffset(float xOffset, float yOffset,
151             float xStep, float yStep, int xPixels, int yPixels) {
152         // update our state, then push it to the renderscript
153         mWorldState.mRotate = (xOffset - 0.5f) * 90;
154         mState.data(mWorldState);
155     }
156 
157     @Override
createScript()158     protected ScriptC createScript() {
159 
160         // Create a renderscript type from a java class. The specified name doesn't
161         // really matter; the name by which we refer to the object in RenderScript
162         // will be specified later.
163         mStateType = Type.createFromClass(mRS, WorldState.class, 1, "WorldState");
164         // Create an allocation from the type we just created.
165         mState = Allocation.createTyped(mRS, mStateType);
166 
167         // First set up the coordinate system and such
168         ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
169         mPVBackground = pvb.create();
170         mPVBackground.setName("PVBackground");
171         mPVAlloc = new ProgramVertex.MatrixAllocation(mRS);
172         mPVBackground.bindAllocation(mPVAlloc);
173         mPVAlloc.setupProjectionNormalized(mWidth, mHeight);
174 
175         mTextures = new Allocation[8];
176         mTextures[0] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.background, Element.RGBA_8888(mRS), true);
177         mTextures[0].setName("Tvumeter_background");
178         mTextures[1] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.frame, Element.RGBA_8888(mRS), true);
179         mTextures[1].setName("Tvumeter_frame");
180         mTextures[2] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_on, Element.RGBA_8888(mRS), true);
181         mTextures[2].setName("Tvumeter_peak_on");
182         mTextures[3] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_off, Element.RGBA_8888(mRS), true);
183         mTextures[3].setName("Tvumeter_peak_off");
184         mTextures[4] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.needle, Element.RGBA_8888(mRS), true);
185         mTextures[4].setName("Tvumeter_needle");
186         mTextures[5] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.black, Element.RGB_565(mRS), false);
187         mTextures[5].setName("Tvumeter_black");
188         mTextures[6] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.albumart, Element.RGBA_8888(mRS), true);
189         mTextures[6].setName("Tvumeter_album");
190         mTextures[7] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.fire, Element.RGB_565(mRS), false);
191         mTextures[7].setName("Tlinetexture");
192 
193         final int count = mTextures.length;
194         for (int i = 0; i < count; i++) {
195             mTextures[i].uploadToTexture(0);
196         }
197 
198         {
199             Sampler.Builder builder = new Sampler.Builder(mRS);
200             builder.setMin(Value.LINEAR);
201             builder.setMag(Value.LINEAR);
202             builder.setWrapS(Value.WRAP);
203             builder.setWrapT(Value.WRAP);
204             mSamplerNoMip = builder.create();
205         }
206 
207         {
208             Sampler.Builder builder = new Sampler.Builder(mRS);
209             builder.setMin(Value.LINEAR_MIP_LINEAR);
210             builder.setMag(Value.LINEAR);
211             builder.setWrapS(Value.WRAP);
212             builder.setWrapT(Value.WRAP);
213             mSamplerMip = builder.create();
214         }
215 
216         {
217             ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS);
218             builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE,
219                                ProgramFragment.Builder.Format.RGBA, 0);
220             mPfBackgroundNoMip = builder.create();
221             mPfBackgroundNoMip.setName("PFBackgroundNoMip");
222             mPfBackgroundNoMip.bindSampler(mSamplerNoMip, 0);
223         }
224 
225         {
226             ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS);
227             builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE,
228                                ProgramFragment.Builder.Format.RGBA, 0);
229             mPfBackgroundMip = builder.create();
230             mPfBackgroundMip.setName("PFBackgroundMip");
231             mPfBackgroundMip.bindSampler(mSamplerMip, 0);
232         }
233 
234         {
235             ProgramStore.Builder builder = new ProgramStore.Builder(mRS, null, null);
236             builder.setDepthFunc(ProgramStore.DepthFunc.EQUAL);
237             //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
238             builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
239             builder.setDitherEnable(true); // without dithering there is severe banding
240             builder.setDepthMask(false);
241             mPfsBackground = builder.create();
242             mPfsBackground.setName("PFSBackground");
243         }
244 
245         // Start creating the mesh
246         final SimpleMesh.Builder meshBuilder = new SimpleMesh.Builder(mRS);
247 
248         // Create the Element for the points
249         Builder elementBuilder = new Builder(mRS);
250         elementBuilder.add(Element.ATTRIB_POSITION_2(mRS), "position");
251         elementBuilder.add(Element.ATTRIB_TEXTURE_2(mRS), "texture");
252         final Element vertexElement = elementBuilder.create();
253         final int vertexSlot = meshBuilder.addVertexType(vertexElement, mPointData.length / 4);
254         // Specify the type and number of indices we need. We'll allocate them later.
255         meshBuilder.setIndexType(Element.INDEX_16(mRS), mIndexData.length);
256         // This will be a line mesh
257         meshBuilder.setPrimitive(Primitive.LINE);
258 
259         // Create the Allocation for the vertices
260         mCubeMesh = meshBuilder.create();
261         mCubeMesh.setName("CubeMesh");
262         mPointAlloc = mCubeMesh.createVertexAllocation(vertexSlot);
263         mPointAlloc.setName("PointBuffer");
264 
265         // Create the Allocation for the indices
266         mLineIdxAlloc = mCubeMesh.createIndexAllocation();
267 
268         // Bind the allocations to the mesh
269         mCubeMesh.bindVertexAllocation(mPointAlloc, 0);
270         mCubeMesh.bindIndexAllocation(mLineIdxAlloc);
271 
272         /*
273          *  put the vertex and index data in their respective buffers
274          */
275         updateWave();
276         for(int i = 0; i < mIndexData.length; i ++) {
277             mIndexData[i] = (short) i;
278         }
279 
280         /*
281          *  upload the vertex and index data
282          */
283         mPointAlloc.data(mPointData);
284         mPointAlloc.uploadToBufferObject();
285         mLineIdxAlloc.data(mIndexData);
286         mLineIdxAlloc.uploadToBufferObject();
287 
288         // Time to create the script
289         ScriptC.Builder sb = new ScriptC.Builder(mRS);
290         // Specify the name by which to refer to the WorldState object in the
291         // renderscript.
292         sb.setType(mStateType, "State", RSID_STATE);
293         sb.setScript(mResources, R.raw.many);
294         sb.setRoot(true);
295 
296         ScriptC script = sb.create();
297         script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
298         script.setTimeZone(TimeZone.getDefault().getID());
299 
300         script.bindAllocation(mState, RSID_STATE);
301         script.bindAllocation(mPointAlloc, RSID_POINTS);
302         script.bindAllocation(mLineIdxAlloc, RSID_LINES);
303         script.bindAllocation(mPVAlloc.mAlloc, RSID_PROGRAMVERTEX);
304 
305         return script;
306     }
307 
308     @Override
start()309     public void start() {
310         super.start();
311         mVisible = true;
312         if (mAudioCapture == null) {
313             mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024);
314         }
315         mAudioCapture.start();
316         updateWave();
317     }
318 
319     @Override
stop()320     public void stop() {
321         super.stop();
322         mVisible = false;
323         if (mAudioCapture != null) {
324             mAudioCapture.stop();
325             mAudioCapture.release();
326             mAudioCapture = null;
327         }
328     }
329 
updateWave()330     void updateWave() {
331         mHandler.removeCallbacks(mDrawCube);
332         if (!mVisible) {
333             return;
334         }
335         mHandler.postDelayed(mDrawCube, 20);
336 
337         int len = 0;
338         if (mAudioCapture != null) {
339             // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit)
340             mVizData = mAudioCapture.getFormattedData(512, 1);
341             len = mVizData.length;
342         }
343 
344         // Simulate running the signal through a rectifier by
345         // taking the average of the absolute sample values.
346         int volt = 0;
347         if (len > 0) {
348             for (int i = 0; i < len; i++) {
349                 int val = mVizData[i];
350                 if (val < 0) {
351                     val = -val;
352                 }
353                 volt += val;
354             }
355             volt = volt / len;
356         }
357 
358         // There are several forces working on the needle: a force applied by the
359         // electromagnet, a force applied by the spring,  and friction.
360         // The force from the magnet is proportional to the current flowing
361         // through its coil. We have to take in to account that the coil is an
362         // inductive load, which means that an immediate change in applied voltage
363         // will result in a gradual change in current, but also that current will
364         // be induced by the movement of the needle.
365         // The force from the spring is proportional to the position of the needle.
366         // The friction force is a function of the speed of the needle, but so is
367         // the current induced by the movement of the needle, so we can combine
368         // them.
369 
370 
371         // Add up the various forces, with some multipliers to make the movement
372         // of the needle more realistic
373         // 'volt' is for the applied voltage, which causes a current to flow through the coil
374         // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current
375         // in the coil, and is also proportional to the friction
376         // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back
377         int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ;
378         int acceleration = netforce / mNeedleMass;
379         mNeedleSpeed += acceleration;
380         mNeedlePos += mNeedleSpeed;
381         if (mNeedlePos < 0) {
382             mNeedlePos = 0;
383             mNeedleSpeed = 0;
384         } else if (mNeedlePos > 32767) {
385             if (mNeedlePos > 33333) {
386                  mWorldState.mPeak = 10;
387             }
388             mNeedlePos = 32767;
389             mNeedleSpeed = 0;
390         }
391         if (mWorldState.mPeak > 0) {
392             mWorldState.mPeak--;
393         }
394 
395         mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range
396 
397         // downsample 1024 samples in to 256
398 
399         if (len == 0) {
400             if (mWorldState.mIdle == 0) {
401                 mWorldState.mIdle = 1;
402             }
403         } else {
404             if (mWorldState.mIdle != 0) {
405                 mWorldState.mIdle = 0;
406             }
407             // TODO: might be more efficient to push this in to renderscript
408             int outlen = mPointData.length / 8;
409             len /= 4;
410             if (len > outlen) len = outlen;
411             for(int i = 0; i < len; i++) {
412                 int amp = (mVizData[i*4]  + mVizData[i*4+1] + mVizData[i*4+2] + mVizData[i*4+3]);
413                 mPointData[i*8+1] = amp;
414                 mPointData[i*8+5] = -amp;
415             }
416             mPointAlloc.data(mPointData);
417             mWorldState.mWaveCounter++;
418         }
419 
420         mState.data(mWorldState);
421     }
422 }
423