Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Python resampyで音声・音楽ファイルをリサンプリング(アップ/ダウンサンプリング)

サンプリング周波数変換(リサンプリング,アップ/ダウンサンプリング)は,非常によく利用されるオーディオファイル処理です。

ただ、リサンプリングは時間がかかる処理なので、なるべく高速なアルゴリズム・ライブラリを使いたいところです。

特に大量のデータを扱っている時や、時間的に長いオーディオファイルを扱う時に重要になります。

また、アンチエイリアシングフィルタ(アップ/ダウンサンプリングでエイリアシングのために用いるローパスフィルタ)等によって音質に違いが出ます

今回紹介する Python 向けリサンプリング ライブラリ resampy は、オーディオ信号向けの効率的なリサンプリング処理ができます

https://resampy.readthedocs.io/en/master/

GitHub - bmcfee/resampy: Efficient sample rate conversion in python

なお、resampyリポジトリの所有者は音楽信号分析ライブラリ LibROSA のプライマリメンテナである bmcfee (Brian McFee)氏であり、resampyはLibROSAのオーディオファイルの読み込み関数librosa.load()でも利用されています。

Librosa

以下,この記事ではリサンプリング ライブラリresampyの使い方を説明します。

使い方

import resampy
y = resampy.resample(x, sr_orig, sr_mod)

sr_origに入力するNumpy配列xのサンプリング周波数、sr_modにサンプリング周波数変換後のサンプリング周波数を与えます。

入力信号がステレオ信号のような複数チャネルの信号の場合、入力となるNumpy配列の形状は(チャンネル, サンプル数)となります。

また、キーワード引数filternum_zeroを与えることで、アンチエイリアシングフィルタの形状などを変更することができます。

以下、ドキュメントのAdvanced filteringからの引用です。

# Or a shorter sinc-filter than the default (num_zeros=64)
y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window', num_zeros=32)

# Or use the pre-built high-quality filter
y = resampy.resample(x, sr_orig, sr_new, filter='kaiser_best')

# Or use the pre-built fast filter
y = resampy.resample(x, sr_orig, sr_new, filter='kaiser_fast')

ベンチマーク

以下の5パターンについて、処理時間と誤差(アップサンプリング後にダウンサンプリング)を計測してみました。

  • resampy.resample(x, sr_orig, sr_new)
  • resampy.resample(x, sr_orig, sr_new, filter='sinc_window', window=scipy.signal.hann)
  • resampy.resample(x, sr_orig, sr_new, filter='sinc_window', num_zeros=32)
  • resampy.resample(x, sr_orig, sr_new, filter='kaiser_best')
  • resampy.resample(x, sr_orig, sr_new, filter='kaiser_fast')

ベンチマークソースコード

seabornを使って少し綺麗なプロットにしてみました。

import numpy as np
import scipy.signal
import resampy
import timeit
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()


number = 5

sr_orig  = 44100
sr_new   = 48000
n_samples = sr_orig * 100 # 100秒
x = np.random.normal(0, 0.2, (n_samples))

t   = np.zeros(5)
err = np.zeros(5)
index = np.arange(1,5+1)


t[0]   = timeit.timeit(lambda: 
            resampy.resample(x, sr_orig, sr_new)
        , number=number)

t[1]   = timeit.timeit(lambda: 
            resampy.resample(x, sr_orig, sr_new, filter='sinc_window', window=scipy.signal.hann)
        , number=number)

t[2]   = timeit.timeit(lambda: 
            resampy.resample(x, sr_orig, sr_new, filter='sinc_window', num_zeros=32)
        , number=number)

t[3]   = timeit.timeit(lambda: 
            resampy.resample(x, sr_orig, sr_new, filter='kaiser_best')
        , number=number)

t[4]   = timeit.timeit(lambda: 
            resampy.resample(x, sr_orig, sr_new, filter='kaiser_fast')
        , number=number)


y = resampy.resample(x, sr_orig, sr_new)
z = resampy.resample(y, sr_new, sr_orig)
err[0] = np.sqrt(np.mean((z-x)**2))

y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window', window=scipy.signal.hann)
z = resampy.resample(y, sr_new, sr_orig, filter='sinc_window', window=scipy.signal.hann)
err[1] = np.sqrt(np.mean((z-x)**2))

y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window', num_zeros=32)
z = resampy.resample(y, sr_new, sr_orig, filter='sinc_window', num_zeros=32)
err[2] = np.sqrt(np.mean((z-x)**2))

y = resampy.resample(x, sr_orig, sr_new, filter='kaiser_best')
z = resampy.resample(y, sr_new, sr_orig, filter='kaiser_best')
err[3] = np.sqrt(np.mean((z-x)**2))

y = resampy.resample(x, sr_orig, sr_new, filter='kaiser_fast')
z = resampy.resample(y, sr_new, sr_orig, filter='kaiser_fast')
err[4] = np.sqrt(np.mean((z-x)**2))

print(t)
print(err)

plt.clf()
plt.bar(index, t, color="rgbym", alpha=0.5)
plt.ylabel("Time [sec]")
plt.show()
plt.clf()
plt.bar(index, err, color="rgbym", alpha=0.5)
plt.ylabel("Error")
plt.yscale("log")
plt.show()

まとめ

Python向けのリサンプリングのライブラリ resampy使い方の紹介と、窓関数(ローパスフィルタ)の種類による音質と計算時間の比較を行いました。

デフォルトの窓関数であるkaiser_best音質に優れていますが、計算に少し時間がかかってしまいます

一方で、kaiser_fast は音質は劣化が目立ちますが、高速に動作するというメリットがあります。

従って、kaiser_best人が聞く音信号にkaiser_fast分析用信号に対して適用するのがよいと思います。