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