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.ScriptField_Vertex; 22 import com.android.musicvis.AudioCapture; 23 24 import android.os.Handler; 25 import android.renderscript.*; 26 import android.renderscript.Element.Builder; 27 import android.renderscript.ProgramStore.BlendDstFunc; 28 import android.renderscript.ProgramStore.BlendSrcFunc; 29 import android.renderscript.Sampler.Value; 30 import android.util.Log; 31 import android.view.MotionEvent; 32 33 import java.util.TimeZone; 34 35 class Visualization5RS extends RenderScriptScene { 36 37 private final Handler mHandler = new Handler(); 38 private final Runnable mDrawCube = new Runnable() { 39 public void run() { 40 updateWave(); 41 } 42 }; 43 private boolean mVisible; 44 45 private int mNeedlePos = 0; 46 private int mNeedleSpeed = 0; 47 // tweak this to get quicker/slower response 48 private int mNeedleMass = 10; 49 private int mSpringForceAtOrigin = 200; 50 51 static class WorldState { 52 public float mAngle; 53 public int mPeak; 54 public float mRotate; 55 public float mTilt; 56 public int mIdle; 57 public int mWaveCounter; 58 } 59 WorldState mWorldState = new WorldState(); 60 61 ScriptC_many mScript; 62 private com.android.musicvis.vis5.ScriptField_Vertex mVertexBuffer; 63 64 private ProgramStore mPfsBackground; 65 private ProgramFragment mPfBackgroundMip; 66 private ProgramFragment mPfBackgroundNoMip; 67 private ProgramRaster mPr; 68 private Sampler mSamplerMip; 69 private Sampler mSamplerNoMip; 70 private Allocation[] mTextures; 71 72 private ProgramVertex mPVBackground; 73 private ProgramVertexFixedFunction.Constants mPVAlloc; 74 75 private Mesh mCubeMesh; 76 77 protected Allocation mPointAlloc; 78 // 256 lines, with 4 points per line (2 space, 2 texture) each consisting of x and y, 79 // so 8 floats per line. 80 protected float [] mPointData = new float[256*8]; 81 82 private Allocation mLineIdxAlloc; 83 // 2 indices per line 84 private short [] mIndexData = new short[256*2]; 85 86 private AudioCapture mAudioCapture = null; 87 private int [] mVizData = new int[1024]; 88 89 private static final int RSID_STATE = 0; 90 private static final int RSID_POINTS = 1; 91 private static final int RSID_LINES = 2; 92 private static final int RSID_PROGRAMVERTEX = 3; 93 94 private float mTouchY; 95 96 Visualization5RS(int width, int height) { 97 super(width, height); 98 mWidth = width; 99 mHeight = height; 100 // the x, s and t coordinates don't change, so set those now 101 int outlen = mPointData.length / 8; 102 int half = outlen / 2; 103 for(int i = 0; i < outlen; i++) { 104 mPointData[i*8] = i - half; // start point X (Y set later) 105 mPointData[i*8+2] = 0; // start point S 106 mPointData[i*8+3] = 0; // start point T 107 mPointData[i*8+4] = i - half; // end point X (Y set later) 108 mPointData[i*8+6] = 1.0f; // end point S 109 mPointData[i*8+7] = 0f; // end point T 110 } 111 } 112 113 @Override 114 public void resize(int width, int height) { 115 super.resize(width, height); 116 if (mPVAlloc != null) { 117 Matrix4f proj = new Matrix4f(); 118 proj.loadProjectionNormalized(width, height); 119 mPVAlloc.setProjection(proj); 120 } 121 mWorldState.mTilt = -20; 122 } 123 124 /*@Override 125 public void onTouchEvent(MotionEvent event) { 126 switch(event.getAction()) { 127 case MotionEvent.ACTION_DOWN: 128 mTouchY = event.getY(); 129 break; 130 case MotionEvent.ACTION_MOVE: 131 float dy = event.getY() - mTouchY; 132 mTouchY += dy; 133 dy /= 10; 134 dy += mWorldState.mTilt; 135 if (dy > 0) { 136 dy = 0; 137 } else if (dy < -45) { 138 dy = -45; 139 } 140 mWorldState.mTilt = dy; 141 mState.data(mWorldState); 142 //updateWorldState(); 143 } 144 }*/ 145 146 @Override 147 public void setOffset(float xOffset, float yOffset, int xPixels, int yPixels) { 148 // update our state, then push it to the renderscript 149 mWorldState.mRotate = (xOffset - 0.5f) * 90; 150 updateWorldState(); 151 } 152 153 @Override 154 protected ScriptC createScript() { 155 mScript = new ScriptC_many(mRS, mResources, R.raw.many); 156 157 // First set up the coordinate system and such 158 ProgramVertexFixedFunction.Builder pvb = new ProgramVertexFixedFunction.Builder(mRS); 159 mPVBackground = pvb.create(); 160 mPVAlloc = new ProgramVertexFixedFunction.Constants(mRS); 161 ((ProgramVertexFixedFunction)mPVBackground).bindConstants(mPVAlloc); 162 Matrix4f proj = new Matrix4f(); 163 proj.loadProjectionNormalized(mWidth, mHeight); 164 mPVAlloc.setProjection(proj); 165 166 mScript.set_gPVBackground(mPVBackground); 167 168 mTextures = new Allocation[8]; 169 mTextures[0] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.background, 170 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 171 Allocation.USAGE_GRAPHICS_TEXTURE); 172 mScript.set_gTvumeter_background(mTextures[0]); 173 mTextures[1] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.frame, 174 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 175 Allocation.USAGE_GRAPHICS_TEXTURE); 176 mScript.set_gTvumeter_frame(mTextures[1]); 177 mTextures[2] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_on, 178 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 179 Allocation.USAGE_GRAPHICS_TEXTURE); 180 mScript.set_gTvumeter_peak_on(mTextures[2]); 181 mTextures[3] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_off, 182 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 183 Allocation.USAGE_GRAPHICS_TEXTURE); 184 mScript.set_gTvumeter_peak_off(mTextures[3]); 185 mTextures[4] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.needle, 186 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 187 Allocation.USAGE_GRAPHICS_TEXTURE); 188 mScript.set_gTvumeter_needle(mTextures[4]); 189 mTextures[5] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.black, 190 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 191 Allocation.USAGE_GRAPHICS_TEXTURE); 192 mScript.set_gTvumeter_black(mTextures[5]); 193 mTextures[6] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.albumart, 194 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 195 Allocation.USAGE_GRAPHICS_TEXTURE); 196 mScript.set_gTvumeter_album(mTextures[6]); 197 mTextures[7] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.fire, 198 Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE, 199 Allocation.USAGE_GRAPHICS_TEXTURE); 200 mScript.set_gTlinetexture(mTextures[7]); 201 202 { 203 Sampler.Builder builder = new Sampler.Builder(mRS); 204 builder.setMinification(Value.LINEAR); 205 builder.setMagnification(Value.LINEAR); 206 builder.setWrapS(Value.WRAP); 207 builder.setWrapT(Value.WRAP); 208 mSamplerNoMip = builder.create(); 209 } 210 211 { 212 Sampler.Builder builder = new Sampler.Builder(mRS); 213 builder.setMinification(Value.LINEAR_MIP_LINEAR); 214 builder.setMagnification(Value.LINEAR); 215 builder.setWrapS(Value.WRAP); 216 builder.setWrapT(Value.WRAP); 217 mSamplerMip = builder.create(); 218 } 219 220 { 221 ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS); 222 builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, 223 ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); 224 mPfBackgroundNoMip = builder.create(); 225 mPfBackgroundNoMip.bindSampler(mSamplerNoMip, 0); 226 mScript.set_gPFBackgroundNoMip(mPfBackgroundNoMip); 227 } 228 229 { 230 ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS); 231 builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, 232 ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); 233 mPfBackgroundMip = builder.create(); 234 mPfBackgroundMip.bindSampler(mSamplerMip, 0); 235 mScript.set_gPFBackgroundMip(mPfBackgroundMip); 236 } 237 238 { 239 ProgramRaster.Builder builder = new ProgramRaster.Builder(mRS); 240 builder.setCullMode(ProgramRaster.CullMode.NONE); 241 mPr = builder.create(); 242 mScript.set_gPR(mPr); 243 } 244 245 { 246 ProgramStore.Builder builder = new ProgramStore.Builder(mRS); 247 builder.setDepthFunc(ProgramStore.DepthFunc.EQUAL); 248 //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 249 builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 250 builder.setDitherEnabled(true); // without dithering there is severe banding 251 builder.setDepthMaskEnabled(false); 252 mPfsBackground = builder.create(); 253 254 mScript.set_gPFSBackground(mPfsBackground); 255 } 256 257 // Start creating the mesh 258 mVertexBuffer = new com.android.musicvis.vis5.ScriptField_Vertex(mRS, mPointData.length / 4); 259 260 final Mesh.AllocationBuilder meshBuilder = new Mesh.AllocationBuilder(mRS); 261 meshBuilder.addVertexAllocation(mVertexBuffer.getAllocation()); 262 // Create the Allocation for the indices 263 mLineIdxAlloc = Allocation.createSized(mRS, Element.U16(mRS), mIndexData.length, 264 Allocation.USAGE_SCRIPT | 265 Allocation.USAGE_GRAPHICS_VERTEX); 266 // This will be a line mesh 267 meshBuilder.addIndexSetAllocation(mLineIdxAlloc, Mesh.Primitive.LINE); 268 269 // Create the Allocation for the vertices 270 mCubeMesh = meshBuilder.create(); 271 272 mPointAlloc = mVertexBuffer.getAllocation(); 273 274 mScript.bind_gPoints(mVertexBuffer); 275 mScript.set_gPointBuffer(mPointAlloc); 276 mScript.set_gCubeMesh(mCubeMesh); 277 278 // put the vertex and index data in their respective buffers 279 updateWave(); 280 for(int i = 0; i < mIndexData.length; i ++) { 281 mIndexData[i] = (short) i; 282 } 283 284 // upload the vertex and index data 285 mPointAlloc.copyFromUnchecked(mPointData); 286 mLineIdxAlloc.copyFrom(mIndexData); 287 mLineIdxAlloc.syncAll(Allocation.USAGE_SCRIPT); 288 289 return mScript; 290 } 291 292 @Override 293 public void start() { 294 super.start(); 295 mVisible = true; 296 if (mAudioCapture == null) { 297 mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024); 298 } 299 mAudioCapture.start(); 300 updateWave(); 301 } 302 303 @Override 304 public void stop() { 305 super.stop(); 306 mVisible = false; 307 if (mAudioCapture != null) { 308 mAudioCapture.stop(); 309 mAudioCapture.release(); 310 mAudioCapture = null; 311 } 312 } 313 314 void updateWave() { 315 mHandler.removeCallbacks(mDrawCube); 316 if (!mVisible) { 317 return; 318 } 319 mHandler.postDelayed(mDrawCube, 20); 320 321 int len = 0; 322 if (mAudioCapture != null) { 323 // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit) 324 mVizData = mAudioCapture.getFormattedData(512, 1); 325 len = mVizData.length; 326 } 327 328 // Simulate running the signal through a rectifier by 329 // taking the average of the absolute sample values. 330 int volt = 0; 331 if (len > 0) { 332 for (int i = 0; i < len; i++) { 333 int val = mVizData[i]; 334 if (val < 0) { 335 val = -val; 336 } 337 volt += val; 338 } 339 volt = volt / len; 340 } 341 342 // There are several forces working on the needle: a force applied by the 343 // electromagnet, a force applied by the spring, and friction. 344 // The force from the magnet is proportional to the current flowing 345 // through its coil. We have to take in to account that the coil is an 346 // inductive load, which means that an immediate change in applied voltage 347 // will result in a gradual change in current, but also that current will 348 // be induced by the movement of the needle. 349 // The force from the spring is proportional to the position of the needle. 350 // The friction force is a function of the speed of the needle, but so is 351 // the current induced by the movement of the needle, so we can combine 352 // them. 353 354 355 // Add up the various forces, with some multipliers to make the movement 356 // of the needle more realistic 357 // 'volt' is for the applied voltage, which causes a current to flow through the coil 358 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current 359 // in the coil, and is also proportional to the friction 360 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back 361 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ; 362 int acceleration = netforce / mNeedleMass; 363 mNeedleSpeed += acceleration; 364 mNeedlePos += mNeedleSpeed; 365 if (mNeedlePos < 0) { 366 mNeedlePos = 0; 367 mNeedleSpeed = 0; 368 } else if (mNeedlePos > 32767) { 369 if (mNeedlePos > 33333) { 370 mWorldState.mPeak = 10; 371 } 372 mNeedlePos = 32767; 373 mNeedleSpeed = 0; 374 } 375 if (mWorldState.mPeak > 0) { 376 mWorldState.mPeak--; 377 } 378 379 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range 380 381 // downsample 1024 samples in to 256 382 383 if (len == 0) { 384 if (mWorldState.mIdle == 0) { 385 mWorldState.mIdle = 1; 386 } 387 } else { 388 if (mWorldState.mIdle != 0) { 389 mWorldState.mIdle = 0; 390 } 391 // TODO: might be more efficient to push this in to renderscript 392 int outlen = mPointData.length / 8; 393 len /= 4; 394 if (len > outlen) len = outlen; 395 for(int i = 0; i < len; i++) { 396 int amp = (mVizData[i*4] + mVizData[i*4+1] + mVizData[i*4+2] + mVizData[i*4+3]); 397 mPointData[i*8+1] = amp; 398 mPointData[i*8+5] = -amp; 399 } 400 mPointAlloc.copyFromUnchecked(mPointData); 401 mWorldState.mWaveCounter++; 402 } 403 404 updateWorldState(); 405 } 406 407 protected void updateWorldState() { 408 mScript.set_gAngle(mWorldState.mAngle); 409 mScript.set_gPeak(mWorldState.mPeak); 410 mScript.set_gRotate(mWorldState.mRotate); 411 mScript.set_gTilt(mWorldState.mTilt); 412 mScript.set_gIdle(mWorldState.mIdle); 413 mScript.set_gWaveCounter(mWorldState.mWaveCounter); 414 } 415 } 416