FIRフィルタのゲイン特性を描画する
思い出すのに苦労したけど、FIRの係数を算出できたので、
せっかくだからゲイン特性の描画もやってみた。
use v5.14; use strict; use warnings; use Imager; use Math::Trig qw/pi/; use List::Util qw/sum/; use constant STEP_X => 4; use constant SCALE_Y => 200.0; use constant FIR_TAP => 19; use constant SPECTRUM_SIZE => 201; # グラフの描画に必要な最低限の幅(先端のマークを含まず) my $graph_width = 1 + (STEP_X * (SPECTRUM_SIZE - 1)); my $graph_height = SCALE_Y + 1; my ( $width, $height ) = ( $graph_width + 40, $graph_height + 100 ); my ( $x0, $y0 ) = ( int(($width - $graph_width) / 2), ($height - 20) ); say '=-' x 10, ' ', FIR_TAP, ' ', '=-' x 10; if (1) { my $fir_tap = FIR_TAP; my $img = Imager->new( xsize => $width, ysize => $height, channels => 4 ); draw_graduation( $img, Imager::Color->new(64,64,64) ); my $fc = 0.4; my $fir = fir_lpf( $fir_tap, $fc ); my $window = hamming_window( $fir_tap ); my $fir2 = apply_window( $fir, $window ); $fir = normalize( $fir ); $fir2 = normalize( $fir2 ); my $n = SPECTRUM_SIZE - 1; my @data1 = map { calc_gain( $fir, $_ / ($n * 2) ); } 0..$n; draw_spectrum( $img, \@data1, 'green' ); my @data2 = map { calc_gain( $fir2, $_ / ($n * 2) ); } 0..$n; draw_spectrum( $img, \@data2, 'orange' ); my $dst_file = sprintf( 'fir_gain_%02d.png', $fir_tap ); $img->write( file => $dst_file ) or die $img->errstr; } sub normalize { my $fir = shift; my $gain = 1.0 / sum( @{$fir} ); my @ret = map { $_ * $gain; } @{$fir}; return \@ret; } sub apply_window { my ( $fir, $window ) = @_; my $n = @{$fir}; my @ret = map { $fir->[$_] * $window->[$_]; } 0..($n - 1); return \@ret; } sub fir_lpf { my ( $n, $fc ) = @_; my @fir = map { my $i = $_ - int($n / 2); if ( $i ) { sin(2.0 * $i * pi() * $fc) / ($i * pi()); } else { 2.0 * $fc; } } 0..($n - 1); return \@fir; } sub kaiser_window { my ( $n, $alpha ) = @_; my @ret = map { bessel( $alpha * sqrt(1.0 - ($_ ** 2.0)) ) / bessel( $alpha ); } map { ($_ - int($n / 2)) / int($n / 2); } 0..($n - 1); #printf( "%2d: %.3f\n", $_, $ret[$_] ) for 0..($n - 1); return \@ret; } sub hann_window { my $n = shift; my @ret = map { my $x = 2.0 * pi() * $_; 0.5 + (0.5 * cos($x)); } map { ($_ - int($n / 2)) / ($n - 1); } 0..($n - 1); return \@ret; } sub hamming_window { my $n = shift; my @ret = map { my $x = 2.0 * pi() * $_; 0.54 + (0.46 * cos($x)); } map { ($_ - int($n / 2)) / ($n - 1); } 0..($n - 1); return \@ret; } sub bessel { my $x = shift; my $total = 1; my $factorial = 1; foreach my $n ( 1..20 ) { $factorial *= $n; my $tmp = (($x / 2) ** $n) / $factorial; $total += ($tmp * $tmp); } #say $total; return $total; } sub calc_gain { my ( $params, $w ) = @_; # H(z) = h0 + h1 * z^-1 + h2 * z^-2 + ... # z^1 = e^(jw) = cos(w) + j*sin(w) # z^-1 = e^(-jw) = cos(w) - j*sin(w) # H(jw) = A - jB # |H(jw)| = sqrt( A^2 + B^2 ) my @h = @{$params}; my $n = scalar( @{$params} ); my $re = sum( map { $h[$_] * cos( 2.0 * pi * $_ * $w ); } 0..($n - 1) ); my $im = sum( map { -1.0 * $h[$_] * sin( 2.0 * pi * $_ * $w ); } 0..($n - 1) ); return sqrt( ($re * $re) + ($im * $im) ); } sub draw_spectrum { my ( $img, $data, $color ) = @_; my $img_tmp = Imager->new( xsize => $img->getwidth(), ysize => $img->getheight(), channels => 4 ); my $n = scalar( @{$data} ); for (my $i=0; $i<$n; $i++) { my $dx = $i * STEP_X; my $dy = int($data->[$i] * SCALE_Y); $img_tmp->line( x1 => ($x0 + $dx), y1 => $y0, x2 => ($x0 + $dx), y2 => ($y0 - $dy), color => $color ); } $img->compose( src => $img_tmp, opacity => 0.25, combine => 'add' ); for (my $i=0; $i<$n; $i++) { my $dx = $i * STEP_X; my $dy = int($data->[$i] * SCALE_Y); my ( $x, $y ) = ( ($x0 + $dx), ($y0 - $dy) ); $img->box( xmin => $x - 1, ymin => $y - 1, xmax => $x + 1, ymax => $y + 1, color => $color, filled => 0 ); } } sub draw_graduation { my ( $img, $color ) = @_; $img->box( filled => 1, color => 'black' ); for ( 0..6 ) { my $x = $x0 + ((($graph_width - 1) / 5) * $_); $img->line( x1 => $x, y1 => $y0, x2 => $x, y2 => $y0 - (($graph_height - 1) * 1.2), color => $color ); } for ( 0..12 ) { my $y = $y0 - ((SCALE_Y / 10) * $_); $img->line( x1 => $x0, y1 => $y, x2 => $x0 + ($graph_width - 1), y2 => $y, color => $color ); } }
ちょいちょい余計なコードが残ってるけどご愛嬌ということで。
IIRのゲイン特性の計算(*1)を見ながらやったので、
思ったより簡単にできた。
おしまい。
(*1) https://github.com/techno-cat/p5-Cassis/blob/master/Samples/amplitude_spectrum.pl
Leave a Comment