Part 3: プロッティング
原文: Onset Detection Part 3: Plotting
音声を処理したり分析したりするときには、何が起きているかを可視化しないと非常に退屈だ。たとえばサンプルの振幅を見てみたいことがあるだろう。そのような場合に助けるになるよう、非常に簡単な Swing ベースの Plot というクラスを書いた。これを使えば、1 つまたは複数の float 配列を簡単にウィンドウへプロットすることができる。値は適切な縮尺にしてくれるので必ずなんらかの結果を確認することができる。 シグネチャは下記のような感じ:
public class Plot
{
public Plot( String title, int width, int height );
public void clear( );
public void plot( float[] samples, float samplesPerPixel, Color color );
}
いつも通りとても小さなクラスであり、やりたいことをやるのには十分なメソッドが備わっている。 Plot クラスをインスタンス化するときには、タイトルと幅・高さ (ピクセル) を渡す。 値の配列をプロットするには Plot.plot() メソッドを使う。 最初の引数はプロットしたいサンプルの配列だ。 注意してほしいのはここで言うサンプルは必ずしも PCM サンプルではないということ。 実際にあなたが渡したいものならなんでも渡すことができる。
次のパラメーターではプロットの中で 1 ピクセルごとに配列の中の何個のサンプルが使われるべきかを定義する。 これによって X 軸のプロットの縮尺を適切に設定することができる。 最後のパラメーターはプロットされるサンプルの色だ。 これまでにプロットしたものを消去したい場合には、Plot.clear() を呼べばよい。 注意してほしいのは、複数の配列をプロットしたいときにはすべてに同じ縮尺の値を指定すべきだということだ。 たとえば [-1, 1] の範囲の値を要素として持つ 2 つの配列をプロットするのは OK。 [-1, 1] の範囲の値を持つ配列と、[0, 200] の範囲の値を持つ配列をプロットするのはあまりよい考えとは言えない。 このシリーズでは同じ縮尺の値を持つ複数の配列だけをプロットするだろう。 もし違う縮尺の複数の配列をプロットしたい場合には、それぞれのプロットで別々の Plot インスタンスを利用すべきだ。 また、Plot クラスは Swing しか利用していないので全体的に遅い。 同期がうまく行えないので、リアルタイムデータの可視化には使わないほうがよいだろう。
興味深いことをやってみよう。”samples/” フォルダに保存した曲のすべてのサンプルをプロットしてみる (PlotExample):
public class PlotExample
{
public static void main( String[] argv ) throws FileNotFoundException, Exception
{
WaveDecoder decoder = new WaveDecoder( new FileInputStream( "samples/sample.wav" ) );
ArrayList allSamples = new ArrayList( );
float[] samples = new float[1024];
while( decoder.readSamples( samples ) > 0 )
{
for( int i = 0; i < samples.length; i++ )
allSamples.add( samples[i] );
}
samples = new float[allSamples.size()];
for( int i = 0; i < samples.length; i++ )
samples[i] = allSamples.get(i);
Plot plot = new Plot( "Wave Plot", 512, 512 );
plot.plot( samples, 44100 / 100, Color.red );
}
}
最初に Wave ファイルの PCM データを得るために WaveDecoder を開いている。 allSamples という名前の ArraList は (低速に) デコーダーからすべてのサンプルを読み込むために利用される。 allSamples はサンプル保存用の内部配列をリサイズしてくれるので、私たちはここでもなまけていられる。 注意すべきなのは、float をそのいとこ的な存在のオブジェクト・インスタンスである Float へ変換したいときにこのような処理を行うのはよい考えとは言えない。 それはオートボクシングと呼ばれており、扱いづらい。 すべてのサンプルを読み込んだら、プロットできるように float[] へと移動させる。
最後の 2 行で Plot インスタンスを作り、読み込んだばかりのサンプルを描画する。 Plot.plot() 呼び出しの 2 番目のパラメーターを見てほしい。 最終的な出力で 44100 / 100 = 441 サンプルごとに 1 ピクセルを使うように Plot に指示している。 よって各ピクセルは 0.0001 秒の音の連続についての振幅を表す。 このパラメーターをいじってより精細に wave ファイルの形式を洞察してみるとよい (注意: samplePerPixel パラメーターの値をとても低く設定する際には、JVM により大きなヒープメモリを割り当てる必要があるだろう。生成される画像が極端に大きくなるので!) 下記がこのプログラムの出力だ。
これがオーディオファイルのすべてのサンプルの振幅のプロットである。このプロットでは音の立ち上がりに関する情報はそんなに多くは得られない。ひどい混乱状態のようだ。samplesPerPixcel をより低い値に設定したらどうなるか見てみよう (そうするとプロットの解像度がよくなる) (samplesPerPixel = 44100/1000)
これを見ると音の中に構造があるようだ。ぱっと推測するなら、ピークとなっている部分はおそらくはドラムから、急に音が出たことを表していそうだ。しかしながら、この形式ではピークを検出するのはかなり難しいだろう。次の記事では、やりたいことを少しだけラクにしてくれるサンプルの形式へたどり着く方法を見ることになるだろう。