Pythonでのwavファイルの扱い方をメモします。
ハイレゾ音源をwaveモジュールで扱う場合も記述しています。
お手軽PySoundFileバージョン
とりあえずPySoundFile使っとけばok。
使い方はこれだけ。
import soundfile as sf
fname = '1980s-Casio-Celesta-C5.wav' # mono
# fname = 'Alesis-Fusion-Pizzicato-Strings-C4.wav' # stereo
data, samplerate = sf.read(fname)
sf.write('new_file.wav', data, samplerate)
print(data.shape)
# stereo音源なら
# l_channel = data[:,0]
# r_channel = data[:,1]
dataはwavのフォーマット(後述)によらずfloat64(デフォルト)で返ってきます。
組み込みモジュールwaveバージョン
王道ではありますが、データの扱いが若干面倒くさい。
(ハイレゾ音源だと超絶面倒くさい。)
import wave
fname = '1980s-Casio-Celesta-C5.wav' # mono
# fname = 'Alesis-Fusion-Pizzicato-Strings-C4.wav' # stereo
waveFile = wave.open(fname, 'r')
buf = waveFile.readframes(1024)
# buf = waveFile.readframes(-1) # 全て読み込む場合
waveFile.close()
print(buf)
このbufを見てもらえば察しがつくかもしれませんが、バイナリで返ってきます。
これを解読しなくてはならないんです、ズボラな私にはちょっと・・・
と、言いつつ続けます。
(たぶんこちらの方がシンプル。)
まず、wavファイルのフォーマット情報を取ってきます。
# wavファイルの情報を取得
# チャネル数:monoなら1, stereoなら2, 5.1chなら6(たぶん)
nchannles = waveFile.getnchannels()
# 音声データ1サンプルあたりのバイト数。2なら2bytes(16bit), 3なら24bitなど
samplewidth = waveFile.getsampwidth()
# サンプリング周波数。普通のCDなら44.1k
framerate = waveFile.getframerate()
# 音声のデータ点の数
nframes = waveFile.getnframes()
print("Channel num : ", nchannles)
print("Sample width : ", samplewidth)
print("Sampling rate : ", framerate)
print("Frame num : ", nframes)
私の手元にあったデータだとこの通り:
- Channel num : 1
- Sample width : 2
- Sampling rate : 44100
- Frame num : 106022
つまりこのwavファイルは:
- モノラル
- 1サンプルあたり2bytes(16bit)
- サンプリング周波数は44.1k[Hz]
- 106022点分のデータが入っている
ということになります。
これらを元にバイナリデータをほどいていくわけなんですが、sample widthが2か4(つまり16bitか32bit)ならすごく楽なんです。
import numpy as np
if samplewidth == 2:
data = np.frombuffer(buf, dtype='int16')
elif samplewidth == 4:
data = np.frombuffer(buf, dtype='int32')
問題はハイレゾ音源などで見られるsample widthが3(つまり24bit)の場合。これはもう地道にやっていくしかありません。
やることは、(24-bitリトルエンディアンの場合)
- 3 bytesずつ読み込む
- 読み込んだbit列の先頭に0を8bit分を埋めて32bitにしてから読み込む
この二つを全てのデータ点について繰り返すだけです。
なお、この部分はこちらを参考にしました。
ハイレゾのデータは、例えばこちらとかSteinway & Sons grand piano recording
# 最上位bitに0を詰めてint32としてunpackすることで
# 24bitの値を32bit intとして値を取り出す
# (<iはリトルエンディアンのint値を仮定)
# unpackはtupleを返すので[0]を取る
from struct import unpack
read_frames = len(data)
nbyte = samplewidth
data = [unpack("<i",
bytearray([0]) + buf[nbyte * idx:nbyte * (idx + 1)])[0]
for idx in range(read_frames)]
data = np.array(data, dtype='int16')
最後にチャネルごとにデータを分割
if nchannles == 2:
l_channel = data[::nchannles]
r_channel = data[1::nchannles]
# nchannels > 2 の場合は割愛
これでようやくNumPyやSciPyで扱えるデータに変換できました。
その他
-
scipy.io.wavfile.read
(24-bit dataを扱えない)<= 対応されてました1 -
scikits.audiolab(使ったことない)
感想
PySoundFileとwaveモジュールの比較になってしまいましたね。
いかにPySoundFileが楽ちんかは、
それぞれに費やした行数からお察しいただければと。
まぁあれだ。
24-bitハイレゾ音源はめんどくださいやつなんだ。
(とんだとばっちり)
-
この記事を書いた当時はSciPy 0.18.1あたりを使っていて、そのドキュメントを参考に24 bit dataを読めないとしていました。v1.6.1のドキュメントを確認したところ、
24-bit integer PCM
も対応済みでした。もしかしたら結構前から対応してた・・・のかもしれません。 ↩