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.vis4;
18
19import static android.renderscript.ProgramStore.DepthFunc.ALWAYS;
20import static android.renderscript.Sampler.Value.LINEAR;
21import static android.renderscript.Sampler.Value.WRAP;
22
23import com.android.musicvis.R;
24import com.android.musicvis.RenderScriptScene;
25import com.android.musicvis.AudioCapture;
26
27import android.os.Handler;
28import android.renderscript.*;
29import android.renderscript.ProgramStore.BlendDstFunc;
30import android.renderscript.ProgramStore.BlendSrcFunc;
31
32import java.util.TimeZone;
33import android.util.Log;
34
35class Visualization4RS 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    }
55    WorldState mWorldState = new WorldState();
56
57    ScriptC_vu mScript;
58
59    private ProgramStore mPfsBackground;
60    private ProgramFragment mPfBackground;
61    private Sampler mSampler;
62    private Allocation[] mTextures;
63
64    private ProgramVertex mPVBackground;
65    private ProgramVertexFixedFunction.Constants mPVAlloc;
66
67    private AudioCapture mAudioCapture = null;
68    private int [] mVizData = new int[1024];
69
70    private static final int RSID_STATE = 0;
71    private static final int RSID_POINTS = 1;
72    private static final int RSID_LINES = 2;
73    private static final int RSID_PROGRAMVERTEX = 3;
74
75
76    Visualization4RS(int width, int height) {
77        super(width, height);
78        mWidth = width;
79        mHeight = height;
80    }
81
82    @Override
83    public void resize(int width, int height) {
84        super.resize(width, height);
85        if (mPVAlloc != null) {
86            Matrix4f proj = new Matrix4f();
87            proj.loadProjectionNormalized(width, height);
88            mPVAlloc.setProjection(proj);
89        }
90    }
91
92    @Override
93    protected ScriptC createScript() {
94
95        mScript = new ScriptC_vu(mRS, mResources, R.raw.vu);
96
97        // First set up the coordinate system and such
98        ProgramVertexFixedFunction.Builder pvb = new ProgramVertexFixedFunction.Builder(mRS);
99        mPVBackground = pvb.create();
100        mPVAlloc = new ProgramVertexFixedFunction.Constants(mRS);
101        ((ProgramVertexFixedFunction)mPVBackground).bindConstants(mPVAlloc);
102        Matrix4f proj = new Matrix4f();
103        proj.loadProjectionNormalized(mWidth, mHeight);
104        mPVAlloc.setProjection(proj);
105
106        mScript.set_gPVBackground(mPVBackground);
107
108        updateWave();
109
110        mTextures = new Allocation[6];
111        mTextures[0] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.background,
112                                           Allocation.MipmapControl.MIPMAP_NONE,
113                                           Allocation.USAGE_GRAPHICS_TEXTURE);
114        mScript.set_gTvumeter_background(mTextures[0]);
115        mTextures[1] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.frame,
116                                           Allocation.MipmapControl.MIPMAP_NONE,
117                                           Allocation.USAGE_GRAPHICS_TEXTURE);
118        mScript.set_gTvumeter_frame(mTextures[1]);
119        mTextures[2] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_on,
120                                           Allocation.MipmapControl.MIPMAP_NONE,
121                                           Allocation.USAGE_GRAPHICS_TEXTURE);
122        mScript.set_gTvumeter_peak_on(mTextures[2]);
123        mTextures[3] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_off,
124                                           Allocation.MipmapControl.MIPMAP_NONE,
125                                           Allocation.USAGE_GRAPHICS_TEXTURE);
126        mScript.set_gTvumeter_peak_off(mTextures[3]);
127        mTextures[4] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.needle,
128                                           Allocation.MipmapControl.MIPMAP_NONE,
129                                           Allocation.USAGE_GRAPHICS_TEXTURE);
130        mScript.set_gTvumeter_needle(mTextures[4]);
131        mTextures[5] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.black,
132                                           Allocation.MipmapControl.MIPMAP_NONE,
133                                           Allocation.USAGE_GRAPHICS_TEXTURE);
134        mScript.set_gTvumeter_black(mTextures[5]);
135
136        Sampler.Builder samplerBuilder = new Sampler.Builder(mRS);
137        samplerBuilder.setMinification(LINEAR);
138        samplerBuilder.setMagnification(LINEAR);
139        samplerBuilder.setWrapS(WRAP);
140        samplerBuilder.setWrapT(WRAP);
141        mSampler = samplerBuilder.create();
142
143        {
144            ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS);
145            builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE,
146                               ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
147            mPfBackground = builder.create();
148            mPfBackground.bindSampler(mSampler, 0);
149
150            mScript.set_gPFBackground(mPfBackground);
151        }
152
153        {
154            ProgramStore.Builder builder = new ProgramStore.Builder(mRS);
155            builder.setDepthFunc(ALWAYS);
156            //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
157            builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
158            builder.setDitherEnabled(true); // without dithering there is severe banding
159            builder.setDepthMaskEnabled(false);
160            mPfsBackground = builder.create();
161
162            mScript.set_gPFSBackground(mPfsBackground);
163        }
164
165        mScript.setTimeZone(TimeZone.getDefault().getID());
166
167        return mScript;
168    }
169
170    @Override
171    public void start() {
172        super.start();
173        mVisible = true;
174        if (mAudioCapture == null) {
175            mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024);
176        }
177        mAudioCapture.start();
178        updateWave();
179    }
180
181    @Override
182    public void stop() {
183        super.stop();
184        mVisible = false;
185        if (mAudioCapture != null) {
186            mAudioCapture.stop();
187            mAudioCapture.release();
188            mAudioCapture = null;
189        }
190    }
191
192    void updateWave() {
193        mHandler.removeCallbacks(mDrawCube);
194        if (!mVisible) {
195            return;
196        }
197        mHandler.postDelayed(mDrawCube, 20);
198
199        int len = 0;
200        if (mAudioCapture != null) {
201            // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit)
202            mVizData = mAudioCapture.getFormattedData(512, 1);
203            len = mVizData.length;
204        }
205
206        // Simulate running the signal through a rectifier by
207        // taking the average of the absolute sample values.
208        int volt = 0;
209        if (len > 0) {
210            for (int i = 0; i < len; i++) {
211                int val = mVizData[i];
212                if (val < 0) {
213                    val = -val;
214                }
215                volt += val;
216            }
217            volt = volt / len;
218        }
219        // There are several forces working on the needle: a force applied by the
220        // electromagnet, a force applied by the spring,  and friction.
221        // The force from the magnet is proportional to the current flowing
222        // through its coil. We have to take in to account that the coil is an
223        // inductive load, which means that an immediate change in applied voltage
224        // will result in a gradual change in current, but also that current will
225        // be induced by the movement of the needle.
226        // The force from the spring is proportional to the position of the needle.
227        // The friction force is a function of the speed of the needle, but so is
228        // the current induced by the movement of the needle, so we can combine
229        // them.
230
231
232        // Add up the various forces, with some multipliers to make the movement
233        // of the needle more realistic
234        // 'volt' is for the applied voltage, which causes a current to flow through the coil
235        // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current
236        // in the coil, and is also proportional to the friction
237        // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back
238        int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ;
239        int acceleration = netforce / mNeedleMass;
240        mNeedleSpeed += acceleration;
241        mNeedlePos += mNeedleSpeed;
242        if (mNeedlePos < 0) {
243            mNeedlePos = 0;
244            mNeedleSpeed = 0;
245        } else if (mNeedlePos > 32767) {
246            if (mNeedlePos > 33333) {
247                 mWorldState.mPeak = 10;
248            }
249            mNeedlePos = 32767;
250            mNeedleSpeed = 0;
251        }
252        if (mWorldState.mPeak > 0) {
253            mWorldState.mPeak--;
254        }
255
256        mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range
257        mScript.set_gAngle(mWorldState.mAngle);
258        mScript.set_gPeak(mWorldState.mPeak);
259    }
260}
261