Python:リングモジュレーターの実装①



今回はリングモジュレータを実装していく。

リングモジュレータ

入力と関係ない音程を出すエフェクター。マルチ・エフェクターに組み込まれていることもある。かなり前衛的な演奏ができる。(weblio引用

音としてはこんな感じ

元の音

エフェクト音

理論としては入力の音に対してある周期波形を乗算してめちゃくちゃな音程にするエフェクトです。
変調波形を正弦波として表すとこのような感じ

  • out[ n] = in[ n] \times Asin2\pi f_{0} t
  • t = n / fs

A:正弦波の振幅
f0:正弦波の周波数[Hz]
n:その時のサンプル
fs:サンプリング周波数[Hz]

エフェクトの波形

f:id:gsmcustomeffects:20180801023157p:plain

  • 上段:入力音
  • 中段:変調波形(200Hzの正弦波)
  • 下段:出力音

かけている正弦波の影響を受けて波形が変形している。

ソースコード

処理の流れはこのようになる

  1. 元データがステレオなのでwavファイルをLRにわける
  2. モノラル化する(LRを半分にして加算)
  3. 正弦波を作る
  4. エフェクトをかける
  5. wavに格納しなおす
import wave
import audio_func as af
import scipy
import struct
import numpy as np
from pylab import *

wf = wave.open("GS04.wav", "r")#wav 扱うならお決まりのやつ
num_data = scipy.fromstring(wf.readframes(wf.getnframes()),dtype = "int16") / 32768.0#正規化

if(wf.getnchannels() == 2):
    left = num_data[::2]#1 スライス
    right= num_data[1::2]
    in_data = left*0.5 + right*0.5#2ステレオモノラル化
    #1:スライスの説明
    #a[1,2,3,4,5]ていうリストがあったとして
    #a[::2]  -> 1,3,5
    #a[1::2] -> 2,4

    #2:ステレオ->モノラル化
    #もともとギターの録音で左右に同じ音ふってるのでLR分解して半分にして足せば同じようなもん

fs = wf.getframerate()
AM_WAVE = []#振幅変調波形のメモリ確保
f0 = 200      #正弦波の周波数[Hz]

#フレームの長さ分だけ正弦波を作る
for n in np.arange(wf.getnframes()):
    sine = 0.2 * np.sin(2 * np.pi * f0 * n / fs)
    AM_WAVE.append(sine*(1/0.2))

#リングモジュレータなので振幅変調する
out_data = in_data * AM_WAVE
subplots_adjust(hspace=0.5)
subplot(311)
plot(in_data[0:6000],label="input")
legend()
subplot(312)
plot(AM_WAVE[0:6000],"red")
subplot(313)
plot(out_data[0:6000],label="output")
legend()
show()
#正規化したデータを元に戻す
out_data = [int(x * 32768.0) for x in out_data]
out_data = struct.pack("h" * len(out_data), *out_data)
#af.play(out_data,wf.getsampwidth(),1,wf.getframerate()) #再生
af.save(out_data, fs,1,"ring.wav")

audio_funcは一例です。(かなり雑な実装なため

import wave
import pyaudio
from pylab import *

def printWaveInfo(wf):
    """WAVEファイルの情報を取得"""
    print("チャンネル数 : "+ str(wf.getnchannels()))
    print("サンプル幅 : "+ str(wf.getsampwidth()))
    print("サンプルレート : "+ str(wf.getframerate()))
    print("フレーム数 : "+ str(wf.getnframes()))
    print("総パラメータ(一括表示用) : "+ str(wf.getparams()))
    print("再生時間 : "+ str(float(wf.getnframes()) / wf.getframerate()))

def play (data,sampleWidth,Channel,sampleRate):
    # ストリームを開く
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(sampleWidth),
                    channels=Channel,
                    rate=int(sampleRate),
                    output= True)
    # チャンク単位でストリームに出力し音声を再生
    chunk = 1024
    sp = 0  # 再生位置ポインタ
    buffer = data[sp:sp+chunk]
    while buffer != '':
        stream.write(buffer)
        sp = sp + chunk
        buffer = data[sp:sp+chunk]
    stream.close()
    p.terminate()


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