まえがき
ボーカルや楽器の録音データに対して、
「音の長さを変えずに、音の高さを変えたい」
と思うことはありませんか?
この処理はピッチシフト*1と呼ばれていて、
- 移調/転調
- ハモり/コーラスパートの生成
- 音の高さの修正
Pythonの音楽分析モジュール LibROSA にはピッチシフトを簡単に実行できる関数が用意されているため、簡単に紹介したいと思います。
librosa.effects.pitch_shift() の使い方
引数について
librosa - librosa.effects.pitch_shift
librosa.effects.pitch_shift(y, sr, n_steps, bins_per_octave=12, res_type='kaiser_best', **kwargs)
重要な引数は以下の通りです。
y
:np.ndarray
型の、時間波形領域の入力信号です。ver. 0.8.0 時点では、1次元配列、すなわちモノラルの信号のみを対象としています。sr
: サンプリング周波数n_steps
: float型であり、どのくらい周波数をシフトさせるかを指定する引数です。bins_per_octave
: float型であり、オクターブのビン数(1オクターブ内に音程がいくつあるか)を指定します。
なお、中身はlibrosa.effects.pitch_shift()
の正体はフェーズボコーダであり、そのパラメタ(STFTフレーム長など)も引数として指定することができます。
フェーズボコーダにご興味がありましたら、以下の記事も合わせてご覧ください。
半音単位のピッチシフト
基本的な使い方として、まず半音単位のピッチシフトを説明します。
n_steps
の値を変更することで、音の高さをどのくらい上げ下げするのか制御します。
具体的には、このn_steps
によって、オクターブ(周波数比2:1)に対してどのくらいシフトさせるかを決めることになります。
デフォルト値のようにbins_per_octave=12
であれば、n_steps
が1増えるごとに、1半音上がるということになります。
例:
- 1オクターブ上:
n_steps
=12 - 1オクターブ下:
n_steps
=-12 - 完全5度上(半音7個分):
n_steps
=7 - 完全4度上(半音5個分):
n_steps
=5
このように、元の音源をN半音を上げ下げするような音波音単位のピッチシフトはn_steps
に整数値を設定すればOKです*2。
周波数を指定するピッチシフト
応用的な使い方として、周波数を指定したピッチシフトを説明します。
具体的には,「10Hzほど音の高さを上げたい」というような、基本周波数(音高)をどれくらいシフトするのかを指定するピッチシフトです。
ピッチシフト前と後の基本周波数をそれぞれFo, Fo_shift
とすると、以下の関係が成立します。
Fo_shift = Fo * 2.0 ** (n_steps/bins_per_octave)
(**はべき乗)
n_steps / bins_per_octave = log2 (Fo_shift / Fo)
簡単化のためbins_per_octave=1
とすると、
n_steps = log2 (Fo_shift / Fo)
となり、ピッチシフト前後の周波数比に対して2を底とする対数を取ることでn_steps
の値を求めることができます。
使い方のサンプル
プロット
コード
import librosa import numpy as np import matplotlib.pyplot as plt sr = 2048 #librosa.phase_vocoderのn_fft=2048 なので Fo = 400 eps = 1.0 x = np.arange(0, sr)/sr y = np.sin(2.0 * np.pi * Fo * x) n_fft = len(y) # 元波形 plt.plot(np.log(np.abs(np.fft.fft(y))**2+eps)[0:n_fft//2+1], label=f"Fo={Fo}") # 完全5度上 label = "Perfect 5th up" y_shift = librosa.effects.pitch_shift(y, sr, n_steps=7) plt.plot(np.log(np.abs(np.fft.fft(y_shift))**2+eps)[0:n_fft//2+1], label=label) # オクターブ下 label = "1 octave down" y_shift = librosa.effects.pitch_shift(y, sr, n_steps=-12) plt.plot(np.log(np.abs(np.fft.fft(y_shift))**2+eps)[0:n_fft//2+1], label=label, alpha=0.5) # ピッチシフト後の周波数指定 Fo_shift = 500 n_steps = np.log2(Fo_shift / Fo) label = f"Fo_shift={Fo_shift}" y_shift = librosa.effects.pitch_shift(y, sr, n_steps=n_steps, bins_per_octave=1) plt.plot(np.log(np.abs(np.fft.fft(y_shift))**2+eps)[0:n_fft//2+1], label=label) Fo_shift = 100 n_steps = np.log2(Fo_shift / Fo) label = f"Fo_shift={Fo_shift}" y_shift = librosa.effects.pitch_shift(y, sr, n_steps=n_steps, bins_per_octave=1) plt.plot(np.log(np.abs(np.fft.fft(y_shift))**2+eps)[0:n_fft//2+1], label=label) plt.xlabel("Frequency (Hz)") plt.ylabel("Log power") plt.legend(loc='upper right', borderaxespad=1, fontsize=12) plt.show()
※STFT窓長とプロットの都合上、サンプリング周波数を2048にしています。
注意
librosa.effects.pitch_shift()
で使われているフェーズボコーダlibrosa.phase_vocoder
はナイーブな実装であるため、特にピッチを上げるピッチシフトでは音質が劣化しやすい傾向があります。
従って、実用時には注意が必要です。
librosa.phase_vocoder
のドキュメントには、
RubberBand libraryのPythonラッパーであるpyrubberband
モジュールの利用が推奨されています。
補足:ピッチシフトはタイムストレッチ?
実は、librosa.effects.pitch_shift()のコードを見てみると、
def pitch_shift(y, sr, n_steps, bins_per_octave=12, res_type="kaiser_best", **kwargs): ... # Stretch in time, then resample y_shift = core.resample( time_stretch(y, rate, **kwargs), float(sr) / rate, sr, res_type=res_type ) ...
実は、ピッチシフト(カラオケの転調)はタイムストレッチ+リサンプリングで実現できることが分かります。
もしタイムストレッチの方にもご興味ありましたら、以下の記事も合わせてご覧ください。