Pythonでのwavファイル操作

Pythonでのwavファイルの扱い方をメモします。
ハイレゾ音源をwaveモジュールで扱う場合も記述しています。

## お手軽PySoundFileバージョン
とりあえず[PySoundFile](https://pypi.python.org/pypi/SoundFile/)使っとけばok。

使い方はこれだけ。

```python: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バージョン
王道ではありますが、データの扱いが若干面倒くさい。
(ハイレゾ音源だと超絶面倒くさい。)

```python: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を見てもらえば察しがつくかもしれませんが、バイナリで返ってきます。
これを解読しなくてはならないんです、ズボラな私にはちょっと・・・

と、言いつつ続けます。
(たぶん[こちら](http://nonbiri-tereka.hatenablog.com/entry/2014/06/24/110011)の方がシンプル。)

まず、wavファイルのフォーマット情報を取ってきます。

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

```python: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にしてから読み込む
この二つを全てのデータ点について繰り返すだけです。
なお、この部分は[こちら](http://qiita.com/wrist/items/4b404230e264c5cb571c)を参考にしました。

ハイレゾのデータは、例えばこちらとか[Steinway & Sons grand piano recording](http://www.lessloss.com/steinway-sons-grand-piano-recording-p-202.html?zenid=ktb9u74261vsm22ccr5srh5nh1)

```python: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')
```
最後にチャネルごとにデータを分割

```python:test2.py
if nchannles == 2:
    l_channel = data[::nchannles]
    r_channel = data[1::nchannles]
# nchannels > 2 の場合は割愛
```

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

## その他

* [scipy.io.wavfile.read](https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.wavfile.read.html) ~~(24-bit dataを扱えない)~~ <= 対応されてました[^1]

* [scikits.audiolab](https://pypi.python.org/pypi/scikits.audiolab/)(使ったことない)


[^1]: この記事を書いた当時は[SciPy 0.18.1](https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.io.wavfile.read.html#scipy.io.wavfile.read)あたりを使っていて、そのドキュメントを参考に24 bit dataを読めないとしていました。v1.6.1のドキュメントを確認したところ、`24-bit integer PCM`も対応済みでした。もしかしたら結構前から対応してた・・・のかもしれません。



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

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