音を伸縮する(タイムストレッチ)

Python

ここでは音声波形を引き伸ばしてスロー再生したり、縮めて早送り再生する方法について考えます。
このとき、なるべく音声の高さが変化しないように伸縮させようと思います。

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

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

音を縮める(早送り)

音声の高さを変えずに音声波形を縮めるには、音声を縮める分だけ一定の割合で波形を間引いていきます。

単純に波形を間引くだけだと、継ぎ接ぎした部分でノイズが生まれて不自然になるため、連続した音声フレームのボリュームを調整し合成する方法をとります。

  1. 音声波形を分割
    音声をフレームと呼ばれる短い間隔に分割します。
    波形が周期性を持つ場合はフレームはその周期を基準に分割し、人声のように波形が周期性を持たない場合は一般に人声の音素が約30ms以上であることから、それより短い長さで分割します。
  2. ボリュームを調整
    合成する2つの連続したフレームに対してはボリュームの調整を行います。前のフレームにはボリュームがだんだんと小さく(フェードアウト)なるように、後ろのフレームにはボリュームがだんだんと大きく(フェードイン)なるように処理を施します。
  3. 波形を合成
    ボリュームを調整した2つの連続したフレームを単純に加算します。
    波形が以降も続く場合は1~3の処理を繰り返します。

元音声

早送り再生

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.2
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")

fig, axs = plt.subplots(2, 1, figsize=(8, 5))
axs[0].plot(data)
axs[1].plot(new_data)

plt.tight_layout()
plt.show()

wavfile.write(OUTPUT_FILENAME, fs, new_data)

音を伸ばす(スロー再生)

音声波形を伸ばすには、音声を伸ばす分だけ一定の割合で波形を水増ししていきます。

先ほどと同様に継ぎ接ぎ部分でノイズが生まれて不自然にならないように、連続した音声フレームのボリュームを調整し合成する方法をとります。

元音声

スロー再生

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

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

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

# スロー再生
## パラメータの設定
rate = 0.85 # 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")

fig, axs = plt.subplots(2, 1, figsize=(8, 5))
axs[0].plot(data)
axs[1].plot(new_data)

plt.tight_layout()
plt.show()

wavfile.write(OUTPUT_FILENAME, fs, new_data)

コメント