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