すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

【python】波形の表示(モノラル・ステレオ)【サウンドプログラミング】

こんにちは,1000chです. 音系VR研究してるんですーっていう割に信号処理能力が雑魚過ぎるので, ちょっとまじめにサウンド処理を勉強しようと思いました. てなわけで久しぶりにこの本でお勉強することにします.

MatlabやらOctaveやらを使って信号処理をする本です. 自分はMatlabは持っていないですが,理論の紹介があって実際のコードがあって,という流れで紹介しているので なかなか参考になります.というかかなりわかりやすい本だと思います.

今まで理論部分だけ読んで「ふーん」となってはすぐに忘れるを繰り返していました. そこで.キチンと自分で手を動かして理論を身につけてこうと思います!

実装として,pythonMax/MSPあたりを使って遊んでいく所存です. python使っての信号処理に関してはこちらを, Max/MSPの使い方に関しては公式のチュートリアルを参考にしていきます. (前者のサイトは非常に丁寧に紹介してくださっているため,なるべく見ずにコーディングしてからの答え合わせ,という参考にしようと思っています)

waveファイルの読み込み

目下の目標は,「波形を直接弄ってエフェクトをかけること」です. という訳で今回は,「waveファイルを読み込んで数値データとして扱う」事を目指します. 先ほど紹介したこちらが非常に参考になります.

pythonでは標準でwaveモジュールが用意されているので,こちらを使えば波形の読み込みは簡単そうです. ただし読み込まれるデータはバイナリなので,適宜数値に変換する必要があります. 今回はnumpyのfrombuffer関数を利用することにしました. 波形プロットはmatplotlibを使えばokですね.

それでは,waveファイルを読み込んでその波形を表示してみます. 以下がコード.

#coding:utf-8
import wave
import numpy as np
import matplotlib.pyplot as plt

def wave_plot(filename):
    # open wave file
    wf = wave.open(filename,'r')
    print(wf.getparams())

    # load wave data
    chunk_size = 1024
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化

    # make time axis
    rate = wf.getframerate()  # フレームレート[1/s]
    size = float(chunk_size)  # 波形サイズ
    x = np.arange(0, size/rate, 1.0/rate) # 

    # plot
    plt.plot(x,data)
    plt.show()

このプログラムを使って,波形を見てみます. audacityを使って440Hzのサイン波を生成しました. こんなんです.

実行結果

f:id:ism1000ch:20140513164750p:plain

いい感じにplotできていますねー.

試しにのこぎり波もplotしてみます. 同じくaudacityを使ってこんなのを生成しました.

実行結果

f:id:ism1000ch:20140513165506p:plain

本当にギザギザです!

ステレオ波形の表示

ここまで表示してきた波形はモノラル音源のものでした. ではここで,ステレオ音源を表示しようとするとどうなるのでしょう?

試しに,右にサイン波,左にのこぎり波の音源を作ってみました.

試してみると,

# (中略)
File "/Users/masa/.virtualenvs/py3/lib/python3.3/site-packages/matplotlib/axes.py", line 239, in _xy_from_xy
    raise ValueError("x and y must have same first dimension")
ValueError: x and y must have same first dimension

起こられました. ステレオ音源なのでデータ数が会わないんですね. 調整してみるとこんな感じに.

f:id:ism1000ch:20140513170252p:plain

かっこいい!

けど明らかに波形がおかしいです. ステレオ音源だとLRの要素が交互に配置されているため,このようなことになるのですね. であれば,plotするデータを1つとびにすればよさそうです. pythonのリストであればlist[::2]とか書けばいけそう.

というわけで先ほどのコードの一部を書き換えます.

#coding:utf-8
import wave
import numpy as np
import matplotlib.pyplot as plt

def wave_plot(filename):
    # open wave file
    wf = wave.open(filename,'r')
    channels = wf.getnchannels() # 追記
    print(wf.getparams())

    # load wave data
    chunk_size = 256
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化

    # make time axis
    rate = wf.getframerate()
    size = float(chunk_size)
    x = np.arange(0, size/rate, 1.0/rate)

    # plot マルチチャンネルに対応
    for i in range(channels):
        plt.plot(x,data[i::channels])

    plt.show()

これでplotしてみます.

f:id:ism1000ch:20140513170725p:plain

青と緑で別々の波形を表示することができました!

これでステレオ波形であったとしても,サウンドを数値として扱えるようになりました. エフェクトをかけるためのお膳立ては整った!

これから

しかし,波形をただ扱えるだけではまだまだエフェクトは作れません. エフェクトを書けるフィルタの設計のためには,まだまだ勉強する要素がたくさんあります. エフェクトの処理自体は,単純なFIRであればフレーム毎にゲインをかけて総和をとるだけです. 単純なエフェクタ(オーバードライブとかコンプレッサとか)ならこれだけでも実現できてしまいます.

しかし,複雑なエフェクト(コーラスとかノイズゲートとか)では,そうもいきません. これらを設計する為には音情報を時間領域のデータから周波数領域のデータに変換する必要があります. デジタル信号処理においては,いわゆる離散フーリエ変換(DFT)というやつにあたります. 周波数領域において適切なフィルタを設計し,これを逆離散フーリエ変換(IDFT)で時間領域のデータにもどすことで,初めて有効なフィルタが設計できるのです.

まだまだ先は長いですが,少しずつ身につけていこうと思います. さーて,がんばろー.

おまけ

ちなみに波形の表示ですが,Max/MSPだとこんな感じです.

ああ,なんと簡単なのでしょう笑 まぁ,理論を勉強するのは重要ですよね!