ビートの出力
発音タイミングの計算
ビートを刻むには、
![図7 発音タイミング 図7 発音タイミング](/assets/images/dev/serial/01/perl-hackers-hub/0015/thumb/TH800_007.jpg)
Perlによる実装
リスト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 } ┛
);
次に、
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は、
# ミックス
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;
WAVEファイルに出力
次に、
WAVEファイルフォーマット
WAVEファイルは、
- 識別情報
(ChunkID) - データ部のバイト数
(ChunkSize) - データ部
(ChunkID、 ChunkSize以外)
![図8 WAVEファイルフォーマット 図8 WAVEファイルフォーマット](/assets/images/dev/serial/01/perl-hackers-hub/0015/thumb/TH800_008.jpg)
図8のように、-1.
で扱ってきたデータを16ビットに収まる範囲、-32768~+32767
に変換して出力します。
Perlによる実装
リスト13は、-1.
に正規化された波形データのファイル出力処理です。量子化ビット数が8ビットの場合は、
sub save_as_wav {
my $samples_per_sec = 44100; # (1)
my $bits_per_sample = 16; # (2)
my $file_name = shift;
my $samples_ref = shift;
my $block_size = $bits_per_sample / 8; # (3)
my $size = scalar(@{$samples_ref}) * $block_size; # (4)
my $bytes_per_sec = $block_size * $samples_per_sec; # (5)
my $header =
'RIFF' # ChunkID
. pack('L', ($size + 36)) # ChunkSize
. 'WAVE'; # FormType
my $fmt_chunk =
'fmt ' # ChunkID
. pack('L', 16) # ChunkSize
. pack('S', 1) # WaveFormatType
. pack('S', 1) # Channel
. pack('L', $samples_per_sec) # SamplesPerSec
. pack('L', $bytes_per_sec) # BytesPerSec
. pack('S', $block_size) # BlockSize
. pack('S', $bits_per_sample); # BitsPerSample
my $data_chunk =
'data' # ChunkID
. pack('L', $size); # ChunkSize
open( my $fh, '>', $file_name ) or die;
binmode $fh;
print $fh ($header . $fmt_chunk . $data_chunk);
foreach my $sample (@{$samples_ref}) {
print $fh pack( 's', int($sample * 32767.0) ); # (6)
}
close $fh;
}
# これまでに生成した音データをファイルに書き出す
save_as_wav( 'sin.wav', \@sin_samples );
save_as_wav( 'sin_with_mod.wav', \@sin_with_mod_samples );
save_as_wav( 'kick.wav', $samples_kick_ref );
save_as_wav( 'snare.wav', $samples_snare_ref );
save_as_wav( 'o_hat.wav', $samples_o_hat_ref );
save_as_wav( 'c_hat.wav', $samples_h_hat_ref );
save_as_wav( 'samples_beats.wav', \@samples_beats );
まとめ
Perlでも音楽ができるということ、
さて、