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 17 package com.android.musicvis.vis5; 18 19 import com.android.musicvis.R; 20 import com.android.musicvis.RenderScriptScene; 21 22 import android.media.MediaPlayer; 23 import android.os.Handler; 24 import android.renderscript.Allocation; 25 import android.renderscript.Element; 26 import android.renderscript.Primitive; 27 import android.renderscript.ProgramFragment; 28 import android.renderscript.ProgramStore; 29 import android.renderscript.ProgramVertex; 30 import android.renderscript.Sampler; 31 import android.renderscript.ScriptC; 32 import android.renderscript.SimpleMesh; 33 import android.renderscript.Type; 34 import android.renderscript.Element.Builder; 35 import android.renderscript.ProgramStore.BlendDstFunc; 36 import android.renderscript.ProgramStore.BlendSrcFunc; 37 import android.renderscript.Sampler.Value; 38 import android.util.Log; 39 import android.view.MotionEvent; 40 41 import java.util.TimeZone; 42 43 class 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; 58 59 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; 72 private ProgramFragment mPfBackgroundMip; 73 private ProgramFragment mPfBackgroundNoMip; 74 private Sampler mSamplerMip; 75 private Sampler mSamplerNoMip; 76 private Allocation[] mTextures; 77 78 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]; 91 92 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; 100 101 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 } 147 148 @Override 149 public void setOffset(float xOffset, float yOffset, 150 float xStep, float yStep, int xPixels, int yPixels) { 151 // 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() { 158 159 // 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]; 175 mTextures[0] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.background, Element.RGBA_8888(mRS), true); 176 mTextures[0].setName("Tvumeter_background"); 177 mTextures[1] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.frame, Element.RGBA_8888(mRS), true); 178 mTextures[1].setName("Tvumeter_frame"); 179 mTextures[2] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_on, Element.RGBA_8888(mRS), true); 180 mTextures[2].setName("Tvumeter_peak_on"); 181 mTextures[3] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_off, Element.RGBA_8888(mRS), true); 182 mTextures[3].setName("Tvumeter_peak_off"); 183 mTextures[4] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.needle, Element.RGBA_8888(mRS), true); 184 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"); 187 mTextures[6] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.albumart, Element.RGBA_8888(mRS), true); 188 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 } 196 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 } 214 215 { 216 ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS); 217 builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE, 218 ProgramFragment.Builder.Format.RGBA, 0); 219 mPfBackgroundNoMip = builder.create(); 220 mPfBackgroundNoMip.setName("PFBackgroundNoMip"); 221 mPfBackgroundNoMip.bindSampler(mSamplerNoMip, 0); 222 } 223 224 { 225 ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS); 226 builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE, 227 ProgramFragment.Builder.Format.RGBA, 0); 228 mPfBackgroundMip = builder.create(); 229 mPfBackgroundMip.setName("PFBackgroundMip"); 230 mPfBackgroundMip.bindSampler(mSamplerMip, 0); 231 } 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 } 243 244 // 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); 249 elementBuilder.add(Element.ATTRIB_POSITION_2(mRS), "position"); 250 elementBuilder.add(Element.ATTRIB_TEXTURE_2(mRS), "texture"); 251 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 271 /* 272 * put the vertex and index data in their respective buffers 273 */ 274 updateWave(); 275 for(int i = 0; i < mIndexData.length; i ++) { 276 mIndexData[i] = (short) i; 277 } 278 279 /* 280 * upload the vertex and index data 281 */ 282 mPointAlloc.data(mPointData); 283 mPointAlloc.uploadToBufferObject(); 284 mLineIdxAlloc.data(mIndexData); 285 mLineIdxAlloc.uploadToBufferObject(); 286 287 // Time to create the script 288 ScriptC.Builder sb = new ScriptC.Builder(mRS); 289 // Specify the name by which to refer to the WorldState object in the 290 // renderscript. 291 sb.setType(mStateType, "State", RSID_STATE); 292 sb.setScript(mResources, R.raw.many); 293 sb.setRoot(true); 294 295 ScriptC script = sb.create(); 296 script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f); 297 script.setTimeZone(TimeZone.getDefault().getID()); 298 299 script.bindAllocation(mState, RSID_STATE); 300 script.bindAllocation(mPointAlloc, RSID_POINTS); 301 script.bindAllocation(mLineIdxAlloc, RSID_LINES); 302 script.bindAllocation(mPVAlloc.mAlloc, RSID_PROGRAMVERTEX); 303 304 return script; 305 } 306 307 @Override 308 public void start() { 309 super.start(); 310 mVisible = true; 311 updateWave(); 312 } 313 314 @Override 315 public void stop() { 316 super.stop(); 317 mVisible = false; 318 } 319 320 void updateWave() { 321 mHandler.removeCallbacks(mDrawCube); 322 if (!mVisible) { 323 return; 324 } 325 mHandler.postDelayed(mDrawCube, 20); 326 327 int len = MediaPlayer.snoop(mVizData, 0); 328 329 // Simulate running the signal through a rectifier by 330 // taking the average of the absolute sample values. 331 int volt = 0; 332 if (len > 0) { 333 for (int i = 0; i < len; i++) { 334 int val = mVizData[i]; 335 if (val < 0) { 336 val = -val; 337 } 338 volt += val; 339 } 340 volt = volt * 4 / len; // arbitrary scalar to get better range 341 } 342 343 // There are several forces working on the needle: a force applied by the 344 // electromagnet, a force applied by the spring, and friction. 345 // The force from the magnet is proportional to the current flowing 346 // through its coil. We have to take in to account that the coil is an 347 // inductive load, which means that an immediate change in applied voltage 348 // will result in a gradual change in current, but also that current will 349 // be induced by the movement of the needle. 350 // The force from the spring is proportional to the position of the needle. 351 // The friction force is a function of the speed of the needle, but so is 352 // the current induced by the movement of the needle, so we can combine 353 // them. 354 355 356 // Add up the various forces, with some multipliers to make the movement 357 // of the needle more realistic 358 // 'volt' is for the applied voltage, which causes a current to flow through the coil 359 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current 360 // in the coil, and is also proportional to the friction 361 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back 362 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ; 363 int acceleration = netforce / mNeedleMass; 364 mNeedleSpeed += acceleration; 365 mNeedlePos += mNeedleSpeed; 366 if (mNeedlePos < 0) { 367 mNeedlePos = 0; 368 mNeedleSpeed = 0; 369 } else if (mNeedlePos > 32767) { 370 if (mNeedlePos > 33333) { 371 mWorldState.mPeak = 10; 372 } 373 mNeedlePos = 32767; 374 mNeedleSpeed = 0; 375 } 376 if (mWorldState.mPeak > 0) { 377 mWorldState.mPeak--; 378 } 379 380 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range 381 382 // downsample 1024 samples in to 256 383 384 if (len == 0) { 385 if (mWorldState.mIdle == 0) { 386 mWorldState.mIdle = 1; 387 } 388 } else { 389 if (mWorldState.mIdle != 0) { 390 mWorldState.mIdle = 0; 391 } 392 // TODO: might be more efficient to push this in to renderscript 393 int outlen = mPointData.length / 8; 394 len /= 4; 395 if (len > outlen) len = outlen; 396 for(int i = 0; i < len; i++) { 397 int amp = (mVizData[i*4] + mVizData[i*4+1] + mVizData[i*4+2] + mVizData[i*4+3]); 398 mPointData[i*8+1] = amp; 399 mPointData[i*8+5] = -amp; 400 } 401 mPointAlloc.data(mPointData); 402 mWorldState.mWaveCounter++; 403 } 404 405 mState.data(mWorldState); 406 } 407 408 } 409