59
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonでのwavファイル操作

Last updated at Posted at 2017-04-18

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で扱えるデータに変換できました。

その他

感想

PySoundFileとwaveモジュールの比較になってしまいましたね。
いかにPySoundFileが楽ちんかは、
それぞれに費やした行数からお察しいただければと。

まぁあれだ。
24-bitハイレゾ音源はめんどくださいやつなんだ。
(とんだとばっちり)

  1. この記事を書いた当時はSciPy 0.18.1あたりを使っていて、そのドキュメントを参考に24 bit dataを読めないとしていました。v1.6.1のドキュメントを確認したところ、24-bit integer PCMも対応済みでした。もしかしたら結構前から対応してた・・・のかもしれません。

59
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?