Part 2: 簡単なフレームワーク
原文: Onset Detection Part 2: A simple framework
この音の立ち上がり検出チュートリアルで必要になる簡単なフレームワークを作ってみた。 http://code.google.com/p/audio-analysis/ に置いてある。 コードを取得するには SVN クライアントが必要だ。Windows なら Tortoise SVN がおすすめだ。Linux ユーザーならやり方はもう分かっているだろう。:) プロジェクトを svn URL http://audio-analysis.googlecode.com/svn/trunk/ からチェックアウトして Eclipse にインポートしてほしい。 準備ができたら、この「フレームワーク」がどんな機能を持っているか見てみよう。
最初のクラスは AudioDevice という名前だ。 下記のような構造になっている。
public class AudioDevice
{
public AudioDevice( );
public void writeSamples( float[] samples );
}
とてもシンプルではないか? このクラスはメインのオーディオカードに 44100Hz モノラルモードで接続する。 コンストラクタを呼び出すだけでよい。 ハードウェアをセットアップすることができなければ例外がスローされることになるが、「それは絶対に起こりえない(TM)」。 PCM データをオーディオデバイスへ出力するには、AudioDevice.writeSamples を呼び出し、float 配列で44100Hz モノラルの PCM サンプルを渡す。
440Hz のサイン波を生成し、オーディオデバイスへ出力する短いサンプルコードを書いてみた。 こんな感じ:
public class NoteGenerator
{
public static void main( String[] argv ) throws Exception
{
final float frequency = 880; // 440Hz for note A
float increment = (float)(2*Math.PI) * frequency / 44100; // angular increment for each sample
float angle = 0;
AudioDevice device = new AudioDevice( );
float samples[] = new float[1024];
while( true )
{
for( int i = 0; i < samples.length; i++ )
{
samples[i] = (float)Math.sin( angle );
angle += increment;
}
device.writeSamples( samples );
}
}
}
基本的には、このシリーズのパート 1 に記載したコードスニペットの修正版だ。 ループの中でサイン波の次の 1024 個分のサンプルを生成し、オーディオデバイスへ送るようにしている。 少しこのコードをいじくってみてほしい。たとえばサイン波の周波数を変えてみるとか。 たとえば、もし周波数を 2 倍にすると、同じ高さで 1 つ上のオクターブの音が出る。 逆もまた真なり。 プログラムを終了させるには、停止ボタンをクリックして Eclipse による実行を中止させればよい。 このような簡単な例にとっては適切な GUI が非常に重要だろう。 ソースコードについては com.badlogic.audio.samples パッケージ配下を参照してほしい。
提供されている第二のクラスは Wave ファイルデコーダーだ。 このクラスを使えば 16bit モノラル/ステレオの Wave ファイルをサンプリングレート 44100 で読み込むことができる。 Wave は他にもいくつかのフォーマットやサンプリングレートをサポートしているが、簡潔さのために上記の設定のみサポートするようにした。 このような魔法の処理を行うのは WaveDecorder という名前のクラスだ。 このクラスはその場で PCM データを Wave ファイルから 32bit float のサンプルに変換してくれる。 クラスのシグネチャは下記の通り:
public class WaveDecoder
{
public WaveDecoder( InputStream stream ) throws Exception;
public int readSamples( float[] samples );
}
今度もスッキリしている。 コンストラクタには Wave ファイルを読み出すための InputStream を渡す。 エラーが起きたら例外がスローされる。 適切にクラスをインスタンス化することができたら、WaveDecoder.readSamples() を呼び出し、Wave ファイルからサンプルを読み込むことができる。 このメソッドはあなたが渡したサンプルの配列の要素数の限界まで Wave ファイルからサンプルを読み出そうと試みる。 戻り値には実際に読み込んだサンプルの数を返却する。 パート 1 で説明した通り、Wave ファイルがステレオならステレオの場合にはステレオのサンプルはモノラルに変換される。 メソッドが 0 を返した場合、ストリームの終端に到達してしまっているかエラーが発生したということだ。 下記の簡単な例では、WaveDecoder を AudioDevice と組み合わせ、Wave ファイルから読み込んだ音を出力するやり方を説明している:
public class WaveOutput
{
public static void main( String[] argv ) throws Exception
{
AudioDevice device = new AudioDevice( );
WaveDecoder decoder = new WaveDecoder( new FileInputStream( "samples/sample.wav" ) );
float[] samples = new float[1024];
while( decoder.readSamples( samples ) > 0 )
device.writeSamples( samples );
}
}
最初に AudioDevice, WaveDecoder および Wave ファイルから読みこんだサンプルを保存するための float 配列をインスタンス化する。 ループの中では、1024 個 (サンプルの配列の長さ) のサンプルを読みこみ、AudioDevice へ書き出そうとする。 それだけだ。 サウンドプログラミングは本当にこんなに簡単なのだ。この例のコードは com.badlogic.audio.samples パッケージにある。いじってみてほしい。たとえば、AudioDevice へ書き出す前にサンプルの振幅を変えてみるとか。各サンプルに 0.5 をかければ音の大きさが半分になったりする。
このシリーズの講義を通して、このプロジェクトへいくつかヘルパークラスを追加して、処理・生成するデータを可視化できるようにしようと思う。 また、適切なビート検出 (高速フーリエ変換とかいうやつ) を行うためにいくつかかっこいい分析用のクラスを作ってプロジェクトを拡張するつもりだ。 簡単なビート検出を行うのはあまりに簡単なので驚くことになるだろう。
以上!