LoginSignup
4
4

【Python】waveモジュールを用いたステレオwavファイルの作成と出力

Last updated at Posted at 2023-10-04

概要

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_stereotest_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() をわざわざ書かなくても、書き忘れても、勝手に閉じてくれるわけですね。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4