傾いた本棚

研究,プログラミング,勉強,教育の話など

【Python 3】Waveファイル入出力のまとめ 【Scipy, PySoundFile, Wave】

はじめに

Python3のWaveファイルの入出力(read / write)には複数の方法があり,使用する際に毎回混乱するため,ライブラリごとの操作方法と,メリット・主な用途をまとめる.

対象

  • 研究・開発で音データを利用する方
  • Scipy, Waveなどのライブラリの違いや,データの型の違いに混乱している方・ライブラリごとの比較をしたい方
  • MATLABなど別のプログラミング言語で音データを扱っていたが,Pythonで試す・変更する可能性のある方
  • とりあえずコピペすれば動作するwaveファイル読み書きのPython3コードが欲しい方  

目次

音声ファイルの読み書き

以下のコードでは,読み込んだ音をそのままwav形式のファイルに書き込むサンプルコードを示す.*1 モノラル(Mono) 1channel の場合を説明する.

結論として,

  • Scipy: 科学計算ライブラリと併用する場合(MATLABに近い目的で利用する場合)
  • PySoundFile: とにかく急いでおり,ファイルのフォーマットや条件を気にしない場合
  • Wave: バイナリデータとしての扱い,ファイル情報の取得・変更など,高度な利用を目的とする場合

と使い分けるのがいいのではないかと思う.

Scipy

  • Scipy, Numpy などの科学計算ライブラリと併用する場合に便利
  • 音データそのもの,サンプリング周波数以外の情報は特に気にしない・取得する必要がない場合
  • 読み込み・書き込みの際,Numpyとの変換などをすべて自動でやってほしい場合
  • もともとMATLABを使っていた場合

ソースコード

# -*- coding: utf-8 -*-
from scipy.io.wavfile import read, write

readfilename = "./read-audio.wav"
writefilename = "./write-audio.wav"

fs, data = read(readfilename)
write(writefilename, rate=fs, data=data)

# data information
print("Sampling rate:", fs)
print("Frame num:", data.shape[0])
print("Sec:", data.shape[0] / fs)
print("Numpy dtype:", data.dtype)

出力結果例

1秒,サンプリング周波数 8000 Hz,量子化ビット数16bit (= 2byte) の場合の例を示す.

Sampling rate: 8000
Sec: 1.0
Numpy dtype: int16

ここで,Scipyの場合は,データ本体をNumpyの行列化したものと,サンプリング周波数のみを取得するため,もとのwave fileフォーマットや量子化ビット数などが知りたい場合はNumpyのdtypeから判断する必要がある.

Scipyでは,以下の通りに音声ファイルとNumpyのdtypeの変換が自動で行われる.

読み込み時

音ファイルの形式 変換後のndarray の型
32-bit floating-point float32
32-bit integer PCM int32
24-bit integer PCM int32
16-bit integer PCM int16
8-bit integer PCM uint8

書き込み時

変換後のndarray の型 音ファイルの形式
float32 32-bit floating-point
int32 32-bit integer PCM
int16 16-bit integer PCM
uint8 8-bit integer PCM

量子化ビット数が24bitのデータは32bitの型で扱い,24bit integer PCMとして書き込みはできないことを表している.

詳しい情報は,ドキュメントを参照. scipy.io.wavfile.read — SciPy v1.6.3 Reference Guide

scipy.io.wavfile.write — SciPy v1.6.3 Reference Guide

PySoundFile

  • 細かい条件は気にしない場合
  • ファイルフォーマットなどを気にせずとにかくデータの読み込み or 書き込みができれば十分な場合
  • 急いでいる方向け
import soundfile as sf

readfilename = "./read-audio.wav"
writefilename = "./write-audio.wav"

data, fs = sf.read(readfilename, dtype="int16")
sf.write(writefilename, data, fs)

# data information
print("Sampling rate:", fs)
print("Frame num:", data.shape[0])
print("Sec:", data.shape[0] / fs)
print("Numpy dtype:", data.dtype)


Scipyと同様に,1行ずつでデータの取得が可能. dtypeを指定しなければ,defaultでfloat64型として扱われる点がScipyとは異なる (Scipyでは,前述の通り自動でデータ型を判断して変換する).

