Part 4: MP3 デコードとより高機能なプロット
原文: Onset Detection Part 4: MP3 decoding and more Plotting
このシリーズの最初の記事で、皆さんに自分で MP3 ファイルをデコードする方法を見つけ出すように指示した。 しかし、私は気前がいいので、例のフレームワークを拡張して JLayer ベースのいい感じの MP3 デコーダーを追加してみた。 ここに WaveDecoder に似たクラスがある:
public class MP3Decoder
{
public MP3Decoder( InputStream in );
public void readFrames( float[] samples );
}
これは WaveDecoder と全く同じように機能する。ちゃんとオブジェクト指向プログラミングするなら、2 つのデコーダーは同じインタフェースを共有すべきなのだが、めんどくさかったのでやらなかった。もし適切なインタフェースを設けたほうが落ち着くというのなら、自由に追加してよい。
もう一つ今日このフレームワークに対して行ったのは、マーカーラインを設定してプレイバックが今プロットのどこにあるのかを確認できるようにしたこと。下記が Plot クラスの新しいメソッドだ:
public class Plot
{
...
public void setMarker( int position, Color color );
}
最初のパラメーターでマーカーが配置されるべきプロット上の x 座標を指定する。2 つ目のパラメーターにはマーカーの色を指定する。また、Plot クラスの内部の機能にも変更を加えた。プロットが定期的に再描画され、変化に対してほぼ瞬間的に反応するようになっている。Plot.setMaker() メソッドの使い方を説明するために RealTimePlot に置いてある簡単なサンプルコードを書いた。新しい部分だけを下記にコピペする。
public class RealTimePlot
{
private static final int SAMPLE_WINDOW_SIZE = 1024;
private static final String FILE = "samples/sample.mp3";
public static void main( String[] argv ) throws FileNotFoundException, Exception
{
float[] samples = readInAllSamples( FILE );
Plot plot = new Plot( "Wave Plot", 1024, 512 );
plot.plot( samples, SAMPLE_WINDOW_SIZE, Color.red );
MP3Decoder decoder = new MP3Decoder( new FileInputStream( FILE ) );
AudioDevice device = new AudioDevice( );
samples = new float[SAMPLE_WINDOW_SIZE];
long startTime = 0;
while( decoder.readSamples( samples ) > 0 )
{
device.writeSamples( samples );
if( startTime == 0 )
startTime = System.nanoTime();
float elapsedTime = (System.nanoTime()-startTime)/1000000000.0f;
int position = (int)(elapsedTime * (44100/SAMPLE_WINDOW_SIZE));
plot.setMarker( position, Color.white );
Thread.sleep(15); // this is needed or else swing has no chance repainting the plot!
}
}
...
}
最初に、前にやったのと同じように MP3 ファイルからすべてのサンプルを読み込む。サンプルは 1 ピクセルあたり 1024 個のサンプル数でプロットされる (これが SAMPLE_WINDOW_SIZE であり、1024 個のサンプルを持つ範囲が得られるというわけだ)。それではファイルを再生し、プロットの中で今どこが再生されているかを見てみたいと思う。動くマーカー、つまり時間が経過するにつれて更新されるプロットの上から下に引かれた線を観察すればよい。
MP3Decoder と AudioDevice をセットアップすることにはもうだいぶ慣れただろう。再生のために MP3Decoder から読み込むサンプル用の float 配列も必要だ。それからデコードと再生のループ処理に入る。現在のサンプルの幅を読み込み、以前やったようにオーディオデバイスへ書き出す。魔法の部分はここからだ。最初の範囲をデバイスへ書き出したあとで経過時間を計測する。このために再生の開始時刻を知る必要があり、 if( startTime == 0 ) という条件文の中で行っている。現在時刻から開始時刻を引いたものを 10 億で割ると再生開始時間からの経過時間を秒で取得できる。それを次の行で計算している。デコードと最初のサンプル範囲書き出しが終わったあとに開始時間を記録しているのは、オーディオの出力とプロットを同期させるためだ。AudioDevice.writeSamples() メソッドはすべてのサンプルがサウンドデバイスに書き出されるまでブロックする。そのためマーカーは最大 1 ピクセル分遅れてしまうが、今回の用途なら特に問題はない。
あとは経過時間に基づいたプロットの中でのマーカーの位置 (ピクセル) を計算し、その通りにマーカーを設定するだけだ。マーカーの位置 (ピクセル) を計算するための魔法の公式は、経過時間にサンプルの周波数をかけ、プロットの中でのピクセル毎のサンプル数で割るというものだ。それだけ。少し考えてみれば、どうやってこの公式にたどり着いたか分かるだろう。分からなければ、もっと考えろ! 残りはプロット上のマーカーの位置を設定することで、これは次の行で行っている。最後にデコード用のスレッドに 15 秒スリープさせる。これによって Swing のスレッドにすべての描画を行う時間を与えている。そうしなければマーカーはスムーズに動かないだろう。
これが出力結果の画像だ:
やった! リアルタイム・プロッティングだ! このサンプルが動いているのを確認するにはお好きな SVN クライアントで http://audio-analysis.googlecode.com/svn/trunk/ をダウンロードし、eclipse へプロジェクトをインポートして RealTimePlot.java というサンプルコードを実行すればよい。
次回はついに、音の立ち上がり検出を行うためのちょっとした数学に着手しようと思う。ここで利用したリアルタイム・プロッティングはオーディオ再生中のどこに音の立ち上がりが置かれているかを視覚的に確認することに役立つだろう。