せっかくwavファイルの読み書きができるようになったので、Pythonで音声処理のマネゴトをいろいろやってみようと思います。
まずは再生速度の変更です。ただし、最近の動画サイトやDVDデッキでやるような『音の高さを変えずに早くしたり遅くしたり』ではなく、一昔前のテープやレコードなどのアナログメディアの『回転数を変える』ような変換から試してみます。
再生速度を変える
再生速度を変えると、波形はどうなるか
今回考える『再生速度を変える』ことは、波形を時間方向に伸び縮みさせるのと同義です。再生速度をn倍にすると波形は時間方向に1/nに圧縮され、再生速度を1/nにすると波形は時間方向にn倍になります。
たとえば、元の波形が440Hzの正弦波(1波長分)だったとすると、
再生速度を2倍にすると、波形が半分に圧縮され、周波数は2倍・再生にかかる時間は半分になります。
再生速度を半分にすると、波形が2倍に伸張され、周波数は半分・再生にかかる時間は2倍になります。
サンプリングデータなので…
ところで、wavファイルの場合、音声信号は連続した波形の形で格納されているわけではなく、一定間隔でその瞬間ごとの信号の高さをサンプリングした値を並べた形になっています。その間の値はデータとしては存在しません。
再生速度を変更する場合、同じ周波数でサンプリングしたデータを作ろうとすると、再生速度を整数倍にする場合以外は元のデータが存在しない瞬間の値が必要になります。例えば下図のようにもとの半分の速度で再生する場合、サンプリング周波数を同じにすると2倍のサンプリングデータが必要になります。
このため、元データから連続した波形を復元し、あらためてサンプリングしなおす必要があります。これを再サンプリングと言います(赤の棒の部分が足りないサンプリングデータ)。
再生速度を速くする場合は必要なサンプリングデータの総数は減りますが、ちょうどサンプリングするタイミングのデータが存在するとは限らないため、再生速度がちょうど『整数分の1』の場合を除き、再サンプリングが必要になります。
実験のためのプログラム
では、実際にやってみましょう。
ソースコード
import numpy
import wavfile
from scipy import interpolate
# 再生速度を通常の何倍にするか
speed = 0.5
# 元音声の読み込み(16ビットモノラル限定)
f = wavfile.readfile("input.wav") # 元のwavファイルの名前をここで指定する
src = f["ldata"]
# 変換後のデータを格納する配列を用意
count = int((len(src)-1)/speed)
dst = numpy.empty(count, dtype="int16")
# 補間関数を求める
f = interpolate.interp1d(range(len(src)), src, kind="cubic") # 補間関数の求め方を替える場合はこの行を書き換える
# 再生速度変換
for i in range(0,count):
dst[i] = f(i*speed)
wavfile.writefile(numpy.array([dst]), "output.wav") # 変換後のファイルの名前をここで指定する
コード解説
冒頭部分
import numpy
import wavfile
from scipy import interpolate
配列処理のために numpy、元の波形を復元する補間関数を求めるために scipy、wavファイルの読み書きのために過去記事で作成した wavfile をインポートしています。
再生速度の指定
# 再生速度を通常の何倍にするか
speed = 0.5
再生速度を元の何倍にするかを任意の実数で指定します。
wavファイルの読み込み
# 元音声の読み込み(16ビットモノラル限定)
f = wavfile.readfile("input.wav") # 元のwavファイルの名前をここで指定する
src = f["ldata"]
wavファイルを読み込みます。wavfile.readfileはステレオのファイルでもモノラルのファイルでも読み込めますが、その後の処理でldataしか参照していないので、処理全体としてはモノラル専用です。
元になる音声データのファイル名を変更したい場合は、input.wav の部分を書き換えて下さい。
変換後のデータを入れる配列を用意
# 変換後のデータを格納する配列を用意
count = int((len(src)-1)/speed)
dst = numpy.empty(count, dtype="int16")
速度を speed 倍にすると再生時間(≒データ数)は 1/speed 倍になるのですが、端数が出たときにエラーが出てしまうので1減らしています。
補間関数を求める
# 補間関数を求める
f = interpolate.interp1d(range(len(src)), src, kind="cubic") # 補間関数の求め方を替える場合はこの行を書き換える
この部分が今回のキモその1です。補間関数(与えられたサンプリングデータから元の波形を表す関数)を求めます。これは『真の波形』と完全一致するわけではありませんが、できるだけ『それっぽい』は系を復元できるようにいろいろなアルゴリズムが用意されています。
補間関数を求めるには scipy.interpolate.interp1d() メソッドを使用します。このメソッド1つでいろいろな補間関数を求められて便利です。基本的な使い方は以下の通りです。
interp1d(x, y, kind=“linear”)
引数
x
実数の1次元の配列(リストやnumpy.arrayなど)で、横軸方向のデータ列を表します。
y
実数の1次元の配列(リストやnumpy.arrayなど)で、縦軸方向のデータ列を表します。
xとyは同じ個数の要素がなければなりません。
kind
補間の方法を表す文字列を与えます。最近傍点補間なら”nearest”、直線補間なら”lineaer”、3次スプライン補間なら”cubic”となります。省略可能で、デフォルト値は”linear” です。
返却値
このメソッドは補間関数を返します。補間関数は引数として与えたxの最小値と最大値の間でしか定義されず、その範囲外の値を与えると ValueError が発生します。
wavファイルから読み込んだサンプリングデータにはx軸方向のデータがないので、range()メソッドでデータ数分の整数の数列を生成して使用しています。
補間関数は、ここでは3次スプライン補間を用いることとし、 kind=”cubic” を指定しています。
速度の変換
# 再生速度変換
for i in range(0,count):
dst[i] = f(i*speed)
補間関数を使って、対応する点の値を求めて配列に格納していきます。
再生速度を元の speed 倍とすると、変換後の配列の n番目の要素には元の波形の n×speed の位置の値が入ると考えます。
結果をファイルへ出力
wavfile.writefile(numpy.array([dst]), "output.wav") # 変換後のファイルの名前をここで指定する
変換後の配列をwavファイルに出力します。出力ファイル名を替えたい場合は output.wav の部分を書き換えて下さい。
では実験しましょう
元データ
今回、実験に使った音声はこちらです。
単語のチョイスについては深くはツッコまないで下さい(苦笑
※この音声は『VOICEVOX』というソフトウェアを使用して作成しました。サイトはこちら
※音声ライブラリは『VOICEVOX:四国めたん』を使用しています。
実行結果
0.5倍
0.75倍
1.5倍
2倍
というわけで、ちゃんとテープの倍速orスロー再生っぽい音声になりました。
コメント