PerlでReverbを実装する(続き)
ちゃんとやるには、コムフィルタのフィードバックゲインを調整して、
例えば0.001になるタイミングを揃える必要があるらしい。
という訳で、リバーブタイムを決めて、
サンプリング周波数からサンプル数を決めて、
そこからフィードバックゲインを求めることにした。
use v5.14; use strict; use warnings; use constant X_MAX => 1024; use constant Y_MAX => 256; use constant Y_MIN => -256; use constant VALUE_MAX => 1.0; use constant VALUE_MIN => -1.0; use constant X_STEP => 8; use Imager; use List::Util qw/sum/; use Math::Trig qw/pi/; my $margin = 10; my $width = $margin + (X_MAX + 1) + $margin; my $height = $margin + (Y_MAX - Y_MIN + 1) + $margin; my ( $x0, $y0 ) = ( $margin, $margin + (Y_MAX + 1) ); my $sf = (X_MAX / X_STEP) / 2; my @m = ( 2, 8, 5 ); my $reverb_time = 2.0; # sec my @g = map { 10 ** ((-3.0 * $_) / ($reverb_time * $sf)); #0.5; } @m; my @f = map { comb_filter( $g[$_], $m[$_] ); } 0..2; say 'Sampling Freq: ', $sf; say 'Reverb Time: ', $reverb_time, '(sec)'; foreach my $i ( 0..2 ) { printf( "f[%d]: %.3f, %2d\n", $i, $g[$i], $m[$i] ); } my $log = { in => [], out1 => [], out2 => [], out3 => [] }; my $in = VALUE_MAX; foreach ( 0..(X_MAX / X_STEP) ) { push @{$log->{in}}, $in; push @{$log->{out1}}, $f[0]->( $in ); push @{$log->{out2}}, $f[1]->( $in ); push @{$log->{out3}}, $f[2]->( $in ); $in = 0; } write_graph( 'bbb.png', { red => $log->{in}, blue => $log->{out1}, green => $log->{out2}, purple => $log->{out3}, } ); foreach my $i ( 0..16 ) { my @tmp = ( $f[0]->( 0 ), $f[1]->( 0 ), $f[2]->( 0 ) ); say sprintf('[%2d] : ', $i), join( ',', map { sprintf('%6.4f', $_); } @tmp ); } sub write_graph { my ( $dst_file, $waves ) = @_; my $img = Imager->new( xsize => $width, ysize => $height, channels => 4 ); $img->box( filled => 1, color => 'white' ); draw_graduation( $img, Imager::Color->new(192, 192, 192) ); foreach my $color ( keys %{$waves} ) { my $x = 0; my @tmp = (); foreach ( @{$waves->{$color}} ) { push @tmp, [ $x, $_ ] if 0 != $_; # <<< 0は無視! $x += X_STEP; } draw_points_with_bar( $img, \@tmp, $color, 0.4 ); } $img->write( file => $dst_file ) or die $img->errstr; } sub calc_points { my ( $fa, $fb ) = @_; my @src = (); for (my $i=0; $i<=X_MAX; $i+=8) { my $t = $i / X_MAX; my $y = ($fa * $t) + ($fb * $t ** 5.0); push @src, [ $i, int( ($y * 128) ) ]; } return \@src; } sub draw_graduation { my ( $img, $color ) = @_; { my $gray = Imager::Color->new( 192, 192, 192 ); my $x = 128;#($w / 4); while ( $x <= X_MAX ) { $img->line( color => $gray, x1 => $x0 + $x, y1 => $y0 + Y_MIN, x2 => $x0 + $x, y2 => $y0 + Y_MAX ); $x += 128;#($w / 4); } my $y = 128;#($h / 4); while ( $y <= Y_MAX ) { $img->line( color => $gray, x1 => $x0 + 0, y1 => $y0 - $y, x2 => $x0 + X_MAX, y2 => $y0 - $y ); $img->line( color => $gray, x1 => $x0 + 0, y1 => $y0 + $y, x2 => $x0 + X_MAX, y2 => $y0 + $y ); $y += 128;#($h / 4); } } { $img->line( color => 'black', x1 => $x0, y1 => $y0 + Y_MIN, x2 => $x0, y2 => $y0 + Y_MAX ); $img->line( color => 'black', x1 => $x0 + 0, y1 => $y0, x2 => $x0 + X_MAX, y2 => $y0 ); } } sub plot_points { my ( $img, $data, $color ) = @_; my $n = 2; foreach my $pt ( @{$data} ) { my ( $x, $y ) = ( $pt->[0], $pt->[1] ); $y /= (VALUE_MAX / Y_MAX); $y = ( $y < .0 ) ? int($y - .5) : int($y + .5); #printf( "%6.3f, %6.3f\n", $x, $y ); $img->box( xmin => $x0 + $x - $n, ymin => $y0 - $y - $n, xmax => $x0 + $x + $n, ymax => $y0 - $y + $n, color => $color, filled => 0 ); } } sub draw_points { my ( $img, $data, $color ) = @_; foreach my $pt ( @{$data} ) { my ( $x, $y ) = ( $pt->[0], $pt->[1] ); $y /= (VALUE_MAX / Y_MAX); $y = ( $y < .0 ) ? int($y - .5) : int($y + .5); #printf( "%6.3f, %6.3f\n", $x, $y ); $img->setpixel( x => $x0 + $x, y => $y0 - $y, color => $color ); #$img->box( xmin => $x0 + $x - $n, ymin => $y0 - $y - $n, # xmax => $x0 + $x + $n, ymax => $y0 - $y + $n, # color => $color, filled => 1 ); } } sub draw_polyline { my ( $img, $data, $color ) = @_; my @points = map { my ( $x, $y ) = ( $_->[0], $_->[1] ); $y /= (VALUE_MAX / Y_MAX); $y = ( $y < .0 ) ? int($y - .5) : int($y + .5); [ $x0 + $x, $y0 - $y ]; } @{$data}; $img->polyline( points => \@points, color => $color ); } # 棒グラフっぽく sub draw_points_with_bar { my ( $img, $data, $color, $opacity ) = @_; my $img_dst = $img; if ( defined($opacity) and $opacity < 1.0 ) { $img_dst = Imager->new( xsize => $img->getwidth(), ysize => $img->getheight(), channels => 4 ); } foreach my $pt ( @{$data} ) { my ( $x, $y ) = ( $pt->[0], $pt->[1] ); $y /= (VALUE_MAX / Y_MAX); $y = ( $y < .0 ) ? int($y - .5) : int($y + .5); $img_dst->line( x1 => ($x0 + $x), y1 => $y0, x2 => ($x0 + $x), y2 => ($y0 - $y), color => $color ); } if ( $img != $img_dst ) { $img->compose( src => $img_dst, opacity => $opacity ); } plot_points( $img, $data, $color ); } sub all_pass_filter { my ( $aa, $n, $fill ) = @_; $n = 1 if not defined($n); $fill = 0 if not defined($fill); my @za = map { $fill; } 1..$n; my @zb = map { $fill; } 1..$n; return sub { my $in = shift; unshift @za, $in; my $out = (pop(@zb) * $aa) + pop(@za) - ($aa * $in); unshift @zb, $out; return $out; }; } sub comb_filter { my ( $aa, $n, $fill ) = @_; $n = 1 if not defined($n); $fill = 0 if not defined($fill); my @za = map { $fill; } 1..$n; return sub { my $in = shift; my $out = pop @za; unshift @za, ($in + ($aa * $out)); return $out; }; } sub comb_filters { my @args = @_; my @filters = (); foreach ( @args ) { push @filters, comb_filter( @{$_} ); } return sub { my $in = shift; return sum( map { $_->( $in ); } @filters ); }; }
$ perl bbb.pl
Sampling Freq: 64
Reverb Time: 2(sec)
f[0]: 0.898, 2
f[1]: 0.649, 8
f[2]: 0.764, 5
[ 0] : 0.0000,0.0000,0.0000
[ 1] : 0.0010,0.0000,0.0012
[ 2] : 0.0000,0.0000,0.0000
[ 3] : 0.0009,0.0000,0.0000
[ 4] : 0.0000,0.0000,0.0000
[ 5] : 0.0008,0.0000,0.0000
[ 6] : 0.0000,0.0000,0.0009
[ 7] : 0.0007,0.0010,0.0000
[ 8] : 0.0000,0.0000,0.0000
[ 9] : 0.0006,0.0000,0.0000
[10] : 0.0000,0.0000,0.0000
[11] : 0.0006,0.0000,0.0007
[12] : 0.0000,0.0000,0.0000
[13] : 0.0005,0.0000,0.0000
[14] : 0.0000,0.0000,0.0000
[15] : 0.0005,0.0006,0.0000
[16] : 0.0000,0.0000,0.0005
なんで、0.001(10の-3乗)辺りで揃えるのかは分からないけど、
ディレイタイム(サンプル数)を素数にするのは、
各コムフィルタの出力を被らないようにするため。
ところで、コムフィルタのディレイタム(サンプル数)が十分にある場合でも、
フィードバックゲインを調整した方が良い結果になるんですかね?
その辺りは、おいおいということで。
おしまい。
Leave a Comment