本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回は@techno_nekoこと伊藤智章さんが、Perlで音を扱う方法について紹介します。
Perlで「音」は扱える?
Perlと言えば文字列操作やWebアプリケーションのイメージが強いと思いますが、今回は「音」を扱って信号処理を行うという、ちょっと特殊なPerlの使い方を紹介します。まずPerlで音を作って、次にビートを刻み、最後にWAVEファイルに出力する方法を説明します。
なお、本稿のコードはWEB+DB PRESS Vol.69のサポートサイトからダウンロードできますので、ぜひみなさんも実際に動かして音を聴いてみてください。
波形のお話
普段みなさんが耳にしている音は、とても複雑な波形(空気の振動)です。今回はその複雑な波形を作るために、いくつかの基本波形を合成して音を作ります(図1)。これらの波形は、三角関数のように同じデータを繰り返すものと、乱数を用いて生成したノイズで表現されます。
図1 波形の種類
リスト1は波形を生成する関数を返すコードです。最初に、ノイズ波形のもととなるノイズデータの初期化を行います。ノイズ波形とは、rand()の戻り値のように周期を持たない波形を指します。ノイズデータは、好みの音が出る状態を保持するために、(1)のようにsrand()による初期化を行ってあらかじめ配列に格納しておきます。これにより、今回の音作りに適したノイズ波形を安定的に生成できます。実際に波形データを生成する関数は(2)のように定義して、あとのコードでこれらを簡単に呼び出せるように、波形の名前を指定して関数を取得できる(3)のcreate_mod_func()を定義しています。
リスト1 波形を生成する関数
use strict;
use warnings;
use Math::Trig qw( pi );
srand( 2 ); #
my @noise = map { rand( 2.0 ) - 1.0; } 1..1024;
my %func_table = ( #
'pulse' => sub { # 矩形波
return ( $_[0] < 0.5 ) ? -1.0 : 1.0;
},
'sin' => sub { # サイン波(正弦波)
return sin( 2.0 * pi() * $_[0] );
},
'saw' => sub { # のこぎり波
return ( 2.0 * $_[0] ) - 1.0;
},
'tri' => sub { # 三角波
if ( $_[0] < 0.5 ) {
# -1.0 -> +1.0
return -1.0 + ( 4.0 * $_[0] );
}
else {
# +1.0 -> -1.0
return 1.0 - ( 4.0 * ($_[0] - 0.5) );
}
},
'noise' => sub { # ノイズ
if ( $_[0] < 1.0 ) {
my $idx = int( $_[0] * scalar(@noise) );
return $noise[$idx];
}
else {
return 0.0;
}
}
);
sub create_mod_func { #
my $func = $func_table{$_[0]} or die;
return $func;
}
音程のお話
図2のように、1オクターブを12等分して算出した音律(周波数と音程の関係)を十二平均律と言います。今回は、一般的に多く採用されている基準となるラの音を440Hzとした場合の、残りの音程の周波数を算出する方法を紹介します。
図2 音程と周波数
Perlによる実装
リスト2は、図2のindexと周波数の関係をコードに落とし込んだものです。たとえばドの音の周波数を計算したい場合は、図2の横軸からドに対応するindexを選択して、(1)のように関数の引数として与えて算出します。さらに1オクターブ高い音の周波数を計算する場合は(2)のように12足したものを与え、逆に1オクターブ低い音の周波数を計算する場合は(3)のように12引いたものを引数に与えます。
リスト2 indexから周波数を求める
sub index_to_freq {
my $index = shift;
return 440.0 * ( 2.0 ** ($index / 12.0) );
}
# ドの音の周波数を計算する
my $freq_C2 = index_to_freq( 3 ); #
my $freq_C3 = index_to_freq( 3 + 12 ); #
my $freq_C4 = index_to_freq( 3 - 12 ); #