真・聴力検査 hacks
TRANSCRIPT
真・聴力検査 HACKS
社外版2012/10/24@nipotan
以前社内で
• 「聴力検査 HACKS」というネタで LT• Mac で簡単に聴力検査をしたい• だけど音を鳴らすのって複雑だよね
say -v Ralph "poe"
#!/bin/sh
v=1while [ $v -le 200 ]do vol=`echo "scale=2; ${v} / 100" | bc | sed -e 's/^\./0./g'` echo $vol /usr/bin/osascript -e "set Volume ${vol}" /usr/bin/say -r 250 -v Ralph "poe" v=`expr $v + 1`done
まぁ…
• 急に作ったネタとは言え、だいぶお粗末• 音程によって声の人物、発音する単語を変えるのはいかがなものか
• “声” ではなく “音” を扱うのに say は不適切なのでは?
聴力検査• オージオメーター• 英語で書くと audiometer• 健康診断でお馴染み• 「“ピーピーピー” “ポーポーポー” と音が鳴ってる間はボタンを押してください」
• もっとオージオメーターを手軽に
ポー そして ピー• say で poe とか pee とか変じゃない?• 音程によって低音は Ralph さんとか、高音なら Ya-Ling さんとか、指名するのはなんか変
• そもそも機械的な音声のピーもポーも音程が違うだけで同じ音色
• 何故 Ralph は “poe” と言わされたか?
擬音語、奥深い• カタカナで表現する擬音語• 子音は attack (立ち上がり) による• 母音は音程による (高 イ→エ→ア→オ→ウ 低)
• decay (減衰) は「ン」で表現• Wikipedia の「子音」の項見るとすごい
※個人の感想であり、効果・効能を示すものではありません
擬音語、奥深い• サイレン「ピーポーピーポー」時報「ピ、ピ、ピ、ポーン」鐘「キンコンカンコン」
• だいたいこの法則に則ってる• 「ノーシンピュア」の CM でアッキーナが最後に言う「ポンピーン」は唯一例外
• 字が違うが、音程違いだけど同じ音なはず※個人の感想であり、効果・効能を示すものではありません
人間の口腔は複雑
• 声帯から出る音声は同じでも、口腔の形状で響き (母音) がかわる
• イコライザ、トーンカーブ的な役割• 人間の音声以外は口腔を介していない
※個人の感想であり、効果・効能を示すものではありません
人間の口腔は複雑• ボコーダー、トーキングモジュレータ、ワウペダル、ワウワウミュート等は人間の口腔による音色変化をシミュレート• ボコーダー: YMO「Technopolis」
• トーキングモジュレータ: BON JOVI「Livin’ On a Prayer」、同じ BON JOVI で、 なかやまきんに君がパスタに粉チーズかける時の BGM「It’s My Life」
• ワウペダル: Jimi Hendrix「Voodoo Child」、B’z 松本孝弘の音作り (マニアック)
• ワウワウミュート: Pee Wee Hunt「Somebody Stole My Gal」(吉本新喜劇のテーマ)
※個人の感想であり、効果・効能を示すものではありません
Mac で音を鳴らす• say はやめて、純粋な音を鳴らす• Perl で .wav (WAVE) ファイルを作ろう• データ形式について軽く調べれば出来る• ちなみに音に関しては完全にド素人。このネタのために、調べたり頭で考えながら作った資料なのであんまり細かいこと言わないでください
※個人の感想であり、効果・効能を示すものではありません
CD 音質• 44100 Hz, 16bit, ステレオ• 秒間 44100 サンプル• 1 サンプル 16bit → 2 bytes• ステレオはサンプルが左右交互に 2 つ• block size は 2 bytes x 2 = 4 bytes• 44100 (sample) x 4 (bytes) =176,400 bytes/sec (≒ 172.27KB ≒ 1.35Mbps)
WAVE データ形式
• RIFF (Resource Interchange File Format) という汎用メタファイル形式
• RIFF ヘッダと RIFF データからなる• WAVE データは RIFF データ内のWAVE ヘッダとサブチャンクから構成
WAVE データ形式
• 格納データはリトルエンディアン形式• Apple が採用する AIFF (Audio Interchange File Format) は似たフォーマットで格納データはビッグエンディアン形式
• そもそも RIFF は AIFF を元に作られた?
WAVE データ内容 バイト数
RIFF ヘッダ 8
WAVE ヘッダ “WAVE” 4
fmt チャンク 24~
data チャンク -
あくまで、音を鳴らすための最低限
RIFF ヘッダ
内容 バイト数
RIFF 識別子 “RIFF” 4
これより後ろのデータサイズ(合計サイズ -8) 4
WAVE ヘッダ
内容 バイト数
WAVE 識別子 “WAVE” 4
fmt チャンク内容 バイト数
fmt 識別子 “fmt ” 4fmt チャンクの
これより後ろのデータサイズ 4フォーマット ID (リニア PCM = 1) 2
チャンネル数(モノ:1 ステレオ: 2) 2
サンプリングレート (Hz) 4データ速度
(サンプリングレート x block size) 4block size 2bit 数 2
data チャンク内容 バイト数
data 識別子 “data” 4
data チャンクのこれより後ろのデータサイズ 4
音声データ -
音声データ内容 バイト数
左チャンネルのサンプル 2
右チャンネルのサンプル 2
↑これが 1 ブロック1 秒あたり 44100 ブロック
音の基礎• 440Hz (秒間 440 回振幅) の音はラ (A4)• 半分の 220Hz、倍の 880Hz もラ(A3, A5)
• A3~A5 は用紙サイズではなく A がラを、後ろの数字がオクターブを表す
• MIDI データだとオクターブが 1 つ下の扱い
音の基礎• オクターブ間の周波数を 12 分割したのが音階 ((十二)平均律) で、世の中の音楽のデファクト
• 和声で平均律の不自然さに異を唱える思想もあり。「音律」で Wikipedia
• 振幅の幅が音量• 振幅の形が音色
振幅の形 (波形)• 代表: 正弦波 (sine)、矩形波 (square)、三角波 (triangle)、のこぎり波 (sawtooth)
楽器の音色の違い
• 波形の違い• attack, decay の違い• 基音 (fundamental tone) と倍音 (harmonics) の音量と組み合わせの違い
• トーンの違い
※個人の感想であり、効果・効能を示すものではありません
純音
• 楽音と違い、オージオメーターは「純音」• 倍音を一切含まない正弦波• 自然界には存在しない音 (口笛、音叉、クリスタルガラス楽器演奏が近い)
※個人の感想であり、効果・効能を示すものではありません
純音• アナログでは、ウィーンブリッジ発振回路 (Wien bridge oscillator) で実現可能
• デジタルでは量子化による誤差が生じる• 「完全な正弦波は作れない」• サンプリングレート、量子化ビット数を上げることで近似的な音は作れる
※個人の感想であり、効果・効能を示すものではありません
オージオメータ仕様• 125Hz, 250Hz, 500Hz, 1000Hz, 2000Hz, 4000Hz, 8000Hz を出力する
• シの音 (123.47Hz, 246.94Hz, 493.88Hz, 987.76Hz ...) に近い
• 音が鳴ってる箇所、無音の箇所の繰り返し• 左右で鳴り分け (ステレオ)
WAVE で作る純音
• 音声データ生成における Hello World (多分)• 正弦波を 16bit で表現する
※個人の感想であり、効果・効能を示すものではありません
WAVE で作る純音sub make_sine { my($hz, $sec, $lvol, $rvol) = @_; my $maxno = 2 ** 16 / 2 - 1; # 32767 $lvol = $lvol / 100 * $maxno; $rvol = $rvol / 100 * $maxno; my $length = 44100 * $sec; my $sound = ''; for my $pos (1 .. $length) { my $val = sin 2 * 3.14 * $hz * ($pos / 44100); $sound .= pack 'v', $val * $lvol; # left $sound .= pack 'v', $val * $rvol; # right } return $sound;}
WAVE で作る無音
sub make_silent { my $sec = shift; my $length = 44100 * $sec; my $sound = ''; for my $pos (1 .. $length) { $sound .= "\0" x 4; } return $sound;}
純音無音の繰り返し
sub audiometer { my($lr, $oct) = @_; my $hz = 500 * 2 ** ($oct - 3); my $data = ''; for my $vol (1 .. 20) { my @vol_args = uc($lr) eq 'L' ? ($vol, 0) : (0, $vol); $data .= make_sine($hz, 0.3, @vol_args); $data .= make_silent(0.2); } return $data;}
忘れないで、ヘッダmy $header = riff_header(length $data);
sub riff_header { my $size = shift; my $header = 'RIFF'; # RIFF header $header .= pack 'V', $size + 36; # following data size $header .= 'WAVE'; # WAVE header $header .= 'fmt '; # fmt header $header .= pack 'V', 16; # format chunk size $header .= pack 'v', 1; # format id (PCM = 1) $header .= pack 'v', 2; # monaural = 1, stereo = 2 $header .= pack 'V', 44100; # sampling rate (44.1kHz) $header .= pack 'V', 44100 * 4; # byte/sec $header .= pack 'v', 4; # block size (16bit stereo) $header .= pack 'v', 16; # bit/sample $header .= 'data'; # data header $header .= pack 'V', $size; # data chunk size return $header;}
お洒落に PSGI でmy $app = sub { my $env = shift; my @path = $env->{REQUEST_URI} =~ m{^/([lr])/([1-7])/?$}i; die "invalid path $env->{REQUEST_URI}" unless @path == 2; my $wave = audiometer(@path); return [ 200, [ 'Content-Type' => 'audio/x-wav', 'Content-Length' => length($wave) + 44, ], [ riff_header(length $wave), $wave, ], ];};
DEMO
応用: モスキート音sub mosquito { return make_sine(17000, 10, 100, 100) }
• 17kHz 以上の音はモスキート音• 若者には聞こえるらしいぜ• はぁ、どうせ聞こえねーし
※個人の感想であり、効果・効能を示すものではありません
応用: 簡易シーケンサsub tone_to_hz { my $tone = shift; my($scale, $shift, $octave) = $tone =~ / ^ ([A-G]) # A-G ([-+b\#]?) # -, +, b, # ([1-9]?) # 1-9 $ /x; return 0 unless $scale; my %tone_index = ( C => -9, D => -7, E => -5, F => -4, G => -2, A => 0, B => 2, ); my $index = $tone_index{$scale}; if ($shift) { my $num = $shift eq '+' || $shift eq '#' ? 1 : -1; $index += $num; } if ($octave && $octave != 3) { $index += ($octave - 3) * 12; } return 440 * (2 ** ($index / 12));}
応用: 簡易シーケンサsub sequence { my $bpm = shift; my $crotchet = 60 / $bpm; my $seconds = +{ 16 => $crotchet / 4, 8 => $crotchet / 2, 4 => $crotchet, 2 => $crotchet * 2, 1 => $crotchet * 4, }; my $wave = ''; for my $data (@_) { my($tone, $len, $vol) = @$data; $vol ||= 80; my $sec = 0; for my $note (split /-/, $len) { my $dotted = $tone =~ s/\.$// ? 1 : 0; $sec += $seconds->{$note} * ($dotted ? 1.5 : 1); } if ($tone eq '-') { $wave .= make_silent($sec); next; } $wave .= make_sine(tone_to_hz($tone), $sec, $vol, $vol); } return $wave;}
応用: 簡易シーケンサsub heavy_rotation { my $bpm = shift; my $crotchet = 60 / $bpm; return sequence( $bpm, ['E3', 4], # I ['F#3', 4], # want ['G#3', '1-4'], # you ['-', 4], ['E3', 4], # I ['F#3', 4], # need ['G#3', '1-4'], # you ['-', 4], ['E3', 4], # I ['F#3', 4], # love ['G#3', '1-4'], # you ['-', 4], ['G#3', 4], # あ ['A3', 4], # た ['B3', 2], # ま ['G#3', 2], # の ['F#3', 2], # な ['E3', 2], # か ['C#4', 4], # ガ
['C#4', 4], # ン ['C#4', 4], # ガ ['A3', 4], # ン ['F#3', 4], # な ['F#3', 4], # っ ['G#3', 4], # て ['A3', 4], # る ['B3', 4], # mu ['B3', 4], # - ['B3', 4], # si ['G#3', 4], # - ['E3', 2], # c ['-', 4], ['G#3', 4], # hea ['A3', 2], # vy ['C#3', 2], # - ['E3', 4], # ro ['D#3', 4], # - ['C#3', 4], # ta ['D#3', 4], # - ['E3', 2], # tion );}
この聴力検査方法• 厳密な聴力の検査には向かない• 再生デバイス側でボリュームコントロール可能
• 音波への変換装置の個体差• あくまで簡易的な検査にとどめましょう• 耳の異常を感じたら、早めに耳鼻科へ
まとめ
• あくまで、音を扱ってみただけです• でも音を扱うのは基本がわかればちょっと楽しくなってくる
おわり
• 質問はあんまり受けたくありません