音を上下させる(ピッチシフト)

Python

ここでは、音の高さ(ピッチ)を高くしたり、低くしたりする方法について学びます。
音の高さを自在に調節できるようになれば、非常に簡単なボイスチェンジャを作成することもできます。

また、本記事で掲載している音声ファイルは以下のコーパスに音声効果を加えたものです。コーパスのダウンロード等は以下のリンクから掲載元のHPを参照してください。

Shinnosuke Takamichi (高道 慎之介) - jvs_corpus
ダウンロード (download)

ピッチシフトの原理

ファイルに録音された音声のピッチ調整は非常に簡単に行うことができます。なぜなら、録音時とは異なるサンプリング周波数で再生・保存するだけで実装することができるからです。

例えば、サンプリング周波数が16000Hzで録音された音声ファイルを24000Hzで再生したとします。すると、1秒間に通常の1.5倍のデータがスピーカーから出力されることになります。それに対し、16000Hzで録音された音声ファイルを8000Hzで再生したとすると、同じデータ量をスピーカーが出力するのに2倍時間がかかります。

実際にサンプリング周波数を1倍、1.5倍、0.66倍したものを聴いてみましょう。

1倍(通常)

1.5倍(ピッチ上昇)

0.66倍(ピッチ下降)

つまり、再生時のサンプリング周波数を録音時よりも増やしたとき、出力される音声のピッチは上昇し、再生時のサンプリング周波数を録音時よりも減らしたとき、出力される音声のピッチは下降します。

ここで1つだけ問題なのが、音の高さ(ピッチ)を2倍、3倍と高くするにつれて、波形の再生時間が2分の1、3分の1と短くなります。逆に、音の高さを2分の1、3分の1と低くするにつれて、波形の再生時間は2倍、3倍と長くなります。
このように、高さ(ピッチ)の増減と共に、波形の再生時間も増減してしまうことです。

この問題を解決する方法は「音を伸縮する」で学んだ波形の早送り再生・スロー再生の手法です。これは音の高さを変えずに波形の長さだけを調節することができるため、ピッチを変更して長く(短く)なってしまった波形長を元の長さに戻すことができるのです。

音の高さ(ピッチ)変更後、波形長を元の長さに戻した音声を聴いてみましょう。

1.5倍(ピッチ上昇)

0.66倍(ピッチ下降)

Pythonでの実装

音の高さ(ピッチ)だけを変化させるためのプログラムを紹介します。

ピッチアップ

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.io import wavfile

# wavファイルのパス
INPUT_FILENAME = "input.wav"
OUTPUT_FILENAME = "output.wav"

# WAVファイルからデータを読み込む。
fs, data = wavfile.read(INPUT_FILENAME)

# スロー再生
## パラメータの設定
rate = 0.66 # 0.5 <= rate < 1の範囲で指定
p = int(fs * 0.01)
q = round((p*rate)/(1-rate))
window = np.linspace(0, 1, p)

## 水増し処理
data = data.astype("f8")
new_data = []
for i in range(len(data)//q):
    tmp = data[i*q: (i+1)*q]
    tmp_p = tmp[:p]
    tmp_q = tmp[p:]
    new_data += tmp_p.tolist()
    tmp_r = tmp_p*window + tmp_q[:p]*window[::-1]
    new_data += tmp_r.tolist()
    new_data += tmp_q.tolist()
new_data = np.array(new_data).astype("int16")


wavfile.write(OUTPUT_FILENAME, int(fs/rate), new_data)

ピッチダウン

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.io import wavfile

# wavファイルのパス
INPUT_FILENAME = "input.wav"
OUTPUT_FILENAME = "output.wav"

# WAVファイルからデータを読み込む。
fs, data = wavfile.read(INPUT_FILENAME)

# 早送り再生
## パラメータの設定
rate = 1.5
p = int(fs * 0.01)
q = round(p / (rate-1))
window = np.linspace(0, 1, p)

## 間引く処理
data = data.astype("f8")
new_data = []
for i in range(len(data)//(p+q)):
    tmp = data[i*(p+q): (i+1)*(p+q)]
    tmp_p = tmp[:p]
    tmp_q = tmp[p:]

    tmp_p = tmp_p * window[::-1]
    tmp_q[:p] *= window
    tmp_q[:p] += tmp_p
    new_data += tmp_q.tolist()
new_data = np.array(new_data).astype("int16")

wavfile.write(OUTPUT_FILENAME, int(fs/rate), new_data)

コメント