Android(Java)を始める – AudioTrackで動的サウンド生成

Filed under Android, Java

絶賛Androidの勉強中。

AudioTrackを使って動的に音を生成してます。

クリックするとランダムに音を作って鳴らしてくれます。

以前AS3で作ったものをAndroidに移植した感じです。

ちなみにIS06だとちゃんと動いたけど、HT-03Aだと音がプチプチでした。

Androidでも動的サウンド生成って出来るんですね。

知りませんでした。

以下、今回のソースです。

Main.java。

package jp.sakef;

import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Main extends Activity
{
	private static int SAMPLING_RATE = 44100;
	private static int BUFFER = 1024;

	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		setVolumeControlStream(AudioManager.STREAM_MUSIC);
		setContentView(new MainView(getApplicationContext()));
	}

	// 描画&音の生成を行うクラス
	public class MainView extends SurfaceView implements SurfaceHolder.Callback, Runnable
	{
		private AudioTrack track;
		private Paint paint;
		private SurfaceHolder sholder;
		private Thread thread;
		private short samples[];
		private ArrayList<SoundData> container;

		public MainView(Context context)
		{
			super(context);

			sholder = null;
			thread = null;
			paint = new Paint();
			paint.setAntiAlias(true);
			samples  = new short[BUFFER];
			container = new ArrayList<SoundData>();

			int minBufferSize = AudioTrack.getMinBufferSize(SAMPLING_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
			track = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLING_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
			track.play();

			getHolder().addCallback(this);
		}

		public void surfaceCreated(SurfaceHolder holder)
		{
			sholder = holder;
			thread = new Thread(this);
		}

		public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
		{
			if(thread != null ) thread.start();
		}

		public void surfaceDestroyed(SurfaceHolder holder)
		{
			thread = null;
			track.stop();
			track.release();
		}

		public void run()
		{
			SoundData sData;
			int sSize;

			while (thread != null)
			{
				// 白く塗りつぶす
				Canvas canvas = sholder.lockCanvas();
				canvas.drawColor(Color.argb(255, 255, 255, 255));

				// 音を生成&書き込み
				for(int i = 0; i < BUFFER; i++ )
				{
					float totalWav = 0.0f;
					sSize = container.size();
					while(sSize > 0)
					{
						sData = container.get(sSize-1);
						sData.position =  (float) ((sData.position + sData.w)%(2*Math.PI));

						float eachWav;
						if(sData.position < Math.PI) eachWav = (float) (1 - 2*sData.position/Math.PI);
						else eachWav = (float) (-1 + 2 * (sData.position - Math.PI)/Math.PI);

						totalWav += eachWav * 0.4 * sData.volume;
						sSize--;
					}

					// データを範囲内に丸める
					totalWav = (totalWav > -0.9)?(totalWav):(-0.9f);
					totalWav = (totalWav < 0.9)?(totalWav):(0.9f);
					samples[i] =  (short) (Short.MAX_VALUE * totalWav);
				}
				track.write(samples, 0, samples.length);

				// 音データの更新&円の描画
				sSize = container.size();
				while(sSize > 0)
				{
					sData = container.get(sSize-1);
					paint.setColor(Color.argb((int)(255*sData.volume), sData.cr, 255, sData.cb));
					canvas.drawCircle(sData.x, sData.y, sData.radius, paint);
					sData.render();
					if(sData.state == 3) container.remove(sData);
					sSize--;
				}
				sholder.unlockCanvasAndPost(canvas);
			}
		}

		// クリック時のイベント
		public boolean onTouchEvent(MotionEvent event)
		{
			if(event.getAction() == MotionEvent.ACTION_DOWN)
			{
				float frequency = (float) (440 * Math.pow(2, ((1.0 / 12.0) * 20 * Math.random())));
				container.add(new SoundData(frequency,0.4f, 0.01f, 2, 0.8f, event.getX(), event.getY()));
			}
			return true;
		}
	}

	// 音データを保持するクラス
	public class SoundData
	{
		public float frequency;
		public float volume;
		public float position;
		public float x;
		public float y;
		public float w;
		public int state;
		public int cr;
		public int cb;
		public int radius;
		private int count;
		private float fadeIn;
		private float fadeOut;
		private float show;
		private float volMax;

		// コンストラクタ
		public SoundData(float frequency, float fadeIn, float fadeOut, int show, float volMax, float x, float y)
		{
			this.frequency = frequency;
			this.fadeIn = fadeIn;
			this.fadeOut = fadeOut;
			this.show = show;
			this.volMax = volMax;
			this.x = x;
			this.y = y;

			position = 0;
			volume = state = count = radius = 0;
			w = (float) ((2*Math.PI * frequency) / SAMPLING_RATE);
			cr = (int)(255 * Math.random());
			cb = (int)(255 * Math.random());
		}

		// 更新用
		public void render()
		{
			radius +=2;
			switch(state)
			{
			// 音をフェードインさせる
			case 0:
				volume += fadeIn;
				if(volume>=volMax)
				{
					volume = volMax;
					state = 1;
				}
				break;

				// 音を流す
			case 1:
				count ++;
				if(count == show) state = 2;
				break;

				// 音をフェードアウトさせる
			case 2:
				volume -= fadeOut;
				if(volume <= 0)
				{
					state = 3;
					volume = 0;
				}
				break;
			}
		}
	}
}

Post a Comment

Your email is never published nor shared.