blob: 76f98dfd48323b6d709c262018e75aaa20f03e42 [file] [log] [blame]
Marco Nelissen838dedb2009-11-03 17:01:21 -08001/*
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 static android.renderscript.ProgramFragment.EnvMode.REPLACE;
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;
25
26import android.media.MediaPlayer;
27import android.os.Handler;
28import android.renderscript.Allocation;
29import android.renderscript.Element;
30import android.renderscript.Primitive;
31import android.renderscript.ProgramFragment;
32import android.renderscript.ProgramStore;
33import android.renderscript.ProgramVertex;
34import android.renderscript.Sampler;
35import android.renderscript.ScriptC;
36import android.renderscript.SimpleMesh;
37import android.renderscript.Type;
38import android.renderscript.Element.Builder;
39import android.renderscript.ProgramStore.BlendDstFunc;
40import android.renderscript.ProgramStore.BlendSrcFunc;
41import android.util.Log;
42import android.view.MotionEvent;
43
44import java.util.TimeZone;
45
46class Visualization5RS extends RenderScriptScene {
47
48 private final Handler mHandler = new Handler();
49 private final Runnable mDrawCube = new Runnable() {
50 public void run() {
51 updateWave();
52 }
53 };
54 private boolean mVisible;
55
56 private int mNeedlePos = 0;
57 private int mNeedleSpeed = 0;
58 // tweak this to get quicker/slower response
59 private int mNeedleMass = 10;
60 private int mSpringForceAtOrigin = 200;
61
62 static class WorldState {
63 public float mAngle;
64 public int mPeak;
65 public float mRotate;
66 public float mTilt;
67 public int mIdle;
68 public int mWaveCounter;
69 }
70 WorldState mWorldState = new WorldState();
71 private Type mStateType;
72 private Allocation mState;
73
74 private ProgramStore mPfsBackground;
75 private ProgramFragment mPfBackground;
76 private Sampler mSampler;
77 private Allocation[] mTextures;
78
79 private ProgramVertex mPVBackground;
80 private ProgramVertex.MatrixAllocation mPVAlloc;
81
82 private SimpleMesh mCubeMesh;
83
84 protected Allocation mPointAlloc;
85 // 256 lines, with 4 points per line (2 space, 2 texture) each consisting of x and y,
86 // so 8 floats per line.
87 protected float [] mPointData = new float[256*8];
88
89 private Allocation mLineIdxAlloc;
90 // 2 indices per line
91 private short [] mIndexData = new short[256*2];
92
93 private short [] mVizData = new short[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
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
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
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
Marco Nelissenaf20a0f2009-11-09 15:41:52 -0800150 public void setOffset(float xOffset, float yOffset,
151 float xStep, float yStep, int xPixels, int yPixels) {
Marco Nelissen838dedb2009-11-03 17:01:21 -0800152 // update our state, then push it to the renderscript
153 mWorldState.mRotate = (xOffset - 0.5f) * 90;
154 mState.data(mWorldState);
155 }
156
157 @Override
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), false);
177 mTextures[0].setName("Tvumeter_background");
178 mTextures[1] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.frame, Element.RGBA_8888(mRS), false);
179 mTextures[1].setName("Tvumeter_frame");
180 mTextures[2] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_on, Element.RGBA_8888(mRS), false);
181 mTextures[2].setName("Tvumeter_peak_on");
182 mTextures[3] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_off, Element.RGBA_8888(mRS), false);
183 mTextures[3].setName("Tvumeter_peak_off");
184 mTextures[4] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.needle, Element.RGBA_8888(mRS), false);
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), false);
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 Sampler.Builder samplerBuilder = new Sampler.Builder(mRS);
199 samplerBuilder.setMin(LINEAR);
200 samplerBuilder.setMag(LINEAR);
201 samplerBuilder.setWrapS(WRAP);
202 samplerBuilder.setWrapT(WRAP);
203 mSampler = samplerBuilder.create();
204
205 {
206 ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS, null, null);
207 builder.setTexEnable(true, 0);
208 builder.setTexEnvMode(REPLACE, 0);
209 mPfBackground = builder.create();
210 mPfBackground.setName("PFBackground");
211 mPfBackground.bindSampler(mSampler, 0);
212 }
213
214 {
215 ProgramStore.Builder builder = new ProgramStore.Builder(mRS, null, null);
216 builder.setDepthFunc(ProgramStore.DepthFunc.EQUAL);
217 //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
218 builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
219 builder.setDitherEnable(true); // without dithering there is severe banding
220 builder.setDepthMask(false);
221 mPfsBackground = builder.create();
222 mPfsBackground.setName("PFSBackground");
223 }
224
225 // Start creating the mesh
226 final SimpleMesh.Builder meshBuilder = new SimpleMesh.Builder(mRS);
227
228 // Create the Element for the points
229 Builder elementBuilder = new Builder(mRS);
230 // By specifying a prefix, even an empty one, the members will be accessible
231 // in the renderscript. If we just called addFloatXYZ(), the members would be
232 // unnamed in the renderscript struct definition.
233 elementBuilder.addFloatXY("");
234 elementBuilder.addFloatST("");
235 final Element vertexElement = elementBuilder.create();
236 final int vertexSlot = meshBuilder.addVertexType(vertexElement, mPointData.length / 4);
237 // Specify the type and number of indices we need. We'll allocate them later.
238 meshBuilder.setIndexType(Element.INDEX_16(mRS), mIndexData.length);
239 // This will be a line mesh
240 meshBuilder.setPrimitive(Primitive.LINE);
241
242 // Create the Allocation for the vertices
243 mCubeMesh = meshBuilder.create();
244 mCubeMesh.setName("CubeMesh");
245 mPointAlloc = mCubeMesh.createVertexAllocation(vertexSlot);
246 mPointAlloc.setName("PointBuffer");
247
248 // Create the Allocation for the indices
249 mLineIdxAlloc = mCubeMesh.createIndexAllocation();
250
251 // Bind the allocations to the mesh
252 mCubeMesh.bindVertexAllocation(mPointAlloc, 0);
253 mCubeMesh.bindIndexAllocation(mLineIdxAlloc);
254
255 /*
256 * put the vertex and index data in their respective buffers
257 */
258 updateWave();
259 for(int i = 0; i < mIndexData.length; i ++) {
260 mIndexData[i] = (short) i;
261 }
262
263 /*
264 * upload the vertex and index data
265 */
266 mPointAlloc.data(mPointData);
267 mPointAlloc.uploadToBufferObject();
268 mLineIdxAlloc.data(mIndexData);
269 mLineIdxAlloc.uploadToBufferObject();
270
271 // Time to create the script
272 ScriptC.Builder sb = new ScriptC.Builder(mRS);
273 // Specify the name by which to refer to the WorldState object in the
274 // renderscript.
275 sb.setType(mStateType, "State", RSID_STATE);
276 sb.setScript(mResources, R.raw.many);
277 sb.setRoot(true);
278
279 ScriptC script = sb.create();
280 script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
281 script.setTimeZone(TimeZone.getDefault().getID());
282
283 script.bindAllocation(mState, RSID_STATE);
284 script.bindAllocation(mPointAlloc, RSID_POINTS);
285 script.bindAllocation(mLineIdxAlloc, RSID_LINES);
286 script.bindAllocation(mPVAlloc.mAlloc, RSID_PROGRAMVERTEX);
287
288 return script;
289 }
290
291 @Override
292 public void start() {
293 super.start();
294 mVisible = true;
295 updateWave();
296 }
297
298 @Override
299 public void stop() {
300 super.stop();
301 mVisible = false;
302 }
303
304 void updateWave() {
305 mHandler.removeCallbacks(mDrawCube);
Marco Nelissend760ff32009-11-10 14:49:08 -0800306 if (!mVisible) {
307 return;
Marco Nelissen838dedb2009-11-03 17:01:21 -0800308 }
Marco Nelissend760ff32009-11-10 14:49:08 -0800309 mHandler.postDelayed(mDrawCube, 20);
Marco Nelissen838dedb2009-11-03 17:01:21 -0800310
311 int len = MediaPlayer.snoop(mVizData, 0);
312
313 // Simulate running the signal through a rectifier by
314 // taking the average of the absolute sample values.
315 int volt = 0;
316 if (len > 0) {
317 for (int i = 0; i < len; i++) {
318 int val = mVizData[i];
319 if (val < 0) {
320 val = -val;
321 }
322 volt += val;
323 }
324 volt = volt * 4 / len; // arbitrary scalar to get better range
325 }
326
327 // There are several forces working on the needle: a force applied by the
328 // electromagnet, a force applied by the spring, and friction.
329 // The force from the magnet is proportional to the current flowing
330 // through its coil. We have to take in to account that the coil is an
331 // inductive load, which means that an immediate change in applied voltage
332 // will result in a gradual change in current, but also that current will
333 // be induced by the movement of the needle.
334 // The force from the spring is proportional to the position of the needle.
335 // The friction force is a function of the speed of the needle, but so is
336 // the current induced by the movement of the needle, so we can combine
337 // them.
338
339
340 // Add up the various forces, with some multipliers to make the movement
341 // of the needle more realistic
342 // 'volt' is for the applied voltage, which causes a current to flow through the coil
343 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current
344 // in the coil, and is also proportional to the friction
345 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back
346 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ;
347 int acceleration = netforce / mNeedleMass;
348 mNeedleSpeed += acceleration;
349 mNeedlePos += mNeedleSpeed;
350 if (mNeedlePos < 0) {
351 mNeedlePos = 0;
352 mNeedleSpeed = 0;
353 } else if (mNeedlePos > 32767) {
354 if (mNeedlePos > 33333) {
355 mWorldState.mPeak = 10;
356 }
357 mNeedlePos = 32767;
358 mNeedleSpeed = 0;
359 }
360 if (mWorldState.mPeak > 0) {
361 mWorldState.mPeak--;
362 }
363
364 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range
365
366 // downsample 1024 samples in to 256
367
368 if (len == 0) {
369 if (mWorldState.mIdle == 0) {
370 mWorldState.mIdle = 1;
371 }
372 } else {
373 if (mWorldState.mIdle != 0) {
374 mWorldState.mIdle = 0;
375 }
376 // TODO: might be more efficient to push this in to renderscript
377 int outlen = mPointData.length / 8;
378 len /= 4;
379 if (len > outlen) len = outlen;
380 for(int i = 0; i < len; i++) {
381 int amp = (mVizData[i*4] + mVizData[i*4+1] + mVizData[i*4+2] + mVizData[i*4+3]);
382 mPointData[i*8+1] = amp;
383 mPointData[i*8+5] = -amp;
384 }
385 mPointAlloc.data(mPointData);
386 mWorldState.mWaveCounter++;
387 }
388
389 mState.data(mWorldState);
390 }
391
392}