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