Perlで学ぶディジタルフィルタ入門

Created by @techno_neko

## 自己紹介 - 仕事: .NETでGUIアプリ - Twitter: @techno_neko - 所属: Hokkaido.pm
## ディジタルフィルタ使ってますか?
## 例えば・・・? - 車のハンドルと実際の動き - 電子楽器 - 画像の輪郭抽出
## アクセスログに使うと・・・? - スパイク(突発的なアクセス)の除去 - スパイクの生じた日時の抽出 (位相のずれを考えると、FIRの方が良いかと・・・。)

スパイク(突発的なアクセス)の除去

低域通過フィルタ(長い周期の信号を通過させる)

スパイクの生じた日時の抽出

高域通過フィルタ(短い周期の信号を通過させる)

## これ、どうやって描いたの? - Imager - Cassis https://github.com/techno-cat/p5-Cassis
## Cassisを使って、ディタルフィルタが学べる!!1
## 試してみよう! 1. 波形の生成 1. 波形の足し算 1. フィルタを通す
## その前に - 波形を描いてみる - WAVファイルで出力
## 波形を描いてみる(1/5) グラフの余白をあらかじめ決めると、後が楽になる ```perl # 目盛の色 use constant TICK_COLOR => Imager::Color->new( 80, 80, 80 ); use constant TICK_X => 20; # x軸方向の目盛の間隔 use constant TICK_Y => 50; # y軸方向の目盛の間隔 use constant STEP_X => 5; # プロット間隔 use constant MARGIN => 20; # グラフ領域と画像サイズの隙間 use constant N => 80; # プロット数 ```
## 波形を描いてみる(2/5) 原点の計算を済ませると描画処理が簡単になる ```perl # グラフ領域の計算 my $graph_width = (STEP_X * N) + 1; my $graph_height = 200; # 原点 my $x0 = MARGIN; my $y0 = MARGIN + $graph_height - 1; # 波形データの生成 my @src = map { 0.5 + sin($_ * 0.1) * 0.25 } 0..(N - 1); draw_graph( 'my_graph.png', \@src ); ```
## 波形を描いてみる(3/5) グラフ描画の手続き ```perl sub draw_graph { my ( $path, $src ) = @_; my $img = Imager->new( xsize => MARGIN + $graph_width + MARGIN, ysize => MARGIN + $graph_height + MARGIN ); $img->box( filled => 1, color => 'black' ); draw_graduation( $img, TICK_COLOR ); draw_wave( $img, \@src, 'red' ); $img->write( file => $path ) or die $img->errstr; } ```
## 波形を描いてみる(4/5) 目盛を描画 ```perl sub draw_graduation { my ( $img, $color ) = @_; my $x = 0; while ( $x <= $graph_width ) { $img->line( color => $color, x1 => $x0 + $x, y1 => $y0, x2 => $x0 + $x, y2 => $y0 - $graph_height - 1 ); $x += (TICK_X * STEP_X); } my $y = 0; while ( $y <= $graph_height ) { $img->line( color => $color, x1 => $x0, y1 => $y0 - $y, x2 => $x0 + $graph_width - 1, y2 => $y0 - $y ); $y += TICK_Y; } } ```
## 波形を描いてみる(5/5) データをプロット ```perl sub draw_wave { my ( $img, $data, $color ) = @_; my $y0 = int($img->getheight() / 2); my $xmax = scalar(@{$data}) - 1; my @points = map { my $gain = TICK_Y * $data->[$_]; [ $_, $y0 - int(($gain < .0) ? ($gain - .5) : ($gain + .5)) ]; } 0..$xmax; $img->polyline( points => \@points, color => $color ); } ```
## WAVファイルで出力 ```perl use Cassis; use Math::Trig ':pi'; use constant SAMPLING_RATE => 44100; my @src = map { sin(440 * 2.0 * pi * ($_ / SAMPLING_RATE)) } 0..(SAMPLING_RATE - 1); Cassis::File::write( file => '440.wav', sf => SAMPLING_RATE, channels => [ \@src ] ); ```
## 波形の生成(1/2) ```perl { my $osc1_out = Cassis::Osc::Pulse->new( fs => N, freq => 2 )->exec( num => N ); my $osc2_out = Cassis::Osc::Sin->new( fs => N, freq => 4 )->exec( num => N ); my $img = Imager->new( xsize => MARGIN + $graph_width + MARGIN, ysize => MARGIN + $graph_height + 100 + MARGIN ); $img->box( filled => 1, color => 'black' ); draw_graduation( $img, TICK_COLOR ); draw_wave( $img, $osc1_out, 'red' ); draw_wave( $img, $osc2_out, 'green' ); $img->write( file => '01.png' ) or die $img->errstr; } ```

波形の生成(2/2)

