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 import com.android.musicvis.AudioCapture; 22 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 AudioCapture mAudioCapture = null; 93 private int [] mVizData = new int[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 150 public void setOffset(float xOffset, float yOffset, 151 float xStep, float yStep, int xPixels, int yPixels) { 152 // 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), true); 177 mTextures[0].setName("Tvumeter_background"); 178 mTextures[1] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.frame, Element.RGBA_8888(mRS), true); 179 mTextures[1].setName("Tvumeter_frame"); 180 mTextures[2] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_on, Element.RGBA_8888(mRS), true); 181 mTextures[2].setName("Tvumeter_peak_on"); 182 mTextures[3] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_off, Element.RGBA_8888(mRS), true); 183 mTextures[3].setName("Tvumeter_peak_off"); 184 mTextures[4] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.needle, Element.RGBA_8888(mRS), true); 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), true); 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 { 199 Sampler.Builder builder = new Sampler.Builder(mRS); 200 builder.setMin(Value.LINEAR); 201 builder.setMag(Value.LINEAR); 202 builder.setWrapS(Value.WRAP); 203 builder.setWrapT(Value.WRAP); 204 mSamplerNoMip = builder.create(); 205 } 206 207 { 208 Sampler.Builder builder = new Sampler.Builder(mRS); 209 builder.setMin(Value.LINEAR_MIP_LINEAR); 210 builder.setMag(Value.LINEAR); 211 builder.setWrapS(Value.WRAP); 212 builder.setWrapT(Value.WRAP); 213 mSamplerMip = builder.create(); 214 } 215 216 { 217 ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS); 218 builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE, 219 ProgramFragment.Builder.Format.RGBA, 0); 220 mPfBackgroundNoMip = builder.create(); 221 mPfBackgroundNoMip.setName("PFBackgroundNoMip"); 222 mPfBackgroundNoMip.bindSampler(mSamplerNoMip, 0); 223 } 224 225 { 226 ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS); 227 builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE, 228 ProgramFragment.Builder.Format.RGBA, 0); 229 mPfBackgroundMip = builder.create(); 230 mPfBackgroundMip.setName("PFBackgroundMip"); 231 mPfBackgroundMip.bindSampler(mSamplerMip, 0); 232 } 233 234 { 235 ProgramStore.Builder builder = new ProgramStore.Builder(mRS, null, null); 236 builder.setDepthFunc(ProgramStore.DepthFunc.EQUAL); 237 //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 238 builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 239 builder.setDitherEnable(true); // without dithering there is severe banding 240 builder.setDepthMask(false); 241 mPfsBackground = builder.create(); 242 mPfsBackground.setName("PFSBackground"); 243 } 244 245 // Start creating the mesh 246 final SimpleMesh.Builder meshBuilder = new SimpleMesh.Builder(mRS); 247 248 // Create the Element for the points 249 Builder elementBuilder = new Builder(mRS); 250 elementBuilder.add(Element.ATTRIB_POSITION_2(mRS), "position"); 251 elementBuilder.add(Element.ATTRIB_TEXTURE_2(mRS), "texture"); 252 final Element vertexElement = elementBuilder.create(); 253 final int vertexSlot = meshBuilder.addVertexType(vertexElement, mPointData.length / 4); 254 // Specify the type and number of indices we need. We'll allocate them later. 255 meshBuilder.setIndexType(Element.INDEX_16(mRS), mIndexData.length); 256 // This will be a line mesh 257 meshBuilder.setPrimitive(Primitive.LINE); 258 259 // Create the Allocation for the vertices 260 mCubeMesh = meshBuilder.create(); 261 mCubeMesh.setName("CubeMesh"); 262 mPointAlloc = mCubeMesh.createVertexAllocation(vertexSlot); 263 mPointAlloc.setName("PointBuffer"); 264 265 // Create the Allocation for the indices 266 mLineIdxAlloc = mCubeMesh.createIndexAllocation(); 267 268 // Bind the allocations to the mesh 269 mCubeMesh.bindVertexAllocation(mPointAlloc, 0); 270 mCubeMesh.bindIndexAllocation(mLineIdxAlloc); 271 272 /* 273 * put the vertex and index data in their respective buffers 274 */ 275 updateWave(); 276 for(int i = 0; i < mIndexData.length; i ++) { 277 mIndexData[i] = (short) i; 278 } 279 280 /* 281 * upload the vertex and index data 282 */ 283 mPointAlloc.data(mPointData); 284 mPointAlloc.uploadToBufferObject(); 285 mLineIdxAlloc.data(mIndexData); 286 mLineIdxAlloc.uploadToBufferObject(); 287 288 // Time to create the script 289 ScriptC.Builder sb = new ScriptC.Builder(mRS); 290 // Specify the name by which to refer to the WorldState object in the 291 // renderscript. 292 sb.setType(mStateType, "State", RSID_STATE); 293 sb.setScript(mResources, R.raw.many); 294 sb.setRoot(true); 295 296 ScriptC script = sb.create(); 297 script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f); 298 script.setTimeZone(TimeZone.getDefault().getID()); 299 300 script.bindAllocation(mState, RSID_STATE); 301 script.bindAllocation(mPointAlloc, RSID_POINTS); 302 script.bindAllocation(mLineIdxAlloc, RSID_LINES); 303 script.bindAllocation(mPVAlloc.mAlloc, RSID_PROGRAMVERTEX); 304 305 return script; 306 } 307 308 @Override 309 public void start() { 310 super.start(); 311 mVisible = true; 312 if (mAudioCapture == null) { 313 mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024); 314 } 315 mAudioCapture.start(); 316 updateWave(); 317 } 318 319 @Override 320 public void stop() { 321 super.stop(); 322 mVisible = false; 323 if (mAudioCapture != null) { 324 mAudioCapture.stop(); 325 mAudioCapture.release(); 326 mAudioCapture = null; 327 } 328 } 329 330 void updateWave() { 331 mHandler.removeCallbacks(mDrawCube); 332 if (!mVisible) { 333 return; 334 } 335 mHandler.postDelayed(mDrawCube, 20); 336 337 int len = 0; 338 if (mAudioCapture != null) { 339 // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit) 340 mVizData = mAudioCapture.getFormattedData(512, 1); 341 len = mVizData.length; 342 } 343 344 // Simulate running the signal through a rectifier by 345 // taking the average of the absolute sample values. 346 int volt = 0; 347 if (len > 0) { 348 for (int i = 0; i < len; i++) { 349 int val = mVizData[i]; 350 if (val < 0) { 351 val = -val; 352 } 353 volt += val; 354 } 355 volt = volt / len; 356 } 357 358 // There are several forces working on the needle: a force applied by the 359 // electromagnet, a force applied by the spring, and friction. 360 // The force from the magnet is proportional to the current flowing 361 // through its coil. We have to take in to account that the coil is an 362 // inductive load, which means that an immediate change in applied voltage 363 // will result in a gradual change in current, but also that current will 364 // be induced by the movement of the needle. 365 // The force from the spring is proportional to the position of the needle. 366 // The friction force is a function of the speed of the needle, but so is 367 // the current induced by the movement of the needle, so we can combine 368 // them. 369 370 371 // Add up the various forces, with some multipliers to make the movement 372 // of the needle more realistic 373 // 'volt' is for the applied voltage, which causes a current to flow through the coil 374 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current 375 // in the coil, and is also proportional to the friction 376 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back 377 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ; 378 int acceleration = netforce / mNeedleMass; 379 mNeedleSpeed += acceleration; 380 mNeedlePos += mNeedleSpeed; 381 if (mNeedlePos < 0) { 382 mNeedlePos = 0; 383 mNeedleSpeed = 0; 384 } else if (mNeedlePos > 32767) { 385 if (mNeedlePos > 33333) { 386 mWorldState.mPeak = 10; 387 } 388 mNeedlePos = 32767; 389 mNeedleSpeed = 0; 390 } 391 if (mWorldState.mPeak > 0) { 392 mWorldState.mPeak--; 393 } 394 395 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range 396 397 // downsample 1024 samples in to 256 398 399 if (len == 0) { 400 if (mWorldState.mIdle == 0) { 401 mWorldState.mIdle = 1; 402 } 403 } else { 404 if (mWorldState.mIdle != 0) { 405 mWorldState.mIdle = 0; 406 } 407 // TODO: might be more efficient to push this in to renderscript 408 int outlen = mPointData.length / 8; 409 len /= 4; 410 if (len > outlen) len = outlen; 411 for(int i = 0; i < len; i++) { 412 int amp = (mVizData[i*4] + mVizData[i*4+1] + mVizData[i*4+2] + mVizData[i*4+3]); 413 mPointData[i*8+1] = amp; 414 mPointData[i*8+5] = -amp; 415 } 416 mPointAlloc.data(mPointData); 417 mWorldState.mWaveCounter++; 418 } 419 420 mState.data(mWorldState); 421 } 422 } 423