[備忘録]pythonのwaveモジュール

#wave
waveはpythonでwavファイルを扱うためのモジュールのこと．
wavファイルは，PCM(Pulse Code Modulation)音源と呼ばれ，
標本化した音声をそのまま保存した非圧縮デジタル音源．
圧縮しないからでかい．リファレンスは[こちら](https://docs.python.org/ja/3/library/wave.html)
##wavの読み込み: wave.open()
wave.open()でWave_readオブジェクトが返される．
wavファイルのサンプルはググれば，フリーでいろいろ出てくる．

```py:
import wave
import numpy as np
import matplotlib.pyplot as plt # 波形見るとき使う
import pyaudio # 鳴らすとき使う
import struct # バイナリデータを扱うとき使う

# 'rb'で読み込みモード
wf = wave.open('sample.wav', mode='rb')
print('type: ', type(wf))
```

```
type:  <class 'wave.Wave_read'>
```

##Wave_read オブジェクト
wave.open()で返されるオブジェクトのこと．こんなメソッドがある．

```py:
print('チャンネル数:', wf.getnchannels()) # モノラルなら1，ステレオなら2
print('サンプル幅:', wf.getsampwidth()) # バイト数 (1byte=8bit)
print('サンプリング周波数:', wf.getframerate()) # CDは44100Hz
print('フレーム数:', wf.getnframes()) # サンプリング周波数で割れば時間
print('パラメータ:', wf.getparams()) # 上記+αのパラメータをタプルで返す
```

```
チャンネル数: 1
サンプル幅: 2
サンプリング周波数: 44100
フレーム数: 10289
パラメータ: _wave_params(nchannels=1, sampwidth=2, framerate=44100, nframes=10289, comptype='NONE', compname='not compressed')
```

## 中身を見る

メソッドreadframes(n)でnフレーム分のデータを読み込む．
読み込んだデータは[バイナリ形式](https://docs.python.org/ja/3/library/stdtypes.html#bytes)である．

```py:
buf1 = wf.readframes(5) # 5フレーム読み込む(-1で全フレーム)
buf2 = wf.readframes(5) # 読み込むたびポインタ位置が更新
print(buf1)
print(buf2)
```

```
b'\x18\x00\x1b\x00\x1d\x00\x1d\x00\x1a\x00'
b'\x18\x00\x19\x00\x19\x00\x1e\x00\x1d\x00'
```

\x~~の~は16進数表記，つまり4bitのデータ．
上記例の場合，サンプル幅2byte，つまり16bitなので，\x~~\x~~が1サンプルを示す．
自力で10進数にしたい方は，符号付き16進数とかリトルエンディアンとかググるといい？．
自分が使ったサンプルをnumpy.frombuffer()で10進数にすると...

```py:
wf.rewind() # ポインタを先頭に戻す
buf = wf.readframes(-1) # 全部読み込む

# 2なら16bit，4なら32bitごとに10進数化
if wf.getsampwidth() == 2:
    data = np.frombuffer(buf, dtype='int16')
elif wf.getsampwidth() == 4:
    data = np.frombuffer(buf, dtype='int32')

# ステレオの場合，チャンネルを分離
if wf.getnchannels()==2:
    data_l = data[::2]
    data_r = data[1::2]
    plt.subplot(211)
    plt.plot(data_l)
    plt.subplot(212)
    plt.plot(data_r)
    plt.show()
else:
    plt.plot(data)
    plt.show()
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364756/31c9fe7c-d145-b414-d416-30dcd14121e0.png" width=50%>
波形として見れた！

##音を鳴らす
pyaudioを使った．pyaudioはバイナリ形式のデータを読み込んで鳴らすモジュールである．
下に出てくるチャンクは，音源から1回読み込むときのデータサイズのこと．
なぜ1024かはよく知らない．

```py:
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True) # このストリームに書き込むと音がなる

chunk = 1024 # チャンク単位でストリームに出力し音声を再生
wf.rewind() # ポインタを先頭にする
data = wf.readframes(chunk)
while data:
    stream.write(data)
    data = wf.readframes(chunk)
stream.close()
p.terminate()
```

##numpy配列を音として保存する
「ラ」の音である440Hzの正弦波を作成して，音として保存する．
numpy配列をwaveを使って保存するには，バイナリ形式に変換する必要がある．
バイナリ形式への変換には[struct](https://docs.python.org/ja/3/library/struct.html)モジュールを用いた．


```py:
f = 440 # 周波数
fs = 44100 # サンプリング周波数(CD)
sec = 3 # 時間

t = np.arange(0, fs * sec) # 時間軸の点をサンプル数用意
sine_wave = np.sin(2 * np.pi * f * t / fs)

max_num = 32767.0 / max(sine_wave) # int16は-32768~32767の範囲
wave16 = [int(x * max_num) for x in sine_wave] # 16bit符号付き整数に変換

# バイナリ化，'h'が2byte整数のフォーマット
bi_wave = struct.pack("h" * len(wave16), *wave16)

# サイン波をwavファイルとして書き出し
w = wave.Wave_write('sine440.wav')

# (チャンネル数(1:モノラル,2:ステレオ))
# サンプルサイズ(バイト)
# サンプリング周波数
# フレーム数
# 圧縮形式(今のところNONEのみ)
# 圧縮形式を人に判読可能な形にしたもの？通常、'NONE'に対して'not compressed'が返される)
p = (1, 2, fs, len(bi_wave), 'NONE', 'not compressed')
w.setparams(p)
w.writeframes(bi_wave)
w.close()
```