《智能设备艺术、科技、文化作品实例开发与设计》android开发系列介绍---1.3琴类作品:小提琴

    xiaoxiao2021-12-04  17

    第一章 琴类作品

    1.3 小提琴开发

     

    小提琴广泛流传于世界各国,是现代管弦乐队弦乐组中最主要的乐器。它在器乐中占非常重要的地位,是现代交响乐队的支柱,也是具有高难度演奏技巧的独奏乐器,与钢琴、古典吉他并称为世界三大乐器。

    小提琴(意大利文:Il violino )是一种弦乐器。总共有四根弦。靠摩擦发出声音。所以现实的小提琴需要上松香和调音,是一个不好上手的乐器,用手机模拟小提琴我采用了两种方式,第一种是靠重力传感器,约在90度,45度,0度,-45度来判断四根弦,用手指在屏幕接触的横坐标来判别触弦位置,实现一个把位的演奏。第二种是靠方向感应,判断为来回拉动琴弦,根据载入的曲谱,只要每拉动琴弦一次就载入一个音符,这样只要掌握节奏就能演奏小提琴,让大家一分钟就能掌握小提琴这高雅乐器。

     

    1.3.1在资源中加载小提琴音符文件

       依然和前面开发乐器一样在资源中加载小提琴音符mp3文件,用MediaPlayer发声。

     

    步骤1.新建一个空白HELLOWORLD工程,

    步骤2.res文件夹中新建raw文件夹,并将音效mp3文件拷入

     

    1.2.2音效制作

       依然和前面开发乐器一样通过,方法一:通过录音。

       方法二:通过网络电脑找到下吉他弹奏的每个音阶的素材。

       方法三:手中有部分的音阶的素材,也可通过GoldWave等软件通过音调调整生成新的吉他音阶。

    来得到我们想要的素材。

     

     

    1.3.3吉他画面的绘制

    手机上演奏小提琴主要靠重力和方向感应,配合手指按键大概把屏幕横坐标分成45个区域,大概的精度为130像素。不像演奏钢琴、吉他那样要看没屏幕,所以可以随便做个背景图,如果调测的话可显示出传感参数值等。

    1.3.4重力感应

    手机重力感应技术:利用压电效应实现,简单来说是是测量内部一片重物(重物和压电片做成一体)重力正交两个方向的分力大小,来判定水平方向。

    手机重力感应指的是手机内置重力摇杆芯片,支持摇晃切换所需的界面和功能,甩歌甩屏,翻转静音,甩动切换视频等,是一种非常具有使用乐趣的功能。

     Android的开发中一共有八种传感器但是不一定每一款真机都支持这些传感器。因为很多功能用户根本不care的所以可能开发商会把某些功能屏蔽掉。还是得根据真机的实际情况来做开发,今天我们主要来讨论加速度传感器的具体实现方式。        传感器名称如下:        加速度传感器(accelerometer)        陀螺仪传感器(gyroscope)        环境光照传感器(light)        磁力传感器(magneticfield)        方向传感器(orientation)        压力传感器(pressure)        距离传感器(proximity)        温度传感器(temperature)

     1.SensorMannager传感器管理对象        手机中的所有传感器都须要通过SensorMannager来访问,调用getSystemService(SENSOR_SERVICE)方法就可以拿到当前手机的传感器管理对象。

    SensorManager mSensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);

         2.实现SensorEventListener接口        说道SensorEventListener接口就不得不说SensorListener接口。在Android1.5一下是通过实现SensorListener接口来捕获手机传感器状态,但是在1.5以上如果实现这个接口系统会提示你这行代码已经过期。今天我们不讨论SensorListener因为它已经是过时的东西了。主要讨论一下SensorEventListener接口。我们须要实现SensorEventListener这个接口 onSensorChanged(SensorEvent event)方法来捕获手机传感器的状态,拿到手机 XYZ轴三个方向的重力分量,有了这三个方向的数据重力感应的原理我们就已经学会了。 public void onSensorChanged(SensorEvent e){ 

    float x = e.values[SensorManager.DATA_X];  float y = e.values[SensorManager.DATA_Y];  float z = e.values[SensorManager.DATA_Z];  }

          手机屏幕向左侧方当X轴就朝向天空,垂直放置这时候 Y Z轴没有重力分量,因为X轴朝向天空所以它的重力分量则最大。这时候X Y Z轴的重力分量的值分别为(1000手机屏幕向右侧方当X轴就朝向地面,这时候X Y Z轴的重力分量的值分别为(-1000)。     

    手机屏幕垂直竖立放置方当Y轴就朝向天空,垂直放置这时候 X Z轴没有重力分量,因为Y轴朝向天空所以它的重力分量则最大。这时候X Y Z轴的重力分量的值分别为(0100)。

     手机屏幕向上当Z轴就朝向天空,水平放置这时候 X轴与Y轴没有重力分量,因为Z轴朝向天空所以它的重力分量则最大。这时候X Y Z轴的重力分量的值分别为(0010)。

       在小提琴拉弦模拟中采用约在90度,45度,0度,-45度来判断四根弦,就是利用重力Y轴传感值来决定。

    if (mGY < -2) {

                    tab = 0;

                } else if (mGY < 3 && mGY > -2) {

                    tab = 1;

                } else if (mGY > 3 && mGY < 7) {

                    tab = 2;

                } else if (mGY > 7) {

                    tab = 3;

                }

     3.注册SensorEventListener        使用SensorMannager调用getDefaultSensor(Sensor.TYPE_ACCELEROMETER)方法拿到加速重力感应的Sensor对象。我们讨论重力加速度传感器所以参数为Sensor.TYPE_ACCELEROMETER,如果须要拿到其它的传感器须要传入对应的名称。使用SensorMannager调用registerListener()方法来注册,第三个参数是检测的灵敏精确度根据不同的需求来选择精准度,游戏开发建议使用  SensorManager.SENSOR_DELAY_GAME

    1.      mSensorMgr = (SensorManager)getSystemService(SENSOR_SERVICE); 

    2.      mSensor =mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 

    3.      //注册listener,第三个参数是检测的精确度 

    4.      //SENSOR_DELAY_FASTEST最灵敏因为太快了没必要使用 

    5.      //SENSOR_DELAY_GAME游戏开发中使用 

    6.      //SENSOR_DELAY_NORMAL正常速度 

    7.      //SENSOR_DELAY_UI最慢的速度 

    8.      mSensorMgr.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_GAME);

    重力感应简单速度计算的方式。每次摇晃手机计算出 X Y Z轴的重力分量可以将它们记录下来然后每次摇晃的重力分量和之前的重力分量可以做一个对比利用差值和时间就可以计算出他们的移动速度。并写在DRAW()函数中显示感应值,画出状态条。主要代码如下:

     

    public class SurfaceViewAcitvity extends Activity {

     

       MyView mAnimView = null;

     

       @Override

       public void onCreate(Bundle savedInstanceState){

          super.onCreate(savedInstanceState);

          // 全屏显示窗口

          requestWindowFeature(Window.FEATURE_NO_TITLE);

          getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

                WindowManager.LayoutParams.FLAG_FULLSCREEN);

          // 强制横屏

          setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

     

          // 显示自定义的游戏View

          mAnimView = new MyView(this);

          setContentView(mAnimView);

       }

     

       public class MyView extends SurfaceViewimplements Callback, Runnable,

             SensorEventListener {

     

          /** 30帧刷新一次屏幕 **/

          public static final int TIME_IN_FRAME = 30;

     

          /** 游戏画笔 **/

          Paint mPaint = null;

          Paint mTextPaint = null;

          SurfaceHolder mSurfaceHolder =null;

     

          /** 控制游戏更新循环 **/

          booleanmRunning =false;

     

          /** 游戏画布 **/

          Canvas mCanvas = null;

     

          booleanmIsRunning =false;

     

         

          private SensorManagermSensorMgr =null;

          Sensor mSensor = null;

     

          /** 手机屏幕宽高 **/

          int mScreenWidth = 0;

          int mScreenHeight = 0;

          int i = 0, k = 0, k2 = 0, k1 = 0;

          float y, maxy = 0;

         

     

          int startPos;

          int tab, xy, xy0;

          float mGY0;

          float mGX0;

          /** 小球资源文件越界区域 **/

          private int mScreenBallWidth = 0;

          private int mScreenBallHeight = 0;

     

          /** 游戏背景文件 **/

          private BitmapmbitmapBg;

     

          private float mPosX = 200;

          private float mPosY = 0;

     

          /** 重力感应X Y Z轴的重力值 **/

          private float mGX = 0;

          private float mGY = 0;

          private float mGZ = 0;

     

         

          public MyView(Context context) {

     

             super(context);

             /** 设置当前View拥有控制焦点 **/

             this.setFocusable(true);

             /** 设置当前View拥有触摸事件 **/

             this.setFocusableInTouchMode(true);

         

             /** 拿到SurfaceHolder对象 **/

             mSurfaceHolder = this.getHolder();

             /** mSurfaceHolder添加到Callback回调函数中 **/

             mSurfaceHolder.addCallback(this);

             /** 创建画布 **/

             mCanvas = new Canvas();

             /** 创建曲线画笔 **/

             mPaint = new Paint();

             mPaint.setColor(Color.WHITE);

         

             /** 加载游戏背景 **/

             mbitmapBg = BitmapFactory.decodeResource(this.getResources(),

                    R.drawable.bg);

     

             /** 得到SensorManager对象 **/

             mSensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);

             mSensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

             // 注册listener,第三个参数是检测的精确度

             // SENSOR_DELAY_FASTEST 最灵敏因为太快了没必要使用

             // SENSOR_DELAY_GAME 游戏开发中使用

             // SENSOR_DELAY_NORMAL 正常速度

             // SENSOR_DELAY_UI 最慢的速度

             mSensorMgr.registerListener(this,mSensor,

                    SensorManager.SENSOR_DELAY_GAME);

          }

     

          private void Draw() {

     

             /** 绘制游戏背景 **/

             Shader mShader = new LinearGradient(0, 0, 100, 100,newint[] {

                    Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW }, null,

                    Shader.TileMode.REPEAT);

             mPaint.setShader(mShader);

             mCanvas.drawBitmap(mbitmapBg, 0, 0,mPaint);

         

         

             mCanvas.drawText("X轴重力值" +maxy + "   ***" +xy +"   ***" +mGX,

                    0, 20, mPaint);

             mCanvas.drawText("Y轴重力值" +mGY, 0, 40, mPaint);

             // mCanvas.drawText("Z轴重力值" + mGZ, 0, 60, mPaint);

             mCanvas.drawRect(90, 90, 111,mGX * 10 + 100,mPaint);

             mCanvas.drawRect(190, 190, 211,mGY * 10 + 100,mPaint);

             mCanvas.drawRect(290, 290,mGZ * 10 + 100, 311,mPaint);

          }

     

          @Override

          public void surfaceChanged(SurfaceHolder holder, int format,int width,

                int height) {

     

          }

     

          @Override

          public void surfaceCreated(SurfaceHolder holder) {

             /** 开始游戏主循环线程 **/

             mIsRunning = true;

             new Thread(this).start();

             /** 得到当前屏幕宽高 **/

             mScreenWidth = this.getWidth();

             mScreenHeight = this.getHeight();

     

         

          }

     

          @Override

          public void surfaceDestroyed(SurfaceHolder holder) {

             mIsRunning = false;

          }

     

          @Override

          public void run() {

             while (mIsRunning) {

     

                /** 取得更新游戏之前的时间 **/

                long startTime = System.currentTimeMillis();

     

                /** 在这里加上线程安全锁 **/

                synchronized (mSurfaceHolder) {

                    /** 拿到当前画布然后锁定 **/

                    mCanvas = mSurfaceHolder.lockCanvas();

                    Draw();

                    /** 绘制结束后解锁显示在屏幕上 **/

                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);

                }

     

                /** 取得更新游戏结束的时间 **/

                long endTime = System.currentTimeMillis();

     

                /** 计算出游戏一次更新的毫秒数 **/

                int diffTime = (int) (endTime - startTime);

     

                /** 确保每次更新时间为50 **/

                while (diffTime <=TIME_IN_FRAME) {

                    diffTime = (int) (System.currentTimeMillis() -startTime);

                    /** 线程等待 **/

                    Thread.yield();

                }

     

             }

     

          }

     

          @Override

          public void onAccuracyChanged(Sensor arg0, int arg1) {

             // TODO Auto-generated method stub

     

          }

     

          @Override

          public void onSensorChanged(SensorEvent event) {

             mGX = event.values[SensorManager.DATA_X];

             mGY = event.values[SensorManager.DATA_Y];

             mGZ = event.values[SensorManager.DATA_Z];

     

             mPosX -= mGX * 2;

             mPosY += mGY * 2;

     

             if (mPosX < 0) {

                mPosX = 0;

             } else if (mPosX > mScreenBallWidth) {

                mPosX = mScreenBallWidth;

             }

             if (mPosY < 0) {

                mPosY = 0;

             } else if (mPosY > mScreenBallHeight) {

                mPosY = mScreenBallHeight;

             }

          }

       }

     

    }

     

    1.3.5方向感应

    mSensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);

             mSensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_ORIENTATION);

     

    Android中的方向传感器在生活中是指南针的使用,我们先来简单介绍一下传感器中三个参数x,y,z的含义,以一幅图来说明。

    补充说明:图中的坐标轴x,y,z和传感器中的X,Y,Z没有任何联系,

    如上图所示,矩形部分表示一个手机,带有小圈那一头是手机头部

    传感器中的X:如上图所示,规定X正半轴为北,手机头部指向OF方向,此时X的值为0,如果手机头部指向OG方向,此时X值为90,指向OH方向,X值为180,指向OEX值为270,可理解为正北为零,转一圈360度的方位角。

    传感器中的Y:现在我们将手机沿着BC轴慢慢向上抬起,即手机头部不动,尾部慢慢向上翘起来,直到AD跑到BC右边并落在XOY平面上,Y的值将从0~180之间变动,如果手机沿着AD轴慢慢向上抬起,即手机尾部不懂,直到BC跑到AD左边并且落在XOY平面上,Y的值将从0~-180之间变动,这就是方向传感器中Y的含义。可理解为水平为0度,垂直为90度手机窄边为轴的俯仰角。

     

    传感器中的Z:现在我们将手机沿着AB轴慢慢向上抬起,即手机左边框不动,右边框慢慢向上翘起来,直到CD跑到AB右边并落在XOY平面上,Z的值将从0~90之间变动,如果手机沿着CD轴慢慢向上抬起,即手机右边框不动,直到AB跑到CD左边并且落在XOY平面上,Z的值将从0~-90之间变动,可理解为水平为0度,垂直为90度手机长木木木木边为轴的俯仰角。

     

    了解了方向传感器中X,YZ的含义之后下面我们就开始学习如何使用

    首先我们创建一个传感器管理器和一个传感器监听器,管理器用来管理传感器以及创建各种各样的传感器,监听器用来监视传感器的变化并且进行相应的操作

    private SensorManagersensorManager;

    private MySensorEventListenermySensorEventListener;

    mySensorEventListener= newMySensorEventListener();//这个监听器当然是我们自己定义的,在方向感应器感应到手机方向有变化的时候,我们可以采取相应的操作,这里紧紧是将x,y,z的值打印出来

    private final classMySensorEventListener implements  SensorEventListener{

     

    @Override

    //可以得到传感器实时测量出来的变化值

    public voidonSensorChanged(SensorEvent event) {

    //方向传感器

    if(event.sensor.getType()==Sensor.TYPE_ORIENTATION){

    //x表示手机指向的方位,0表示北,90表示东,180表示南,270表示西

    float x =event.values[SensorManager.DATA_X];

    float y =event.values[SensorManager.DATA_Y];

    float z =event.values[SensorManager.DATA_Z];

     

    我们在onResume方法中创建一个方向传感器,并向系统注册监听器

    protected void onResume() {

        Sensorsensor_orientation=sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

        sensorManager.registerListener(mySensorEventListener,sensor_orientation,SensorManager.SENSOR_DELAY_UI);

    super.onResume();

    }

    最后我们在onPause()中注销所有传感器的监听,释放方向感应器资源!

    protected void onPause() {

    //注销所有传感器的监听

    sensorManager.unregisterListener(mySensorEventListener);

    super.onPause();

    }

      在小提琴拉弦方法2根据节奏来回模拟拉小提琴,发出每个音符,就是利用方位X轴传感值来变动决定。

     

     

    1.3.6小提琴实现方式1:重力感应与触屏结合

     

    在触屏事件中判断屏幕横坐标上的位置,每隔130像素加1

       public boolean onTouchEvent(MotionEvent event) {

             /* 取得手指触控屏幕的位置 */

     

             // event.getAction() ==

             // MotionEvent.ACTION_POINTER_1_DOWN

             // MotionEvent.ACTION_POINTER_2_DOWN

             int p = event.getPointerCount();

             // float x = event.getX(p-1);

             maxy = 0;

             for (i = 0;i < p;i++) {

                y = event.getX(i);

                if (maxy <y) {

                    maxy = y;

                }

             }

     

             xy = (int)maxy / 130;

             // xy=p-1;

             return true;

          }

    MyView中Draw()函数每秒执行30次,循环判断Y轴重力值与触屏位置,来判断该演奏哪个音符,startPos = (tab * 4 + xy) * 1500;这个里与之前每个音做个一个MP3文件在资源文件不一样,把所有的音做成一个MP3文件在资源文件夹中。每音间隔1500毫秒,所以循环判断Y轴重力值与触屏位置算来系数后乘以1500,就是播放MP3位置,从而发出音符来。

    private void Draw() {

     

             /** 绘制游戏背景 **/

         

             mCanvas.drawBitmap(mbitmapBg, 0, 0,mPaint);

         

             /** X Y Z轴的重力值 **/

             if (Math.abs(mGY -mGY0) > 1) {

                k++;

             } else {

                k = 0;

             }

             if (mGY <= -8) {

                // mMediaPlayer01.pause();

                mMediaPlayer01.pause();// .stop();

            

                       }

     

         

             else if (Math.abs(mGY - mGY0) > 2 || Math.abs(xy - xy0) > 0) {

                xy0 = xy;

                if (mGY < -2) {

                    tab = 0;

                } else if (mGY < 3 && mGY > -2) {

                    tab = 1;

                } else if (mGY > 3 && mGY < 7) {

                    tab = 2;

                } else if (mGY > 7) {

                    tab = 3;

                }

                if (song[k2] == 0) {

                    k2 = 0;

                }

                // startPos= (song[k2])*1500;k2++;

                startPos = (tab * 4 +xy) * 1500;

                k2++;

                mMediaPlayer01.seekTo(startPos);

                mMediaPlayer01.start();

                i--;

                mGY0 = mGY;

                mGX0 = mGX;

             }

     

             mCanvas.drawText("X轴重力值" +maxy + "   ***" +xy +"   ***" +mGX,

                    0, 20, mPaint);

             mCanvas.drawText("Y轴重力值" +mGY, 0, 40, mPaint);

             // mCanvas.drawText("Z轴重力值" + mGZ, 0, 60, mPaint);

             mCanvas.drawRect(90, 90, 111,mGX * 10 + 100,mPaint);

             mCanvas.drawRect(190, 190, 211,mGY * 10 + 100,mPaint);

             mCanvas.drawRect(290, 290,mGZ * 10 + 100, 311,mPaint);

          }

     

    1.3.7小提琴实现方式2:方位感应与加载曲谱发出音符

      定义乐谱数组int song[] = { 33, 3, 5, 6, 11, 12, 6, 11, 5, 15, 21, 16, 15, 13, 15, 12, 12, 13,7, 6, 5, 6, 11, 12, 3, 11, 6, 5, 6, 11, 5, 13, 15, 5};

    或在编辑框中输入。

      判断方位传感有变化到门限值时,根据数组加载音符

    if ((mGY -mGY0) < (-1.5 -tt) &&k == 1) {

                mMediaPlayer01.release();//

                switch (song[k2]) {

     

                case 33:

                    mMediaPlayer01 = MediaPlayer.create(

                          SurfaceViewAcitvity.this, R.raw.ss);

                    mMediaPlayer01.start();

                    break;

                case 5:

                    mMediaPlayer01 = MediaPlayer.create(

                          SurfaceViewAcitvity.this, R.raw.m13);

                    mMediaPlayer01.start();

                    break;

                case 6:

                    mMediaPlayer01 = MediaPlayer.create(

                          SurfaceViewAcitvity.this, R.raw.m145);

                    mMediaPlayer01.start();

                    break;

                case 7:

                    mMediaPlayer01 = MediaPlayer.create(

                          SurfaceViewAcitvity.this, R.raw.m155);

                    mMediaPlayer01.start();

                    break;

                case 11:

                    mMediaPlayer01 = MediaPlayer.create(

                          SurfaceViewAcitvity.this, R.raw.m16);

                    mMediaPlayer01.start();

                    break;

     

    方式2如视频所示。注:必须在AndroidManifest.xml中添加权限,否侧摇动时不能监听

    <uses-permissionandroid:name="android.hardware.sensor.accelerometer"/>

     

    转载请注明原文地址: https://ju.6miu.com/read-680413.html

    最新回复(0)