出力結果例

Sampling rate: 8000
Frame num: 8000
Sec: 1.0
Numpy dtype: int16

詳しい個別の設定は,

SoundFile · PyPI

を参照.

Wave (組み込みモジュール)

  • バイナリデータとして音を取り出したい場合
  • ファイルの圧縮方法など,wavから得られる詳細情報を取得したい場合
  • 書き込むファイルに対して,手動で設定を行いたい場合

ソースコード

# -*- coding: utf-8 -*-
import wave
import numpy as np
from numpy import frombuffer

readfilename = "./read-audio.wav"
writefilename = "./write-audio.wav"

with wave.open(readfilename, "rb") as wr:

# data information
    params = wr.getparams()
    print("Prams:", params)
    ch_num, sampwidth, fr, frame_num, comptype, compname = params

    print("Sampling rate:", fr)
    print("Frame num:", frame_num)
    print("Sec:", frame_num / fr)
    print("Samplewidth:", sampwidth) 
    print("Channel num:", ch_num)

# read wavedata as binary format
    binary_data = wr.readframes(wr.getnframes())

# convert bibary to numpy array
    if sampwidth == 2:
        width_dtype = "int16"
    elif sampwidth == 4:
        width_dtype = "int32"

    num_data = frombuffer(binary_data, dtype = width_dtype)
    print(num_data)


# write wavefiles
with wave.open(writefilename, "wb") as ww:
    ww.setparams(params)

    #convert numpy arrays to binary format
    write_binary_data = num_data.tobytes()
    ww.writeframes(write_binary_data)

readframes, writeframes で読み書きするデータはすべてバイナリ文字列で扱う. バイナリ文字列として扱える,ファイルから取得できる・ファイルにセットできる情報量が多い代わりに,Numpy, Scipyなどのライブラリで扱いやすいように行列化する際には,やや手間がかかる.

以下にデータ変換に使った関数の説明を記述する.

変換前 変換後 適用関数・メソッド
wave のバイナリ文字列 Numpy のndarray Numpyのfrombuffer()関数
Numpy のndarray wave のバイナリ文字列 ndarray オブジェクトの tobytes() メソッド

また,書き込みの際,

    ww.setparams(params)

と読み込んだ際の情報をそのままセットしているが,もし入力したファイルとは異なる設定にしたい場合は,setnchannel(), setnframes() などの関数で個別に設定していくことになる.

詳しい個別の設定は, wave --- WAVファイルの読み書き — Python 3.8.10 ドキュメント を参照.

出力結果例

Prams: _wave_params(nchannels=1, sampwidth=2, framerate=8000, nframes=8000, comptype='NONE', compname='not compressed')
Sampling rate: 8000
Frame num: 8000
Sec: 1.0
Samplewidth: 2
Channel num: 1
[ 23171  24918  26514 ... -32768 -23166      2]

readするときのオブジェクト (wr) では get* 関数でファイル情報の取得が,writeするときのオブジェクト (ww) では,set* 関数でファイル情報の設定ができる.*以降の関数名はすべてget / set で対応している.

 

おわりに

ステレオの場合の例など,他にも書こうと思っていたことがあったが,思ったよりも長くなってしまったため,ここで一区切りとする. 音波形の正規化の方法,ステレオデータを利用する場合の違い,スペクトログラムのライブラリごとの作成方法の違いなども実験後・実験の間にまとめていく予定.  

 

参考にしたURL

scipy.io.wavfile.read — SciPy v1.6.3 Reference Guide

scipy.io.wavfile.write — SciPy v1.6.3 Reference Guide

wave --- WAVファイルの読み書き — Python 3.8.10 ドキュメント

SoundFile · PyPI

PythonでWAVファイルを読み込む - 音楽プログラミングの超入門(仮)

【python】波形の表示(モノラル・ステレオ)【サウンドプログラミング】 - すこしふしぎ.

Pythonでのwavファイル操作 - Qiita

 

*1:音データの加工をして保存したい場合は,読み込み・書き込みの間に処理を加えてください.