NDKでテルミンを作る

ただテルミンを作るだけなのにC++サウンド処理するのは意味が無いのですが、何事も基本ですから。

Android NDKの使い方などの説明は優れた他のサイトへ。しかし簡単に述べるとproject以下にjniというフォルダを生成し、その中にヘッダファイル.hとC++ソースさらにmakeファイルなのかなAndroid.mkの3つを作ります、今回はApplication.mkは作らなかった。以下順番に貼りますので参考にしてください。

まずjavaのコードから。昨日のコードとほぼ同じですが、サイン波のクラスがごっそりありません。当然C++のソースに書かれるからです。C++の関数を呼んでるところに注意です。

package jun.miu.sndjni;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.widget.Button;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import java.lang.Thread;

public class SndJni extends Activity{
	Button button;
	public MyCanvas myview;
	public int pwidth;
	public int pheight;
	public Snd snd;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        pwidth = display.getWidth();
        pheight = display.getHeight();
        myview = new MyCanvas(getApplication());
        setContentView(myview);
        
        snd = new Snd();
        snd.setPriority(10);
        snd.start();
    }
    @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:
                                //実際にC++の関数を呼んで周波数と振幅を変化させている
        			setFrequency((int)((px/(double)displayWidth)*3000.0));
        			setAmplitude((float)((py/(double)displayHeight)*1.0));
        		    size = Math.round(10)+35;
        			invalidate();
        			break;
        		case MotionEvent.ACTION_UP:
        			size = 30;
        			invalidate();
        			break;
        		}
    		}
    		return true;
    	}
    }
    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) {
                        //ここでC++にサンド処理させます。
            	        final float chunkFloat[] = getSamples(chunk_size);
                	for (int i = 0; i < chunk_size; ++i) {
                            buf[i] = (short)(chunkFloat[i] * Short.MAX_VALUE);
                    }
    		        track.write(buf, 0, chunk_size);
    		        try {
                            sleep(1);
                        } catch (Exception localException) {}

            }
        }
    }
    //C++のメソッドを呼ぶための宣言。nativeをつけ忘れないように
    public static native float[] getSamples(int numSamples);
    public static native void setFrequency(int numFrequency);
    public static native void setAmplitude(float numAmplitude);
    //名前からしてライブラリをロードしているらしい
    static {
	    System.loadLibrary("synth");
    }
}

まずはヘッダファイル.hから。これは自動生成できるようですが(javahコマンドを使う)ルールを知れば自分でも書けます。CでもかけるのですがC++を使いたいのでextern "C" {}で囲ってます。後は「JNIEXPORT j戻り値の型 JNICALL パッケージ名+クラス名+メソッド名」となっています。あと第2引数がjclassをjobjectと書く人もいるようです、ソッチの方が正しいのか。ここら辺はあとで調べてみます。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jun_miu_sndjni_SndJni */

#ifndef _Included_jun_miu_sndjni_SndJni
#define _Included_jun_miu_sndjni_SndJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jun_miu_sndjni_SndJni
 * Method:    getSamples
 * Signature: (I)[F
 */
JNIEXPORT jfloatArray JNICALL Java_jun_miu_sndjni_SndJni_getSamples
  (JNIEnv *, jclass, jint);
 
 /*
 * Class:     jun_miu_sndjni_SndJni
 * Method:    setFrequency
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_jun_miu_sndjni_SndJni_setFrequency
  (JNIEnv *, jclass, jint);
  
   /*
 * Class:     jun_miu_sndjni_SndJni
 * Method:    setAmplitude
 * Signature: (F)V
 */
JNIEXPORT void JNICALL Java_jun_miu_sndjni_SndJni_setAmplitude
  (JNIEnv *, jclass, jfloat);

#ifdef __cplusplus
}
#endif
#endif

最後.cppです。読めばわかりますね。

#include <stddef.h>
#include <jni.h>
#include <synth-jni.h>
#include <math.h>
float phase = 0.0;
float freq = 440;
float amp = 1.0;
JNIEXPORT jfloatArray JNICALL Java_jun_miu_sndjni_SndJni_getSamples(
    JNIEnv* env,
    jclass obj,
    jint num_samples) {
      float* buffer = new float[num_samples];
      for (int i = 0; i < num_samples; ++i) {
      phase += freq/44100.0;
      if(phase > 1.0){
        phase = 0.0;
      }
      if(phase>0.5){
          buffer[i] = amp;
      }else{
          buffer[i] = -1*amp;
      }
  }
  jfloatArray result = env->NewFloatArray(num_samples);
  env->SetFloatArrayRegion(result, 0, num_samples, buffer); 
  delete buffer;
  return result;
}
JNIEXPORT void JNICALL Java_jun_miu_sndjni_SndJni_setFrequency(
    JNIEnv* env,
    jclass obj,
    jint num_frequency) {
    freq = (float)num_frequency;
    
}
JNIEXPORT void JNICALL Java_jun_miu_sndjni_SndJni_setAmplitude(
    JNIEnv* env,
    jclass obj,
    jfloat num_Amplitude) {
    amp = num_Amplitude;
    
}

次にAndroid.mkです。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := synth
LOCAL_SRC_FILES := synth-jni.cpp

include $(BUILD_SHARED_LIBRARY)

最後はご自身の環境でndk-buildを使ってコンパイルする。eclipseに戻って、、、となります。

とりあえずAndroid NDKのうまく説明されたサイトを参考にしてからいろいろ試してください。