Edited at

Pythonでのwavファイル操作

More than 1 year has passed since last update.

Pythonでのwavファイルの扱い方をメモします。

ハイレゾ音源をwaveモジュールで扱う場合も記述しています。


お手軽PySoundFileバージョン

とりあえずPySoundFile使っとけばok。

使い方はこれだけ。


test1.py

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バージョン

王道ではありますが、データの扱いが若干面倒くさい。

(ハイレゾ音源だと超絶面倒くさい。)


test2.py

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ファイルのフォーマット情報を取ってきます。


test2.py

# 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)ならすごく楽なんです。


test2.py

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リトルエンディアンの場合)

1. 3 bytesずつ読み込む

2. 読み込んだbit列の先頭に0を8bit分を埋めて32bitにしてから読み込む

この二つを全てのデータ点について繰り返すだけです。

なお、この部分はこちらを参考にしました。

ハイレゾのデータは、例えばこちらとかSteinway & Sons grand piano recording


test2.py

# 最上位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')


最後にチャネルごとにデータを分割


test2.py

if nchannles == 2:

l_channel = data[::nchannles]
r_channel = data[1::nchannles]
# nchannels > 2 の場合は割愛

これでようやくNumPyやSciPyで扱えるデータに変換できました。


その他

scipy.io.wavfile.read(24-bit dataを扱えない)

scikits.audiolab(使ったことない)


感想

PySoundFileとwaveモジュールの比較になってしまいましたね。

いかにPySoundFileが楽ちんかは、

それぞれに費やした行数からお察しいただければと。

まぁあれだ。

24-bitハイレゾ音源はめんどくださいやつなんだ。

(とんだとばっちり)