前置き
音楽プレイヤーのような音楽ソフトウェアの開発では、まずオーディオファイルから時間信号を読み込む処理が第一歩となります。
オーディオファイルは数多くの種類のフォーマットがあり、音楽ソフトウェアにおけるユーザの利便性を考えると、なるべく多くのフォーマットに対応することが望ましいです。しかしながら、それら全てのフォーマットのデコード処理をプログラミングするのは困難です。そのため、なるべく誰かが開発した外部ライブラリを利用することになります。
Python においては、例えば ``Pysoundfile```モジュールなどを使うことで、数行でオーディオファイルを読み込むことができます。
自分だけが使用するソフトウェアや、スクリプトとしての利用・配布であればこのようなモジュールを単純に採用すればよいのですが、実行ファイルで配布するようなソフトウェア開発の場合はライセンスに注意が必要です。
Pysoundfile
自体はMITライセンスですが、そのコアであるlibsndfile
というライブラリはLGPLライセンスです。
従って、PyInstaller
などで一つの実行ファイルにまとめるのはライセンス的には厳しいと考えられます。
また、PyInstaller
などを使った時に、非標準のライブラリを組み込むため実行ファイル(群)の総ファイルサイズが大きくなってしまうことも懸念されます。
Python wave モジュール
ところでPythonには waveモジュールという標準モジュールが用意されています。
標準モジュールであるため、ライセンスの問題を回避でき、また、実行ファイルサイズを増やさないソフトウェア設計が可能です。
そこで、今回は wave
モジュールを使って様々な形式のwavファイルを読み込んでみました。
実装/テスト
unsign 8-bit, signed 16-bit, signed 32-bitについては、numpy.frombuffer()
で素直に読み込んでいます。
signed 24-bit については、3バイトなので読み込み方法に悩みますが、以下の記事のように下位ビットを8ビット分ゼロ詰めして、4バイト (signed 32-bit) として読み込むことができます。
上位8ビットでは符号に影響が出るため下位8ビットであることがポイントです。
最初に掲載してある図が検証結果です。L/Rの信号が意図通り分離されており、また、[-1.0, 1.0) に正規化されていることを検証しました。
import wave import numpy as np from struct import unpack import matplotlib.pyplot as plt def load_wav(wavfilepath): # wavファイルを読み込む wf = wave.open(wavfilepath, "r") # wavファイルのパラメタを取得 sr = wf.getframerate() # サンプリング周波数 n_ch = wf.getnchannels() # チャンネル数 n_frames = wf.getnframes() # オーディオフレーム数 n_bytes = wf.getsampwidth() # 1サンプル数あたりのバイト数 # 信号データを取得 audiobuffer = wf.readframes(n_frames) wf.close() # 1サンプルあたりのバイト数に応じて信号を読込 if n_bytes == 1: # 8-bit uint norm = 2**7 data = np.frombuffer(audiobuffer, dtype=np.uint8).astype(np.float32) data = data.copy() - norm elif n_bytes == 2: # 16-bit int data = np.frombuffer(audiobuffer, dtype=np.int16).astype(np.float32) norm = 2**15 elif n_bytes == 3: # 24-bit int data = [unpack("<i", bytearray([0]) + audiobuffer[n_bytes * k:n_bytes * (k+1)])[0] for k in range(n_frames)] data = np.array(data, dtype='int32') norm = 2**31 elif n_bytes == 4: # 32-bit int data = np.frombuffer(audiobuffer, dtype=np.int32) norm = 2**31 # 各チャネルの信号に分離 n_samples = data.shape[0] // n_ch x = np.zeros((n_ch, n_samples), dtype=np.float32) for k in range(n_ch): x[k] = data[k::n_ch] # [-1, 1) に正規化 x /= norm print(np.min(x), np.max(x)) return x, sr if __name__ == "__main__": filepath_list = ["sinu8int.wav", "sin16int.wav", "sin24int.wav", "sin32int.wav"] for k, filepath in enumerate(filepath_list): x, sr = load_wav(filepath) x = x[:,0:1000] plt.subplot(len(filepath_list), 1, k+1) plt.plot(x[0], c="c", label="Right") plt.plot(x[1], c="m", label="Left") plt.ylim(-1.0, 1.0) plt.title(filepath) plt.legend() plt.tight_layout() plt.show()
最後に
Pythonにおいてwave
モジュールを使うことで、16-bitだけでなく、より高/低解像度なwavファイルの読み込みもできることを示しました。
wavio
というライブラリもあるのですが、実行ファイル化を目指すPythonソフトウェア開発の場合は、これくらいは自分で書いてしまったほうが使い勝手が良いと思います。
なお、wav以外のオーディオファイルフォーマットの対応に関しては、ffmpegなどの開発中のソフトウェアとは切り離された外部アプリケーション/ライブラリを利用してwavファイルを作成し、wave
モジュールで読み込むような設計が考えられます。