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