人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ディストーション

Pythonで音声信号処理(2011/05/14)

今回は、ディストーション(distortion)というサウンドエフェクトを試してみます。ディストーションは、音を極端に増幅し、わざとクリッピングを発生させることで、歪んだ(ひずんだ)音色を作る技術とのこと。普通の音にこのエフェクトをかけるとエレキギター風の音になるらしい。

音の波形データは、音声の量子化ビット数が16bitの場合は、波形は-32768から32767の値(正規化した場合は-1.0から1.0)しか取れません。波形が範囲外になるように増幅し、飛び出た部分をすぱっと切る(ハードクリッピング)とディストーションになるとのこと。サイン波にディストーションをかけると下みたいに上の部分が切れて四角波っぽくなります。上がオリジナルのサイン波(440Hzと880Hzの合成波形)で下がディストーションをかけた波です。

f:id:aidiary:20111015223156p:plain

音声も作ってみました。上がオリジナルのサイン波で下がディストーションをかけたサイン波です。

Pythonで実装したディストーションのプログラムです。

#coding:utf-8
import wave
import pyaudio
import struct
from pylab import *

def play(data, fs, bit):
    p = pyaudio.PyAudio()
    stream = p.open(format=pyaudio.paInt16,
                    channels=1,
                    rate=int(fs),
                    output=True)
    # 再生
    chunk = 1024
    sp = 0
    buffer = data[sp:sp+chunk]
    while stream.is_active():
        stream.write(buffer)
        sp += chunk
        buffer = data[sp:sp+chunk]
        if buffer == '': stream.stop_stream()
    stream.close()
    p.terminate()

def distortion(data, gain, level):
    length = len(data)
    newdata = [0.0] * length
    for n in range(length):
        newdata[n] = data[n] * gain  # 増幅
        # クリッピング
        if newdata[n] > 1.0:
            newdata[n] = 1.0
        elif newdata[n] < -1.0:
            newdata[n] = -1.0
        # 音量を調整
        newdata[n] *= level
    return newdata

def save(data, fs, bit, filename):
    """波形データをWAVEファイルへ出力"""
    wf = wave.open(filename, "w")
    wf.setnchannels(1)
    wf.setsampwidth(bit / 8)
    wf.setframerate(fs)
    wf.writeframes(data)
    wf.close()

if __name__ == "__main__":
    # 音声をロード
    wf = wave.open("data/sine.wav")
    fs = wf.getframerate()
    length = wf.getnframes()
    data = wf.readframes(length)

    # デフォルトの音声を再生、ファイルにも保存
    play(data, fs, 16)
    save(data, fs, 16, "original.wav")

    # エフェクトをかけやすいようにバイナリデータを[-1, +1]に正規化
    data = frombuffer(data, dtype="int16") / 32768.0

    # オリジナル波形の一部をプロット
    subplot(211)
    plot(data[0:200])
    xlabel("time [sample]")
    ylabel("amplitude")
    ylim([-1.0, 1.0])

    # ここでサウンドエフェクト
    newdata = distortion(data, 200, 0.3)

    # サウンドエフェクトをかけた波形の一部をプロット
    subplot(212)
    plot(newdata[0:200])
    xlabel("time [sample]")
    ylabel("amplitude")
    ylim([-1.0, 1.0])

    # 正規化前のバイナリデータに戻す
    newdata = [int(x * 32767.0) for x in newdata]
    newdata = struct.pack("h" * len(newdata), *newdata)

    # サウンドエフェクトをかけた音声を再生
    play(newdata, fs, 16)
    save(newdata, fs, 16, "distortion.wav")

    show()

音の波をgain倍して [-1.0, 1.0] を超えた場合は、最大値の-1または+1にしてクリッピングしています。このままだと非常に大きな音になってしまうためlevelをかけて音量を下げています。ちなみに波形の上限・下限を飛びぬけてしまうことを業界用語で「サチる」と呼ぶようです。初めて聞いたときはどこかの方言かと思ってましたが、Saturation(飽和する)の略語とのこと。

(参考)「ネゴる」「サチる」ってどういう意味?

波形をクリッピングすると本来の音に含まれていない周波数成分が発生します。元のサイン波とディストーションをかけたサイン波でFFTを用いて周波数成分を比べてみました。

f:id:aidiary:20111015224257p:plain
f:id:aidiary:20111015224256p:plain

左がオリジナルの音声、右がディストーションをかけた音声のFFT結果です。ディストーションをかけると、倍音成分が発生することが確認できました。そういえば、ディストーションの結果と波形が似ている四角波にも倍音がありましたね(2011/6/11)。このようにオリジナルの音になかった周波数成分を発生させる処理を非線形処理と呼ぶとのこと。

サイン波だけではつまらないので単音のメロディにかけてみます。「ディストーション、オーヴァードライヴ攻略」から音声をお借りしました。このサイトには、ディストーションをかけた音声もあるので聴き比べてみます。できた波形は下のようになります。

f:id:aidiary:20111015224745p:plain

音声も作ってみました。上がオリジナルのメロディで下がディストーションをかけたメロディです。

おぉ、確かにエレキギターっぽい!でも上のアルゴリズムでディストーションをかけると少しリバーブ(2011/6/19)がかかったように聞こえるのはなぜなんだろう?元サイトのディストーションとも効果が違うみたいだ。ディストーションにもいろいろあるのかな?

参考

C言語ではじめる音のプログラミング―サウンドエフェクトの信号処理

C言語ではじめる音のプログラミング―サウンドエフェクトの信号処理