Home | History | Annotate | Download | only in vis4
      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