## 波形の足し算(1/2) ```perl { my $osc1_out = Cassis::Osc::Pulse->new( fs => N, freq => 2 )->exec( num => N ); my $osc2_out = Cassis::Osc::Sin->new( fs => N, freq => 4 )->exec( num => N ); my $mixer_out = Cassis::Mixer::mix( { src => $osc1_out, volume => 0.2 }, { src => $osc2_out, volume => 0.1 } ); my $img = Imager->new( xsize => MARGIN + $graph_width + MARGIN, ysize => MARGIN + $graph_height + 100 + MARGIN ); $img->box( filled => 1, color => 'black' ); draw_graduation( $img, TICK_COLOR ); draw_wave( $img, $mixer_out, 'orange' ); draw_wave( $img, $osc1_out, 'red' ); draw_wave( $img, $osc2_out, 'green' ); $img->write( file => '02.png' ) or die $img->errstr; } ```

波形の足し算(2/2)

## フィルタを通す(1/2) ```perl { my $osc1_out = Cassis::Osc::Pulse->new( fs => N, freq => 2 )->exec( num => N ); my $osc2_out = Cassis::Osc::Sin->new( fs => N, freq => 4 )->exec( num => N ); my $mixer_out = Cassis::Mixer::mix( { src => $osc1_out, volume => 0.2 }, { src => $osc2_out, volume => 0.1 } ); # "linear => 1"を設定すると、一般的な方法で計算が使われる(音楽用途だと使いにくい) my $q = 1.0 / sqrt(2.0); my $lpf = Cassis::Iir2::LPF->new( cutoff => 0.04, q => $q, linear => 1 ); my $hpf = Cassis::Iir2::HPF->new( cutoff => 0.015, q => $q, linear => 1 ); my $img = Imager->new( xsize => MARGIN + $graph_width + MARGIN, ysize => MARGIN + $graph_height + 100 + MARGIN ); $img->box( filled => 1, color => 'black' ); draw_graduation( $img, TICK_COLOR ); draw_wave( $img, $mixer_out, 'orange' ); draw_wave( $img, $lpf->exec( src => $mixer_out ), 'red' ); draw_wave( $img, $hpf->exec( src => $mixer_out ), 'green' ); $img->write( file => '03.png' ) or die $img->errstr; } ```

フィルタを通す(2/2)

## まとめ - 使い道はいろいろ - Cassisは難しい - でも、Imagerは便利だよ!
## おまけ アクセスログのグラフに使ったスクリプト ```perl #!perl use strict; use warnings; use Cassis; use Imager; use constant TICK_COLOR => Imager::Color->new( 80, 80, 80 ); use constant TICK_X => 20; use constant TICK_Y => 50; use constant STEP_X => 5; use constant MARGIN => 20; use constant N => 80; my $graph_width = (STEP_X * N) + 1; my $graph_height = 200; my $x0 = MARGIN; my $y0 = MARGIN + $graph_height - 1; my @src = map { (sin($_ * 0.1) * 0.01) + ($_ * 0.005); } 0..(N - 1); # スパイクの追加 $src[20] += 0.3; $src[50] += 0.1; $src[60] += 0.2; my $q = 1.0 / sqrt(2.0); { my $f = Cassis::Iir2::LPF->new( cutoff => 0.04, q => $q, linear => 1 ); my $dst = $f->exec( src => \@src ); draw_graph( 'sample_lpf.png', \@src, $dst ); } { my $f = Cassis::Iir2::HPF->new( cutoff => 0.015, q => $q, linear => 1 ); my $dst = $f->exec( src => \@src ); draw_graph( 'sample_hpf.png', \@src, $dst ); } sub draw_graph { my ( $path, $src, $dst ) = @_; my $img = Imager->new( xsize => MARGIN + $graph_width + MARGIN, ysize => MARGIN + $graph_height + MARGIN ); $img->box( filled => 1, color => 'black' ); draw_graduation( $img, TICK_COLOR ); draw_wave( $img, \@src, 'red' ); draw_wave( $img, $dst, 'green' ); $img->write( file => $path ) or die $img->errstr; } sub draw_graduation { my ( $img, $color ) = @_; my $x = 0; while ( $x <= $graph_width ) { $img->line( color => $color, x1 => $x0 + $x, y1 => $y0, x2 => $x0 + $x, y2 => $y0 - $graph_height - 1 ); $x += (TICK_X * STEP_X); } my $y = 0; while ( $y <= $graph_height ) { $img->line( color => $color, x1 => $x0, y1 => $y0 - $y, x2 => $x0 + $graph_width - 1, y2 => $y0 - $y ); $y += TICK_Y; } } sub draw_wave { my ( $img, $data, $color ) = @_; my $y1; my $n = scalar( @{$data} ); for (my $i=0; $i<$n; $i++) { my $x = $x0 + ($i * STEP_X); my $y = $y0 - ($graph_height * $data->[$i]); $y = int( ($y < .0) ? ($y - .5) : ($y + .5) ); if ( 0 < $i ) { my $x1 = $x - STEP_X; $img->line( x1 => $x1, y1 => $y1, x2 => $x , y2 => $y, color => $color ) } $img->box( xmin => $x - 1, ymin => $y - 1, xmax => $x + 1, ymax => $y + 1, color => $color, filled => 0 ); $y1 = $y; } } ```
## ご静聴ありがとうございました!