サイン波の基本的な理解ができたところでJavaで使い勝手の良いオシレーターを作ります。サイン波は音を扱うときに多様な場所で使われています。シンプルな実装にして後でいろいろなことに使えるようにします。
インタフェースと実装内容を決める
まずインターフェースを決めます。デジタルでの音声処理で必要な入力情報は以下の三つです。
- サンプリングレート
- 周波数
- ボリューム
このうちサンプリングレートはシステム全体で変わることがありません。現在、音声ファイルで途中でサンプリングレートの変更されるようなものは存在しません。複数の音声を合成する時にはこれらは同じサンプリングレートに合わせられてからミックスされます。
周波数とボリュームは時間の経過とともに変化することがあるでしょう。むしろ変化しないものの方が自然界では珍しいでしょう。人の声も風の音も音量や高さは短い時間の間に多彩に変化するものです。
これらのことから以下のようなインタフェースは次のように決めます。
- サンプリングレートはコンストラクタで指定され、インスタンスが続くかぎりこの値を使い続ける。
- 周波数とボリュームはサンプルを取得するreadメソッドで指定され一度きりの値として計算される。
- readメソッドの戻り値は、サイン波の計算結果はsin関数の仕様から-1〜+1の範囲になるが、引数で与えられたボリュームを掛け合わせられて返される。
- 内部では現在のx座標がラジアン単位の角度Δtとして保持されている。
- 引数の周波数は次の経過角度を計算するために使われる。
- Δtの初期値は0とする。初回のreadメソッドの戻り値は必ず0となる。
実装する
ここまで決まると実装はとても楽です。コードを作る前になるべく日本語(あるいはその他人間が日常的に使う言葉)で書いておくと実装内容に揺らぎができにくいです。
フィールド値とコンストラクタ
double omega_t; double delta_t;
omega_tは1サンプルで1Hzのサイン波が移動する角度です。この値は一度決めたら変わることはありません。
この角度を元にして1サンプルを流ごとに次の位置を角度として計算します。100Hzの周波数の場合次の位置はomega_tの100倍の角度分進んだ場所になります。
delta_tは上記の Δtを表す変数で現在の位置を保持する変数です。初期値は0にします。
public SineOscillator(double sample_rate) { omega_t = 2.0 * Math.PI / sample_rate; delta_t = 0; }
readメソッド
public double read(double freq, double volume) { double point = Math.sin(delta_t); delta_t += freq * omega_t; return point * volume; }
引数から周波数freqとボリュームvolumeが指定されています。まず現在のy座標をsin関数を使って変数pointとして計算します。その後delta_tを指定された周波数の分だけ角度を進めます。
まとめとコード
ここまででサイン波を作るオシレーターができました。次回はこれを利用して音声ファイルを作成する側を作ります。今まで作ったものを組み合わせれば簡単にできます。
以下今回作成したコードです。パッケージはこのサイトの名前からmocha.soundとしています。適宜変更して再利用してください。
package mocha.sound; public class SineOscillator { double omega_t; double delta_t; public SineOscillator(double sample_rate) { omega_t = 2.0 * Math.PI / sample_rate; delta_t = 0; } public double read(double freq, double volume) { double point = Math.sin(delta_t); delta_t += freq * omega_t; return point * volume; } }