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.*; 29 import android.renderscript.ProgramStore.BlendDstFunc; 30 import android.renderscript.ProgramStore.BlendSrcFunc; 31 32 import java.util.TimeZone; 33 import android.util.Log; 34 35 class Visualization4RS 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 } 55 WorldState mWorldState = new WorldState(); 56 57 ScriptC_vu mScript; 58 59 private ProgramStore mPfsBackground; 60 private ProgramFragment mPfBackground; 61 private Sampler mSampler; 62 private Allocation[] mTextures; 63 64 private ProgramVertex mPVBackground; 65 private ProgramVertexFixedFunction.Constants mPVAlloc; 66 67 private AudioCapture mAudioCapture = null; 68 private int [] mVizData = new int[1024]; 69 70 private static final int RSID_STATE = 0; 71 private static final int RSID_POINTS = 1; 72 private static final int RSID_LINES = 2; 73 private static final int RSID_PROGRAMVERTEX = 3; 74 75 76 Visualization4RS(int width, int height) { 77 super(width, height); 78 mWidth = width; 79 mHeight = height; 80 } 81 82 @Override 83 public void resize(int width, int height) { 84 super.resize(width, height); 85 if (mPVAlloc != null) { 86 Matrix4f proj = new Matrix4f(); 87 proj.loadProjectionNormalized(width, height); 88 mPVAlloc.setProjection(proj); 89 } 90 } 91 92 @Override 93 protected ScriptC createScript() { 94 95 mScript = new ScriptC_vu(mRS, mResources, R.raw.vu); 96 97 // First set up the coordinate system and such 98 ProgramVertexFixedFunction.Builder pvb = new ProgramVertexFixedFunction.Builder(mRS); 99 mPVBackground = pvb.create(); 100 mPVAlloc = new ProgramVertexFixedFunction.Constants(mRS); 101 ((ProgramVertexFixedFunction)mPVBackground).bindConstants(mPVAlloc); 102 Matrix4f proj = new Matrix4f(); 103 proj.loadProjectionNormalized(mWidth, mHeight); 104 mPVAlloc.setProjection(proj); 105 106 mScript.set_gPVBackground(mPVBackground); 107 108 updateWave(); 109 110 mTextures = new Allocation[6]; 111 mTextures[0] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.background, 112 Allocation.MipmapControl.MIPMAP_NONE, 113 Allocation.USAGE_GRAPHICS_TEXTURE); 114 mScript.set_gTvumeter_background(mTextures[0]); 115 mTextures[1] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.frame, 116 Allocation.MipmapControl.MIPMAP_NONE, 117 Allocation.USAGE_GRAPHICS_TEXTURE); 118 mScript.set_gTvumeter_frame(mTextures[1]); 119 mTextures[2] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_on, 120 Allocation.MipmapControl.MIPMAP_NONE, 121 Allocation.USAGE_GRAPHICS_TEXTURE); 122 mScript.set_gTvumeter_peak_on(mTextures[2]); 123 mTextures[3] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_off, 124 Allocation.MipmapControl.MIPMAP_NONE, 125 Allocation.USAGE_GRAPHICS_TEXTURE); 126 mScript.set_gTvumeter_peak_off(mTextures[3]); 127 mTextures[4] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.needle, 128 Allocation.MipmapControl.MIPMAP_NONE, 129 Allocation.USAGE_GRAPHICS_TEXTURE); 130 mScript.set_gTvumeter_needle(mTextures[4]); 131 mTextures[5] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.black, 132 Allocation.MipmapControl.MIPMAP_NONE, 133 Allocation.USAGE_GRAPHICS_TEXTURE); 134 mScript.set_gTvumeter_black(mTextures[5]); 135 136 Sampler.Builder samplerBuilder = new Sampler.Builder(mRS); 137 samplerBuilder.setMinification(LINEAR); 138 samplerBuilder.setMagnification(LINEAR); 139 samplerBuilder.setWrapS(WRAP); 140 samplerBuilder.setWrapT(WRAP); 141 mSampler = samplerBuilder.create(); 142 143 { 144 ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS); 145 builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, 146 ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); 147 mPfBackground = builder.create(); 148 mPfBackground.bindSampler(mSampler, 0); 149 150 mScript.set_gPFBackground(mPfBackground); 151 } 152 153 { 154 ProgramStore.Builder builder = new ProgramStore.Builder(mRS); 155 builder.setDepthFunc(ALWAYS); 156 //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 157 builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 158 builder.setDitherEnabled(true); // without dithering there is severe banding 159 builder.setDepthMaskEnabled(false); 160 mPfsBackground = builder.create(); 161 162 mScript.set_gPFSBackground(mPfsBackground); 163 } 164 165 mScript.setTimeZone(TimeZone.getDefault().getID()); 166 167 return mScript; 168 } 169 170 @Override 171 public void start() { 172 super.start(); 173 mVisible = true; 174 if (mAudioCapture == null) { 175 mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024); 176 } 177 mAudioCapture.start(); 178 updateWave(); 179 } 180 181 @Override 182 public void stop() { 183 super.stop(); 184 mVisible = false; 185 if (mAudioCapture != null) { 186 mAudioCapture.stop(); 187 mAudioCapture.release(); 188 mAudioCapture = null; 189 } 190 } 191 192 void updateWave() { 193 mHandler.removeCallbacks(mDrawCube); 194 if (!mVisible) { 195 return; 196 } 197 mHandler.postDelayed(mDrawCube, 20); 198 199 int len = 0; 200 if (mAudioCapture != null) { 201 // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit) 202 mVizData = mAudioCapture.getFormattedData(512, 1); 203 len = mVizData.length; 204 } 205 206 // Simulate running the signal through a rectifier by 207 // taking the average of the absolute sample values. 208 int volt = 0; 209 if (len > 0) { 210 for (int i = 0; i < len; i++) { 211 int val = mVizData[i]; 212 if (val < 0) { 213 val = -val; 214 } 215 volt += val; 216 } 217 volt = volt / len; 218 } 219 // There are several forces working on the needle: a force applied by the 220 // electromagnet, a force applied by the spring, and friction. 221 // The force from the magnet is proportional to the current flowing 222 // through its coil. We have to take in to account that the coil is an 223 // inductive load, which means that an immediate change in applied voltage 224 // will result in a gradual change in current, but also that current will 225 // be induced by the movement of the needle. 226 // The force from the spring is proportional to the position of the needle. 227 // The friction force is a function of the speed of the needle, but so is 228 // the current induced by the movement of the needle, so we can combine 229 // them. 230 231 232 // Add up the various forces, with some multipliers to make the movement 233 // of the needle more realistic 234 // 'volt' is for the applied voltage, which causes a current to flow through the coil 235 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current 236 // in the coil, and is also proportional to the friction 237 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back 238 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ; 239 int acceleration = netforce / mNeedleMass; 240 mNeedleSpeed += acceleration; 241 mNeedlePos += mNeedleSpeed; 242 if (mNeedlePos < 0) { 243 mNeedlePos = 0; 244 mNeedleSpeed = 0; 245 } else if (mNeedlePos > 32767) { 246 if (mNeedlePos > 33333) { 247 mWorldState.mPeak = 10; 248 } 249 mNeedlePos = 32767; 250 mNeedleSpeed = 0; 251 } 252 if (mWorldState.mPeak > 0) { 253 mWorldState.mPeak--; 254 } 255 256 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range 257 mScript.set_gAngle(mWorldState.mAngle); 258 mScript.set_gPeak(mWorldState.mPeak); 259 } 260 } 261