※2018/10/19: Bloggerから移行しました
概要
Python(LibROSA)を用いた音響音楽信号処理として、クロスフェード自動生成アルゴリズムを設計します。
具体的には、複数の曲をフォルダに入れておけば、クロスフェード音源を自動で作ってくれるアルゴリズムを作りたいと思います。
LibROSAについて
音響音楽信号処理用のパッケージです。
メル周波数解析やMFCC、定Q変換によるクロマベクトル生成といった周波数領域の解析のほか、BPM・ビート検知、様々な楽曲特徴量を算出する関数が用意されています。
GitHub
Document
問題設定
問題設定として、すでに曲順が決まっている複数の楽曲が用意されているというタスクにします。また、曲順はファイル名で指定することとします。
例: 01.wav, 02.wav, ..., 09.wav
アルゴリズム設計と実装
必要な処理の列挙
- 各曲に対して、クロスフェード区間を切り出す
- 各曲のクロスフェード区間の先端・終端部に、それぞれフェードイン・フェードアウト処理を行う
- 各曲において、前後の楽曲とフェードイン・フェードアウト区間を繋げて、1つの音源として出力する
Pythonスクリプトの全体像
import sys, os import numpy as np import librosa sr = 44100 dirpath = sys.argv[1] duration = int(sys.argv[2] fadetime = int(sys.argv[3]) files = os.listdir(dirpath) files.sort() xfade = None for file in files: _, ext = os.path.splitext(file) if ext.lower() in ['.wav', '.mp3', '.flac']: y, sr = librosa.load(os.path.join(dirpath,file),\ sr=sr, mono=False) y = get_highlight(y, duration, sr) y = add_fade(y, fadetime, sr) xfade = gen_xfade(xfade, y, fadetime, sr) librosa.output.write_wav('_xfade.wav', xfade, sr)
ここで、srはサンプリング周波数を示し、各関数内で指定した秒数をサンプル数に変換するのに利用します。
注意点として、librosa.loadは引数monoがデフォルトではTrueになっています。この時、読みこんだ音源はモノラルになってしまうため、ステレオチャネルの音源を扱うときは必ずmono=Falseにしてください。
なお、ステレオチャネル信号をロードすると、Numpyのshapeとしては(チャンネル数、サンプル数) となります。
実行例: $> python autoxfade.py audio 10 3
audioディレクトリ以下にあるオーディオファイルを、クロスフェード区間10秒、フェードイン・フェードアウト区間それぞれ5秒としてクロスフェード音源を自動生成します。
get_highlight()
後日、音響特徴量や楽曲構造(サビ検出)を利用した区間抽出を解説する予定です。
def get_highlight(x, duration, sr): x = x.T length = x.shape[0] d_len = int(duration*sr) offset = np.random.randint(d_len,length-d_len) x_highlight = x[offset:offset+d_len].T return x_highlight
add_fade()
よく利用されているcosineを利用したパワー領域でのフェードイン・フェードアウトを行っています。
詳しくは、次回のクロスフェード関数デザインで解説します。
def add_fade(x, fadetime, sr): ft_len = int(fadetime*sr) r = np.arange(0, ft_len)*np.pi/ft_len w_fo = (0.5+0.5*np.cos(r))**0.5 w_fi = (0.5-0.5*np.cos(r))**0.5 if x.ndim == 1: x[0:ft_len] *= w_fi x[length-ft_len::] *= w_fo else: ch, length = x.shape for c in np.arange(0, ch): x[c,0:ft_len] *= w_fi x[c,length-ft_len::] *= w_fo return x
gen_xfade()
愚直に書いています。def gen_xfade(x_pre, x_next, fadetime, sr): ft_len = int(fadetime*sr) if x_pre is None: xfade = x_next else: if x_next.ndim == 1: x_pre_len = x_pre.shape x_next_len = x_next.shape x_pre_len -= ft_len x_next_len -= ft_len xfade = np.c_[x_pre, np.zeros(x_next_len)]\ + np.c_[np.zeros(x_pre_len),x_next] else: ch, x_pre_len = x_pre.shape ch, x_next_len = x_next.shape x_pre_len -= ft_len x_next_len -= ft_len xfade = np.c_[x_pre, np.zeros((ch, x_next_len))]\ + np.c_[np.zeros((ch, x_pre_len)),x_next] return xfade