概要
Python でステレオwavファイルを作成し、出力する方法をメモしておきます。
今回は、
- サンプリング周波数: 44100 Hz
- ビット深度: 16bit
- チャンネル数: 2 (ステレオなので当然)
のデータを作成します。
ファイルの作成
例として、左からAのサイン波、右から2オクターブ下のAの矩形波が聞こえるようなデータを作成します。
インポート
import numpy as np
import wave
データ作成にあたり、便利な関数を定義
# sec を時間軸に変換する関数
def frac_t(sec, samplerate):
return np.arange(int(sec * samplerate)) / samplerate
# サイン波を定義 (A:振幅, f:周波数, t:時間)
def sin_wave(A, f, t):
return A * np.sin(2 * np.pi * f * t)
# 矩形波を生成
def sq_wave(A, f, t):
return A * np.sign(sin_wave(A, f, t))
# のこぎり波を生成 (今回使いません。オマケです)
def saw_wave(A, f, t):
return 2 * A * (f * t - np.floor(f * t) - 1 / 2)
設定
# 設定
fs = 44100 # サンプリング周波数 [Hz]
sec = 1 # 信号を生成する秒数 [s]
A = 0.5 # 振幅
f = 440 # 信号の周波数 [Hz]
t = frac_t(sec, fs) # 秒数を時間軸に変換
データの作成
まず、440Hzのサイン波を data_L
に、 110Hz (440Hz の 2オクターブ下)の矩形波を data_R
に格納します。
# Left/Right Channel のデータを生成
data_L = sin_wave(A, f, t) # 440Hz のサイン波
data_R = sq_wave(A / 10, f / 4, t) # 110Hz の矩形波
ステレオデータは、Left/Rightのデータを交互に格納する必要があるので、そのようなデータを data_stereo
に格納します。また、扱いやすいよう、そのリスト型を np.array 型に変換しておきます。
# ステレオデータを格納するリスト
data_stereo = []
# ステレオデータは [LRLRLRLRLR...] の形式にしなきゃいけない
for i in range(len(data_L)):
data_stereo.append(data_L[i])
data_stereo.append(data_R[i])
# リストを np.array に変換
data_stereo = np.array(data_stereo)
これでステレオのデータを格納することができました。あとは、これを 16bit のデータに変換します。
# 16bit のデータとして書き込むためにスケールを調整 (※1)
data_stereo = data_stereo * np.iinfo(np.int16).max # (データ)×(16bitで表現できる最大値)
# # (※1) にて音割れする場合… 代わりにこれを用いる
# data_stereo = data_stereo / max(data_stereo) * 0.7 * np.iinfo(np.int16).max # (データ)/(データの最大値)×0.7×(16bitで表現できる最大値)
# 16bit のデータに変換
data_stereo = data_stereo.astype(np.int16)
補足
ここで、(※1) で音割れする場合は、対症療法的ですが
$$
\frac{y_i}{\max(y_i)}\times (割合)
$$
として音割れを防ぎました。
また、ここで割合は -3dB として、
$$
(-3 \,[\mathrm{dB}] の減衰比) \simeq \frac{1}{\sqrt{2}} \simeq 0.7
$$
をとりました。(-3dB は音楽業界でマスタリング前の2MIXデータの上限値の目安として選ばれることの多い値です。(ほんとか???))
データをファイルに出力
これで下準備は完了したので、最後に data_stereo
を test_01.wav
というファイル名で出力するだけです。
wave モジュールでは出力する際に、チャンネル数やサンプル幅などのメタデータを設定する必要があるため、それも行っています。
# データをファイルとして出力
output_file = "./wav/test_01.wav"
with wave.open(output_file, "w") as wf:
wf.setnchannels(2) # チャンネル数の設定 (1:mono, 2:stereo)
wf.setsampwidth(2) # サンプル幅の設定 (2bytes = 16bit)
wf.setframerate(fs) # サンプリングレートの設定
wf.writeframes(data_stereo) # ステレオデータを書き込み
これで出力が完了しました!
余談
with
構文を使わずにコードを書こうとすると、
wf = wave.open(outputfile, "w")
wf.setnchannels(2) # チャンネル数の設定 (1:mono, 2:stereo)
wf.setsampwidth(2) # サンプル幅の設定 (2bytes = 16bit)
wf.setframerate(fs) # サンプリングレートの設定
wf.writeframes(data_stereo) # ステレオデータを書き込み
wf.close() # close
と少し冗長で見づらいコードになります。(なるほど~これが with
構文のメリットかぁ)
wf.close()
をわざわざ書かなくても、書き忘れても、勝手に閉じてくれるわけですね。