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 mScript.set_gTvumeter_background(mTextures[0]); 113 mTextures[1] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.frame); 114 mScript.set_gTvumeter_frame(mTextures[1]); 115 mTextures[2] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_on); 116 mScript.set_gTvumeter_peak_on(mTextures[2]); 117 mTextures[3] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.peak_off); 118 mScript.set_gTvumeter_peak_off(mTextures[3]); 119 mTextures[4] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.needle); 120 mScript.set_gTvumeter_needle(mTextures[4]); 121 mTextures[5] = Allocation.createFromBitmapResource(mRS, mResources, R.drawable.black); 122 mScript.set_gTvumeter_black(mTextures[5]); 123 124 Sampler.Builder samplerBuilder = new Sampler.Builder(mRS); 125 samplerBuilder.setMinification(LINEAR); 126 samplerBuilder.setMagnification(LINEAR); 127 samplerBuilder.setWrapS(WRAP); 128 samplerBuilder.setWrapT(WRAP); 129 mSampler = samplerBuilder.create(); 130 131 { 132 ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS); 133 builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, 134 ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); 135 mPfBackground = builder.create(); 136 mPfBackground.bindSampler(mSampler, 0); 137 138 mScript.set_gPFBackground(mPfBackground); 139 } 140 141 { 142 ProgramStore.Builder builder = new ProgramStore.Builder(mRS); 143 builder.setDepthFunc(ALWAYS); 144 //builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 145 builder.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA); 146 builder.setDitherEnabled(true); // without dithering there is severe banding 147 builder.setDepthMaskEnabled(false); 148 mPfsBackground = builder.create(); 149 150 mScript.set_gPFSBackground(mPfsBackground); 151 } 152 153 mScript.setTimeZone(TimeZone.getDefault().getID()); 154 155 return mScript; 156 } 157 158 @Override 159 public void start() { 160 super.start(); 161 mVisible = true; 162 if (mAudioCapture == null) { 163 mAudioCapture = new AudioCapture(AudioCapture.TYPE_PCM, 1024); 164 } 165 mAudioCapture.start(); 166 updateWave(); 167 } 168 169 @Override 170 public void stop() { 171 super.stop(); 172 mVisible = false; 173 if (mAudioCapture != null) { 174 mAudioCapture.stop(); 175 mAudioCapture.release(); 176 mAudioCapture = null; 177 } 178 } 179 180 void updateWave() { 181 mHandler.removeCallbacks(mDrawCube); 182 if (!mVisible) { 183 return; 184 } 185 mHandler.postDelayed(mDrawCube, 20); 186 187 int len = 0; 188 if (mAudioCapture != null) { 189 // arbitrary scalar to get better range: 512 = 2 * 256 (256 for 8 to 16 bit) 190 mVizData = mAudioCapture.getFormattedData(512, 1); 191 len = mVizData.length; 192 } 193 194 // Simulate running the signal through a rectifier by 195 // taking the average of the absolute sample values. 196 int volt = 0; 197 if (len > 0) { 198 for (int i = 0; i < len; i++) { 199 int val = mVizData[i]; 200 if (val < 0) { 201 val = -val; 202 } 203 volt += val; 204 } 205 volt = volt / len; 206 } 207 // There are several forces working on the needle: a force applied by the 208 // electromagnet, a force applied by the spring, and friction. 209 // The force from the magnet is proportional to the current flowing 210 // through its coil. We have to take in to account that the coil is an 211 // inductive load, which means that an immediate change in applied voltage 212 // will result in a gradual change in current, but also that current will 213 // be induced by the movement of the needle. 214 // The force from the spring is proportional to the position of the needle. 215 // The friction force is a function of the speed of the needle, but so is 216 // the current induced by the movement of the needle, so we can combine 217 // them. 218 219 220 // Add up the various forces, with some multipliers to make the movement 221 // of the needle more realistic 222 // 'volt' is for the applied voltage, which causes a current to flow through the coil 223 // mNeedleSpeed * 3 is for the movement of the needle, which induces an opposite current 224 // in the coil, and is also proportional to the friction 225 // mNeedlePos + mSpringForceAtOrigin is for the force of the spring pushing the needle back 226 int netforce = volt - mNeedleSpeed * 3 - (mNeedlePos + mSpringForceAtOrigin) ; 227 int acceleration = netforce / mNeedleMass; 228 mNeedleSpeed += acceleration; 229 mNeedlePos += mNeedleSpeed; 230 if (mNeedlePos < 0) { 231 mNeedlePos = 0; 232 mNeedleSpeed = 0; 233 } else if (mNeedlePos > 32767) { 234 if (mNeedlePos > 33333) { 235 mWorldState.mPeak = 10; 236 } 237 mNeedlePos = 32767; 238 mNeedleSpeed = 0; 239 } 240 if (mWorldState.mPeak > 0) { 241 mWorldState.mPeak--; 242 } 243 244 mWorldState.mAngle = 131f - (mNeedlePos / 410f); // ~80 degree range 245 mScript.set_gAngle(mWorldState.mAngle); 246 mScript.set_gPeak(mWorldState.mPeak); 247 } 248 } 249