Perl Hackers Hub

第15回 Perl meets beats―鳴らして学ぶシンセサイザー入門(3)

この記事を読むのに必要な時間:およそ 3.5 分

ビートの出力

(1)(2)までで単音の波形データが作れるようになりました。次は生成した波形データを任意のテンポとパターンで鳴らしてビートを刻みます。

発音タイミングの計算

ビートを刻むには,単音の波形データを間隔を置いて配置したチャンネルデータを作ります。間隔の計算には,BPMBeats Per Minute1分間に鳴る4分音符の回数)を決める必要があり,BPMが決まると配置先が決まります。この配置先のことを「発音タイミング」と呼び,図7の横軸の目盛りのことを指します。図7①は4分音符を配置するための間隔です。

図7 発音タイミング

図7 発音タイミング

Perlによる実装

リスト10は,ビートを刻むのに必要なパラメータの定義です。(1)はビートを刻むテンポで,BPMで定義します。(2)はビートをどうやって刻むかを定義した発音パターンで,1つの音色につき1つ定義します。定義した配列の中で「1」のときだけ鳴るように実装することで,任意のビートを刻むことができます。(3)は,発音パターンと音色と音量からなるチャンネルを定義しています。今回は,リスト6~8で4つの音色を定義したので,チャンネルを4つ用意しました。

リスト10 ビートのパラメータ

# (1)テンポ

my $bpm = 138; # beats per minute
# (2)発音パターンの定義
my $seq_kick = [ 1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0 ];
my $seq_snare = [ 0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0 ];
my $seq_o_hat = [ 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0 ];
my $seq_c_hat = [ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ];

# 発音パターン, 音色, 音量の定義
my @beats = (
    { seq => $seq_kick, tone => $kick, vol => 1.00 },   
{ seq => $seq_snare, tone => $snare, vol => 0.12 },     
    { seq => $seq_o_hat, tone => $o_hat, vol => 0.06 }, ┃(3)
    { seq => $seq_c_hat, tone => $c_hat, vol => 0.02 }  
);

次に,これらのパラメータを使ってチャンネルデータを作りますリスト11)⁠(1)のcreate_channel()には,リスト10で定義したテンポとチャンネルを引数に与えます。今回は,発音パターンを16分音符として解釈したいので,(2)のようにBPMから1秒間に16分音符が鳴る回数を算出します。次にサンプリング周波数をこの回数で割って,16分音符の配置間隔である(3)を算出します。(4)はチャンネルデータである配列へ波形データを配置する際の格納開始添え字を算出しています。実際は,16分音符が鳴る間隔よりも単音の波形データのほうが長い場合も考慮して,単純にコピーして上書きしてしまうのではなく(5)のように足し込むことで,既存の波形データと同時に鳴らすことができます。

リスト11 チャンネルデータの生成

sub create_channel { # (1)
    my $samples_per_sec = 44100;
    my $bpm = shift; # beats per minute
    my $arg_ref = shift;

    my $seq = $arg_ref->{seq};
    my $tone = $arg_ref->{tone};

    # 単音の波形データの生成
    my $oneshot_ref = create_oneshot( $tone );

    # 発音タイミングの計算
    my $bps = ( $bpm / 60.0 ) * 4; # (2)
    my $seq_cnt = scalar( @{$seq} );
    my $interval = $samples_per_sec / $bps; # (3)
    my @plot_tmpl = ();
    for (my $i=0; $i<$seq_cnt; $i++) {
        push @plot_tmpl, int($i * $interval); # (4)
    }

    # 必要な配列サイズを計算して初期化
    my $wav_size =
        $plot_tmpl[$seq_cnt - 1] + scalar(@{$oneshot_ref});
    my @channel = map { 0.0; } 1..$wav_size;

    # チャンネルデータの生成
    for (my $i=0; $i<$seq_cnt; $i++) {
        if ( not $seq->[$i] ) { next; }
        my $j = $plot_tmpl[$i];
        map { $channel[$j++] += $_; } @{$oneshot_ref}; # (5)
    }

    return \@channel;
}

# チャンネルデータの生成
my @channels = map {
    create_channel( $bpm, $_ );
} @beats;

ミキシング

リスト11で生成したチャンネルデータを,それぞれ異なる音量でミックスする場合を考えます。

Perlによる実装

リスト12は,生成したチャンネルデータをミックスしてクリップするコードです。クリップとは,任意の範囲を超える値を上限値または下限値で書き換える処理のことを指します。(1)はリスト11で生成したチャンネルデータです。チャンネルデータの音量を変えるには,(2)のようにすべてのデータに同じ係数を掛けることで実現できます。同時に音を鳴らすには,(2)のようにデータを足し込むことで実現できます。ただし,波形データをWAVEファイルに書き出す場合は,(2)の処理を行ったことで書き出し可能な範囲(-1.0~+1.0)を超えている場合が考えられるので,(3)のようにクリップが必要になります。

リスト12 ミキシングとクリップ処理

# ミックス
my @samples_beats = ();
foreach my $ch_info ( @beats ) {
    my $ch = create_channel( $bpm, $ch_info ); # (1)
    my $vol = $ch_info->{vol};
    for (my $i=0; $i<scalar(@{$ch}); $i++) {
        $samples_beats[$i] += ( $ch->[$i] * $vol ); # (2)
    }
}

# クリップ
@samples_beats = map {
    ( 1.0 < $_ ) ? 1.0 : ( ($_ < -1.0) ? -1.0 : $_ ); # (3)
} @samples_beats;

著者プロフィール

伊藤智章(いとうともあき)

組み込み系の会社に所属し,ファームウェアとGUIアプリケーションの開発を担当。その開発作業の効率化のためにPerlを使用している。

主に「Hokkaido.pm」に出没して,あご髭が長いというだけで北海道のPerl(?)仙人と呼ばれている。

Twitter:techno_neko

写真:Japan Perl Association

コメント

コメントの記入