Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
41
Help us understand the problem. What are the problem?

posted at

updated at

Pythonでのwavファイル操作

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も対応済みでした。もしかしたら結構前から対応してた・・・のかもしれません。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
41
Help us understand the problem. What are the problem?