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
17package com.android.musicvis.vis5;
18
19import com.android.musicvis.R;
20import com.android.musicvis.RenderScriptScene;
21import com.android.musicvis.ScriptField_Vertex;
22import com.android.musicvis.AudioCapture;
23
24import android.os.Handler;
25import android.renderscript.*;
26import android.renderscript.Element.Builder;
27import android.renderscript.ProgramStore.BlendDstFunc;
28import android.renderscript.ProgramStore.BlendSrcFunc;
29import android.renderscript.Sampler.Value;
30import android.util.Log;
31import android.view.MotionEvent;
32
33import java.util.TimeZone;
34
35class 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
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
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
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
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
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
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
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
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