トップ 差分 一覧 ソース 検索 ヘルプ RSS ログイン

時系列データの解析

音声データ(リスト)を加工したり分析したりする方法を考えよう。

 エコーをかける

先週最後に少し触れたように、波形を時間方向にすこしずらせ、重ねると、エコーがかかったように聞こえる。

先週の、波形を合成するプログラムmix.pyに、時間遅れを作る関数delayを追加する。

delay.py

from myaudio import *
from math import *

def sinewave(freq,length, amp):
    data = []
    for i in range(0, int(length*44100)):
        data += [ int( amp * sin(2.0*pi*i*freq/44100)) ]
    return data

def show(data):
    beginpath(0,320)
    #全部は表示できないので、はじめの1000個だけ表示
    for i in range(0, min(1000,len(data))):
        lineto(i, data[i]/100+320)
    moveto(0,320)
    endpath()

def mix(data1,data2):
    #max(x,y)はx,yのうち大きいほうを返す関数。
    length = max(len(data1),len(data2))
    #長いほうにあわせる。
    data = [0] * length
    for i in range(0,len(data1)):
        data[i] += data1[i]
    for i in range(0,len(data2)):
        data[i] += data2[i]
    return data

#####ここまでは先週作った関数
    

#波形の音量(振幅)をratio倍する。
def mute(data,ratio):
    result = []
    for i in range(0,len(data)):
        result.append( int(data[i]*ratio) )
    return result


#5秒間録音し
voice=record(5)
#0.3秒の無音を頭にくっつける
delayed = sinewave(400, 0.3, 0)+voice
#音量を小さくして
muted = mute(delayed, 0.2)
#2つを重ねて
mixed = mix(voice,muted)
#再生する。
play(mixed)

エコーは音を重厚にしたり、やわらかくするのに利用できるが、あまり音を重ねすぎるとお風呂の中の声のようにやかましくなってしまう。

 エコーを消す

では、すでにエコーがかかってしまっている音源から、エコーを除くことはできるだろうか。

一番単純なケースとして、エコーの遅延時間と、合成率がわかっている場合は、逆波形を足しあわせることで、だいたい消すことができる。次のプログラムでは、サンプル音に一旦エコーをかけてから、その音だけを使ってエコーを消そうとしている。

cancel-echo.py

from myaudio import *
from math import *

def sinewave(freq,length, amp):
    data = []
    for i in range(0, int(length*44100)):
        data += [ int( amp * sin(2.0*pi*i*freq/44100)) ]
    return data

def show(data):
    beginpath(0,320)
    #全部は表示できないので、はじめの1000個だけ表示
    for i in range(0, min(1000,len(data))):
        lineto(i, data[i]/100+320)
    moveto(0,320)
    endpath()

def mix(data1,data2):
    #max(x,y)はx,yのうち大きいほうを返す関数。
    length = max(len(data1),len(data2))
    #長いほうにあわせる。
    data = [0] * length
    for i in range(0,len(data1)):
        data[i] += data1[i]
    for i in range(0,len(data2)):
        data[i] += data2[i]
    return data
    
#####ここまでは先週作った関数
    

#波形の音量(振幅)をratio倍する。
def mute(data,ratio):
    result = []
    for i in range(0,len(data)):
        result.append( int(data[i]*ratio) )
    return result



#3秒間録音し
voice=record(3)
#0.3秒の無音を頭にくっつける
delayed = sinewave(400, 0.3, 0)+voice
#音量を小さくして
muted = mute(delayed, 0.2)
#2つを重ねて
mixed = mix(voice,muted)
#再生する。
play(mixed)


#mixedだけをもとに、エコーを消す。
#0.3秒遅らせ、
delayed = sinewave(400, 0.3, 0) + mixed
#反響音の強さにあわせ、波の上下を逆にして
echo = mute(delayed, -0.2)
#重ねあわせる。
remixed = mix(mixed, echo)
play(remixed)

「だいたい」と書いたのは、エコーのかかった音の逆波形にもエコーが含まれているせいである。完全に消すには、これを消すためにさらに音を重ねる必要がある。

ノイズキャンセリングヘッドホンの原理もこれと同じである。ヘッドホンのハウジングの外側に装備したマイクで、外部のノイズを拾い、ハウジングの中に侵入するノイズとちょうど逆波形の音を、音楽に重ねることで、ノイズを消してしまう。

