別の方法でテルミンを作る

2週間ぐらい開きましたが続けます。ただ今後は徐々にサウンド処理はAndroid NDKを使ってC++で行おうと思っています。最終的にはAudioTrackを叩くのですがやはり早いですね。路線変更したのはCausticの作者がサウンド処理はC++でUIはOpenGLだと言ってたからで、あのレスポンスの良さはそういうわけかと、ゲームのプログラマはつええなと。

さて簡単なテルミンもどきを作ります。Canvasに赤い丸があるので指で動かすと音が変化するというイメージです。

今までやってきたコールバックを検知するという方法ですが、これが遅い!。結局Threadを使います。こちらはいい感じです。2つを比較してみて相当な遅れがありましたので、今後はThreadのほうを採用します。いろいろ調べたのにThreadの方が早いとか怒り心頭ですよ。ただ誤解していたら教えてくださいコメントとかで。

package com.modoki.ThereminModoki;

import android.app.Activity;
import android.os.Bundle;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.view.View;
import android.content.Context;
import android.graphics.*;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.Display;

public class ThereminModoki extends Activity {
    public MyCanvas myview;
    SinOsc sinosc;
    short[] buf;
    int bf;
    public int pwidth;
    public int pheight;
    public Snd snd;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //ここから4行は画面のサイズを取得
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        pwidth = display.getWidth();
        pheight = display.getHeight();

        //画面
        myview = new MyCanvas(getApplication());
        setContentView(myview);

        //サイン波
        sinosc = new SinOsc(440, 1);
        snd = new Snd();
        snd.setPriority(10); //スレッドの優先順位で10が1番高い
        snd.start();
    }
    @Override
    public void onResume() {
        //電話を切って再び遊ぶために音を出す。
    	super.onResume();
    	sinosc.amp = 1;
    }
    @Override
    public void onPause() {
        //電話がかかってきたりした時のために音を絞る
    	super.onPause();
    	sinosc.amp = 0;
    }
    @Override
    public void onDestroy() {
    	//if( snd.track != null ){
    	    snd.isRunning = false; 
    	    snd.track.setStereoVolume(0, 0);
    	    if(snd.track.getPlayState()==AudioTrack.PLAYSTATE_PLAYING){
    	    	snd.track.stop();
    	    }
    	    snd.track.flush();
    	    snd.track.release();
    	//}
        super.onDestroy();
    }
    class MyCanvas extends View{
    	public int displayWidth;
    	public int displayHeight;
    	public int px;
    	public int py;
    	public int prepy;
    	public int size;
    	public MyCanvas(Context context){
    		super(context);
    		setFocusable(true);
    		displayWidth = pwidth;
    		displayHeight = pheight;
    		px = displayWidth/2;
    		py = displayHeight/2;
    		size = 25;
    		prepy = 0;
    	}
    	public void onDraw(Canvas canvas){
    		canvas.drawColor(Color.WHITE);
    		Paint paint = new Paint();
    		paint.setAntiAlias(true);
    		paint.setStyle(Paint.Style.FILL);
    		paint.setColor(Color.RED);
    		canvas.drawCircle(px, py, size, paint);		
    	}
    	public boolean onTouchEvent(MotionEvent event){
    		int tempx = (int)event.getX();
    		int tempy = (int)event.getY();
                //赤丸の内側に指があるときだけ反応する。
    		if(!(px-size>tempx || tempx>px+size || py-size>tempy || tempy>py+size)){
    		    px = (int)event.getX();
    		    py = (int)event.getY();
    		    switch(event.getAction()){
        		case MotionEvent.ACTION_DOWN:
        			size = 35;
        			invalidate();
        			break;
        		case MotionEvent.ACTION_MOVE:
        			sinosc.freq=(int)((px/(double)displayWidth)*3000.0);
        			sinosc.amp=(float)((py/(double)displayHeight)*1.0);
        		    size = Math.round(10)+35;
        			invalidate();
        			break;
        		case MotionEvent.ACTION_UP:
        			size = 30;
        			invalidate();
        			break;
        		}
    		}
    		return true;
    	}
    }
    class SinOsc{
        public double sampleRate = 44100;
        public double freq;
    	public double amp;
    	public double phase = 0;
    	public SinOsc(double f, double a){
    		freq = f;
    		amp = a;
    	}
    	public double updata(){
    		phase += freq/sampleRate;
    		phase = (phase > 1) ? 0 : phase;
    		return Math.sin(2*Math.PI*phase)*amp;
    	}
    }

    void sndOut(short data[],SinOsc input) {
        for (int i = 0; i < data.length; i++) {
            data[i] = (short)(Short.MAX_VALUE * input.updata());
        }
    }
    
    class Snd extends Thread {
        public AudioTrack track ;
        public int buffer_size;
        public int chunk_size;
        public short[] buf;
        public boolean isRunning = true;

        public Snd() {
        	buffer_size = android.media.AudioTrack.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);
        	track = new AudioTrack(AudioManager.STREAM_MUSIC,
                    44100, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, buffer_size,
                    AudioTrack.MODE_STREAM);
                chunk_size = buffer_size/4;
                buf = new short[chunk_size];
        }
        public void run() {
            track.play();
            while (isRunning) {
            	sndOut(buf,sinosc);
    		track.write(buf,0,buf.length);
    		//try {
                    //sleep(1);
                //} catch (Exception localException) {}

            }
        }
    }
}