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.vis4; 18 19 import static android.renderscript.ProgramStore.DepthFunc.ALWAYS; 20 import static android.renderscript.Sampler.Value.LINEAR; 21 import static android.renderscript.Sampler.Value.WRAP; 22 23 import com.android.musicvis.R; 24 import com.android.musicvis.RenderScriptScene; 25 import com.android.musicvis.AudioCapture; 26 27 import android.os.Handler; 28 import android.renderscript.Allocation; 29 import android.renderscript.Element; 30 import android.renderscript.ProgramFragment; 31 import android.renderscript.ProgramStore; 32 import android.renderscript.ProgramVertex; 33 import android.renderscript.Sampler; 34 import android.renderscript.ScriptC; 35 import android.renderscript.Type; 36 import android.renderscript.ProgramStore.BlendDstFunc; 37 import android.renderscript.ProgramStore.BlendSrcFunc; 38 39 import java.util.TimeZone; 40 import android.util.Log; 41 42 class Visualization4RS extends RenderScriptScene { 43 44 private final Handler mHandler = new Handler(); 45 private final Runnable mDrawCube = new Runnable() { 46 public void run() { 47 updateWave(); 48 } 49 }; 50 private boolean mVisible; 51 52 private int mNeedlePos = 0; 53 private int mNeedleSpeed = 0; 54 // tweak this to get quicker/slower response 55 private int mNeedleMass = 10; 56 private int mSpringForceAtOrigin = 200; 57 58 static class WorldState { 59 public float mAngle; 60 public int mPeak; 61 } 62 WorldState mWorldState = new WorldState(); 63 private Type mStateType; 64 private Allocation mState; 65 66 private ProgramStore mPfsBackground; 67 private ProgramFragment mPfBackground; 68 private Sampler mSampler; 69 private Allocation[] mTextures; 70 71 private ProgramVertex mPVBackground; 72 private ProgramVertex.MatrixAllocation mPVAlloc; 73 74 private AudioCapture mAudioCapture = null; 75 private int [] mVizData = new int[1024]; 76 77 private static final int RSID_STATE = 0; 78 private static final int RSID_POINTS = 1; 79 private static final int RSID_LINES = 2; 80 private static final int RSID_PROGRAMVERTEX = 3; 81 82 83 Visualization4RS(int width, int height) { 84 super(width, height); 85 mWidth = width; 86 mHeight = height; 87 } 88 89 @Override 90 public void resize(int width, int height) { 91 super.resize(width, height); 92 if (mPVAlloc != null) { 93 mPVAlloc.setupProjectionNormalized(width, height); 94 } 95 } 96 97 @Override 98 protected ScriptC createScript() { 99 100 // Create a renderscript type from a java class. The specified name doesn't 101 // really matter; the name by which we refer to the object in RenderScript 102 // will be specified later. 103 mStateType = Type.createFromClass(mRS, WorldState.class, 1, "WorldState"); 104 // Create an allocation from the type we just created. 105 mState = Allocation.createTyped(mRS, mStateType); 106 107 // First set up the coordinate system and such 108 ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null); 109 mPVBackground = pvb.create(); 110 mPVBackground.setName("PVBackground"); 111 mPVAlloc = new ProgramVertex.MatrixAllocation(mRS); 112 mPVBackground.bindAllocation(mPVAlloc); 113 mPVAlloc.setupProjectionNormalized(mWidth, mHeight); 114 115 updateWave(); 116 117 mTextures = new Allocation[6]; 118 mTextures[0] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.background, Element.RGBA_8888(mRS), false); 119 mTextures[0].setName("Tvumeter_background"); 120 mTextures[1] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.frame, Element.RGBA_8888(mRS), false); 121 mTextures[1].setName("Tvumeter_frame"); 122 mTextures[2] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_on, Element.RGBA_8888(mRS), false); 123 mTextures[2].setName("Tvumeter_peak_on"); 124 mTextures[3] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.peak_off, Element.RGBA_8888(mRS), false); 125 mTextures[3].setName("Tvumeter_peak_off"); 126 mTextures[4] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.needle, Element.RGBA_8888(mRS), false); 127 mTextures[4].setName("Tvumeter_needle"); 128 mTextures[5] = Allocation.createFromBitmapResourceBoxed(mRS, mResources, R.drawable.black, Element.RGB_565(mRS), false); 129 mTextures[5].setName("Tvumeter_black"); 130 131 final int count = mTextures.length; 132 for (int i = 0; i < count; i++) { 133 mTextures[i].uploadToTexture(0); 134 } 135 136 Sampler.Builder samplerBuilder = new Sampler.Builder(mRS); 137 samplerBuilder.setMin(LINEAR); 138 samplerBuilder.setMag(LINEAR); 139 samplerBuilder.setWrapS(WRAP); 140 samplerBuilder.setWrapT(WRAP); 141 mSampler = samplerBuilder.create(); 142 143 { 144 ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS); 145 builder.setTexture(ProgramFragment.Builder.EnvMode.REPLACE, 146 ProgramFragment.Builder.Format.RGBA, 0); 147 mPfBackground = builder.create(); 148 mPfBackground.setName("PFBackground"); 149 mPfBackground.bindSampler(mSampler, 0); 150 } 151 152 { 153 ProgramStore.Builder builder = new ProgramStore.Builder(mRS, null, null); 154 builder.setDepthFunc(ALWAYS); 155 //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 156 builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 157 builder.setDitherEnable(true); // without dithering there is severe banding 158 builder.setDepthMask(false); 159 mPfsBackground = builder.create(); 160 mPfsBackground.setName("PFSBackground"); 161 } 162 163 // Time to create the script 164 ScriptC.Builder sb = new ScriptC.Builder(mRS); 165 // Specify the name by which to refer to the WorldState object in the 166 // renderscript. 167 sb.setType(mStateType, "State", RSID_STATE); 168 sb.setScript(mResources, R.raw.vu); 169 sb.setRoot(true); 170 171 ScriptC script = sb.create(); 172 script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f); 173 script.setTimeZone(TimeZone.getDefault().getID()); 174 175 script.bindAllocation(mState, RSID_STATE); 176 script.bindAllocation(mPVAlloc.mAlloc, RSID_PROGRAMVERTEX); 177 178 return script; 179 } 180 181 @Override 182 public void start() { 183 super.start(); 184 mVisible = true; 185 if (mAudioCapture == null) { 186 mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024); 187 } 188 mAudioCapture.start(); 189 updateWave(); 190 } 191 192 @Override 193 public void stop() { 194 super.stop(); 195 mVisible = false; 196 if (mAudioCapture != null) { 197 mAudioCapture.stop(); 198 mAudioCapture.release(); 199 mAudioCapture = null; 200 } 201 } 202 203 void updateWave() { 204 mHandler.removeCallbacks(mDrawCube); 205 if (!mVisible) { 206 return; 207 } 208 mHandler.postDelayed(mDrawCube, 20); 209 210 int len = 0; 211 if (mAudioCapture != null) { 212 // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit) 213 mVizData = mAudioCapture.getFormattedData(512, 1); 214 len = mVizData.length; 215 } 216 217 // Simulate running the signal through a rectifier by 218 // taking the average of the absolute sample values. 219 int volt = 0; 220 if (len > 0) { 221 for (int i = 0; i < len; i++) { 222 int val = mVizData[i]; 223 if (val < 0) { 224 val = -val; 225 } 226 volt += val; 227 } 228 volt = volt / len; 229 } 230 // There are several forces working on the needle: a force applied by the 231 // electromagnet, a force applied by the spring, and friction. 232 // The force from the magnet is proportional to the current flowing 233 // through its coil. We have to take in to account that the coil is an 234 // inductive load, which means that an immediate change in applied voltage 235 // will result in a gradual change in current, but also that current will 236 // be induced by the movement of the needle. 237 // The force from the spring is proportional to the position of the needle. 238 // The friction force is a function of the speed of the needle, but so is 239 // the current induced by the movement of the needle, so we can combine 240 // them. 241 242 243 // Add up the various forces, with some multipliers to make the movement 244 // of the needle more realistic 245 // 'volt' is for the applied voltage, which causes a current to flow through the coil 246 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current 247 // in the coil, and is also proportional to the friction 248 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back 249 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ; 250 int acceleration = netforce / mNeedleMass; 251 mNeedleSpeed += acceleration; 252 mNeedlePos += mNeedleSpeed; 253 if (mNeedlePos < 0) { 254 mNeedlePos = 0; 255 mNeedleSpeed = 0; 256 } else if (mNeedlePos > 32767) { 257 if (mNeedlePos > 33333) { 258 mWorldState.mPeak = 10; 259 } 260 mNeedlePos = 32767; 261 mNeedleSpeed = 0; 262 } 263 if (mWorldState.mPeak > 0) { 264 mWorldState.mPeak--; 265 } 266 267 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range 268 mState.data(mWorldState); 269 } 270 } 271