(なお、ノイズキャンセラーは、コンピュータを使わなくても、opアンプ1つだけで簡単に作れます。 Wikipedia:オペアンプ#.E5.B7.AE.E5.8B.95.E5.A2.97.E5.B9.85.E5.9B.9E.E8.B7.AF)

音の引き算は、ほかにもいろんな用途が考えられる。

  • 人間は左右の耳に聞こえる音量の差で、ある程度音源の方角を推定することができる。これを利用し、ステレオ録音の曲では、ギターやドラムの音を左右にふりわけて立体感を出している、一方、ステレオ録音であっても、ボーカルは中央に定位している(右と左に同じ音量で録音されている)場合が多い。そこで、ステレオ録音の左右の音の波形の差をとると、ボーカルがきれいにキャンセルして、カラオケを作ることができる。
from myaudio import *
from math import *

def sinewave(freq,length, amp):
    data = []
    for i in range(0, int(length*44100)):
        data += [ int( amp * sin(2.0*pi*i*freq/44100)) ]
    return data

def show(data):
    beginpath(0,320)
    #全部は表示できないので、はじめの1000個だけ表示
    for i in range(0, min(1000,len(data))):
        lineto(i, data[i]/100+320)
    moveto(0,320)
    endpath()

def mix(data1,data2):
    #max(x,y)はx,yのうち大きいほうを返す関数。
    length = max(len(data1),len(data2))
    #長いほうにあわせる。
    data = [0] * length
    for i in range(0,len(data1)):
        data[i] += data1[i]
    for i in range(0,len(data2)):
        data[i] += data2[i]
    return data
    
#####ここまでは先週作った関数
    

#波形の音量(振幅)をratio倍する。
def mute(data,ratio):
    result = []
    for i in range(0,len(data)):
        result.append( int(data[i]*ratio) )
    return result



left = loadwave("left.wav")
right= loadwave("right.wav")
#左トラックだけ聞いてみる。
play(left)


#左/2 - 右/2を計算
left = mute(left,0.5)
right = mute(right,-0.5)
play(mix(left,right))

left.wav(217)
right.wav(210)

  • 逆に、シングルCDなどで、原曲とカラオケバージョンがいっしょに録音されているなら、原曲とカラオケの波形の差をとると、歌手の生の声だけを抽出することができるはずである。
  • 音楽データは1分あたり約10Mbytesの大きさがあるので、iPodなどのデジタル音楽プレーヤーでは、より多くの曲をもちあるけるように、CDの楽曲の音質を少し落し、MP3形式等に圧縮する。CDの波形と、圧縮音の波形の差分をとれば、圧縮によってどんな音が失われたかを聞くことができる。

遅延時間なんてわかりません。

上の例では、遅延時間と減衰率を自分で設定したので、その知識を使って、逆波形の音を重ねることで残響音を消すことができた。しかし、通常は遅延時間や減衰率などわからない。どうやってそれらを推定すればいいだろうか。

反響音は元の音よりは小さいから、エコーが加わっていても全体の波形は元の音からそれほど変化していないはずである。そこで、反響の含まれる音が元の音とほぼ等しいという前提のもとに、音源と、それをΔtだけ遅延させた音の波形を重ねてみる。すると、Δtがちょうど残響時間に等しい時に、2つの波の重なりが一番大きくなるはずである。

2つの波の重なりは、次のような式で計算することにする。

このような関数を相関関数と呼び、得られる重なり量を相関係数と呼ぶ。fとgが同じ波形であれば相関係数は1、無関係な波形であれば0になる。

今回の場合は、重なりを見積もる対象は音源をΔtだけ遅延させた音である。このような場合は自己相関関数と呼ぶ。

エコーの加わった音の、自己相関係数を求めるプログラムを以下に示す。

corr.py

from myaudio import *
from math import *

def sinewave(freq,length, amp):
    data = []
    for i in range(0, int(length*44100)):
        data += [ int( amp * sin(2.0*pi*i*freq/44100)) ]
    return data

def show(data):
    beginpath(0,320)
    #全部は表示できないので、はじめの1000個だけ表示
    for i in range(0, min(1000,len(data))):
        lineto(i, data[i]/100+320)
    moveto(0,320)
    endpath()

def mix(data1,data2):
    #max(x,y)はx,yのうち大きいほうを返す関数。
    length = max(len(data1),len(data2))
    #長いほうにあわせる。
    data = [0] * length
    for i in range(0,len(data1)):
        data[i] += data1[i]
    for i in range(0,len(data2)):
        data[i] += data2[i]
    return data
    
def mute(data,ratio):
    result = []
    for i in range(0,len(data)):
        result.append( int(data[i]*ratio) )
    return result

#2つの波形の重なり積分を計算する関数
def overlap(data1,data2):
    sum=0.0
    #min(x,y)はx,yのうち小さいほうを返す関数。
    length = min(len(data1),len(data2))
    #長いほうのリストにあわせて積分する。
    for i in range(0,length):
        sum += data1[i]*data2[i]
    #積分値を返す。
    return sum

#1秒間録音し
tone=record(1)
#0.1秒のエコーを重ねる。
echo=sinewave(400,0.1,0) + mute(tone,0.2)
tone = mix(tone,echo)

#解析
#少しずつ(0.05sec〜1sec)ずらしながら、相関係数を求める。
A = overlap(tone,tone)
for t in range(0,100):
    delta = 0.01*t
    delayed = sinewave(400,delta,0) + tone
    corr = overlap(tone,delayed) / A
    print delta, corr

確かに、0.1秒ずらしたところで、自己相関係数がかなり大きくなっていることがわかる。

自己相関をとり、重なりが最大になるような遅延時間を探し、逆波形を足しあわせてエコーを除去し、また自己相関をとり、というサイクルを繰替えせば、単純なエコーは除去できる(はず)。ただし、やりすぎると、楽器内での共鳴音まで削りとってしまうので注意。

 特定波数の成分をさがす

音源の中に、音源と同じ波形(エコー)をさがすのに相関関数を使う方法を紹介したが、同じ理屈で、音源の周波数成分を調べることができる。

例えば、400Hzの正弦波との相関係数を求めれば、音源の中に400Hzの正弦波がどれだけ含まれているかがわかる。これはフーリエ変換の原理である。