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