Androidでサイン波、矩形波、三角波、ノコギリ波をスイッチ
まず楽器の中身に入る前に、昨日までの楽器はQUITボタンがありAudioTrack.release()で開放して終了していたのですが、QUITボタンとか画面に乗っけておくと間違えて触ってしまう。
@Override public void onDestroy() { //中略 track.release(); } super.onDestroy(); }
戻るボタンなんかを押せばonDestroy()が呼ばれるようなのでここに書きます。
後もう1つは、再生と停止のボタンがありましたがボタンを押したら再生で指を離したら停止とします。ボタン1つで済みます。onClickではボタンアップができないのでonTouchにします。onClickに書かれていた処理をonTouchに移動。言わずもがなOnTouchListenerをインプリメントします。
public class TestSnd extends Activity implements OnTouchListener{ public boolean onTouch(View view, MotionEvent event){ if(view == play && event.getAction() == MotionEvent.ACTION_DOWN) { //playボタンが押されたら音量を0から1に line.reset(0, 1); }else if(view == play && event.getAction() == MotionEvent.ACTION_UP){ //playボタンを話したら音量を0から1にして line.reset(1, 0);
GUIの部分はこんな感じ。
さて昨日のサイン波の周波数を変調する楽器に矩形波、三角波、ノコギリ波を追加します。昨日はSndオブジェクトをSinOscオブジェクトが継承していましたがその間にGenというオブジェクトを入れます。SinOscを矩形波、三角波、ノコギリ波を継承しても問題ないのですが、4つの波形とは違うオシレータ(サイン波や矩形波などをこう呼ぶ)なんかも追加するかもしれないので。
class Snd{ public double sampleRate = 44100; public Snd(){} public double updata(){ return 0; } } class Gen extends Snd{ public double freq = 0; public double amp = 0; public double phase = 0; public Snd addfreq = new Snd(); public Snd addamp = new Snd();; public Gen(){ freq = 0; amp = 0; } //中略 public Gen(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } //中略 } class SinOsc extends Gen{ //中略 public SinOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } //中略 public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? 0 : phase; return Math.sin(2*Math.PI*phase)*(amp + addamp.updata()); } }
このGenオブジェクトを矩形波、三角波、ノコギリ波が継承します。
・矩形波はサインの波がゼロより大きい状態だと1を出力し、それ以外は-1。
・のこぎり波は位相が1に達したら位相に-1を代入する
・三角波は位相が1に達したら位相に-1を代入する。それをを2倍して1や-1をこえたら増減を逆向きにします。
簡単な説明ですがコードを読めばわかります。
class SqrOsc extends Gen{ public SqrOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? 0 : phase; //ゼロより大きい状態だと1を出力し、それ以外は-1 return Math.sin(2*Math.PI*phase) > 0 ? (amp + addamp.updata()) : -1*(amp + addamp.updata()); } } class SawOsc extends Gen{ public SawOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; //位相が1に達したら位相に-1を代入する phase = (phase > 1) ? -1 : phase; return phase*(amp + addamp.updata()); } } class TriOsc extends Gen{ public TriOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? -1 : phase; //ここまでのこぎり波と同じ、2倍する double temp = phase * 2; //1や-1の値を越えたら増減が逆 temp = (temp > 1) ? 1-(temp-1) : temp; temp = (temp < -1) ? -2-temp : temp; return temp *(amp + addamp.updata()); } }
楽器のデザインは以下のようになります。
line = new Line(0, 0, 0.01); modfreq = new SinOsc(4, 40); osc = new SinOsc(400, 0, modfreq, line); osc2 = new SqrOsc(400, 0, modfreq, line); osc3 = new SawOsc(400, 0, modfreq, line); osc4 = new TriOsc(400, 0, modfreq, line); select = new Select(new Snd[]{osc, osc2, osc3, osc4}, 0);
ここでSelectオブジェクトを作ります。これは第2引数の番号で再生する楽器をスイッチします。Switchが良かったかな、まあいいや。そのSelectです。
class Select extends Snd{ public Snd[] input; public int number = 0; public Select(Snd n[], int m){ input = n; number = m; } public double updata(){ return input[number % input.length].updata(); } }
Selectのメンバ変数numberが1になれば矩形波にスイッチします。
これで以上です。
package miu.jun.TestSnd; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnTouchListener; import android.view.MotionEvent; import android.widget.Button; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; public class TestSnd extends Activity implements OnSeekBarChangeListener, OnTouchListener{ private Button play; private SeekBar sb; private SeekBar sb4; int ii; Line line; SinOsc modfreq; SinOsc osc; SqrOsc osc2; SawOsc osc3; TriOsc osc4; Select select; private AudioTrack track; short[] buf; double phase = 0.0; int bf; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); play = (Button)this.findViewById(R.id.button1); play.setOnTouchListener(this); sb = (SeekBar)this.findViewById(R.id.SeekBar01); sb.setMax(1000); sb.setProgress(50); sb.setOnSeekBarChangeListener(this); sb4 = (SeekBar)this.findViewById(R.id.SeekBar04); sb4.setMax(4); sb4.setProgress(0); sb4.setOnSeekBarChangeListener(this); bf = 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, bf, AudioTrack.MODE_STREAM); buf = new short[1024]; track.setPositionNotificationPeriod(1024); track.setPlaybackPositionUpdateListener( new AudioTrack.OnPlaybackPositionUpdateListener() { public void onMarkerReached(AudioTrack track) {} public void onPeriodicNotification(AudioTrack track) { sndOut(buf,select); track.write(buf,0,buf.length); } } ); line = new Line(0, 0, 0.01); modfreq = new SinOsc(4, 40); osc = new SinOsc(400, 0, modfreq, line); osc2 = new SqrOsc(400, 0, modfreq, line); osc3 = new SawOsc(400, 0, modfreq, line); osc4 = new TriOsc(400, 0, modfreq, line); select = new Select(new Snd[]{osc, osc2, osc3, osc4}, 0); short[] buf2 = new short[bf]; track.play(); for (int i = 0; i < buf2.length; i++) { buf2[i] = 0; } track.write(buf2,0,buf2.length); } //自動的にAudioTrack.release() @Override public void onDestroy() { if( track != null ){ track.setStereoVolume(0, 0); if(track.getPlayState()==AudioTrack.PLAYSTATE_PLAYING){ track.stop(); } track.flush(); track.release(); } super.onDestroy(); } public boolean onTouch(View view, MotionEvent event){ if(view == play && event.getAction() == MotionEvent.ACTION_DOWN) { line.reset(0, 1); }else if(view == play && event.getAction() == MotionEvent.ACTION_UP){ line.reset(1, 0); } return true; } class Snd{ public double sampleRate = 44100; public Snd(){} public double updata(){ return 0; } } class Gen extends Snd{ public double freq = 0; public double amp = 0; public double phase = 0; public Snd addfreq = new Snd(); public Snd addamp = new Snd();; public Gen(){ freq = 0; amp = 0; } public Gen(double f, double a){ freq = f; amp = a; } public Gen(double f, double a, Snd af){ freq = f; amp = a; addfreq = af; } public Gen(double f, double a, double af, Snd aa){ freq = f; amp = a; addamp = aa; } public Gen(double f, double a, Snd af, Snd aa){ freq = f; amp = a; addfreq = af; addamp = aa; } } class SinOsc extends Gen{ public SinOsc(double f, double a){ super(f,a); } public SinOsc(double f, double a, Snd af){ super(f,a,af); } public SinOsc(double f, double a, double af, Snd aa){ super(f,a,0,aa); } public SinOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? 0 : phase; return Math.sin(2*Math.PI*phase)*(amp + addamp.updata()); } } class SqrOsc extends Gen{ public SqrOsc(double f, double a){ super(f,a); } public SqrOsc(double f, double a, Snd af){ super(f,a,af); } public SqrOsc(double f, double a, double af, Snd aa){ super(f,a,0,aa); } public SqrOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? 0 : phase; return Math.sin(2*Math.PI*phase) > 0 ? (amp + addamp.updata()) : -1*(amp + addamp.updata()); } } class SawOsc extends Gen{ public SawOsc(double f, double a){ super(f,a); } public SawOsc(double f, double a, Snd af){ super(f,a,af); } public SawOsc(double f, double a, double af, Snd aa){ super(f,a,0,aa); } public SawOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? -1 : phase; return phase*(amp + addamp.updata()); } } class TriOsc extends Gen{ public TriOsc(double f, double a){ super(f,a); } public TriOsc(double f, double a, Snd af){ super(f,a,af); } public TriOsc(double f, double a, double af, Snd aa){ super(f,a,0,aa); } public TriOsc(double f, double a, Snd af, Snd aa){ super(f,a,af,aa); } public double updata(){ phase += (freq + addfreq.updata())/sampleRate; phase = (phase > 1) ? -1 : phase; double temp = phase * 2; temp = (temp > 1) ? 1-(temp-1) : temp; temp = (temp < -1) ? -2-temp : temp; return temp *(amp + addamp.updata()); } } class Line extends Snd{ double start_value; double end_value; double current_value; double dur_time; double sample_length; double count; public Line(double s, double e, double t){ super(); start_value = s; end_value = e; dur_time = t; count = 0; sample_length = dur_time*sampleRate; } public void reset(double s, double e){ start_value = s; end_value = e; count = 0; //} } public double updata(){ if(sample_length > count){ count = count + 1; }else{ count = sample_length; } current_value = start_value + (end_value - start_value)*(count / sample_length); return current_value; } } class Select extends Snd{ public Snd[] input; public int number = 0; public Select(Snd n[], int m){ input = n; number = m; } public void setNumber(int n){ number = n; } public double updata(){ return input[number % input.length].updata(); } } void sndOut(short data[],Snd input) { for (int i = 0; i < data.length; i++) { data[i] = (short)(Short.MAX_VALUE * input.updata()); } } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { if(seekBar == sb){ osc.freq = progress; osc2.freq = progress; osc3.freq = progress; osc4.freq = progress; }else if(seekBar == sb4){ select.number = progress; } } @Override public void onStopTrackingTouch(SeekBar seekBar) {} }