標準ライブラリなしでチャレンジ
Pythonではwaveが標準ライブラリに含まれていますが、今回はPythonでゼロから作ってみることにしました。その方がWAVEフォーマットがどのようなものなのかが分かるからです。
学習用に選んだ書籍は「サウンドプログラミング入門 〜音響合成の基本とC言語による実装〜」(著:青木直史 出版社:技術評論社)です。こちらの書籍はC言語で書かれております。サポートサイト(書籍内に書かれています)も設置されており、各章のプログラムもダウンロードできます。また先日、著者の方にメールしたところ、すぐに返信がありました。2013年発売にも関わらず、サポートもしっかりされていて勉強には最適な印象です。
この書籍を参考に、サウンドプログラミングの勉強をするにあたって、まず必要となるコードがwave.hに相当するものですが、今回は1ファイルで完結するようにしました。
from struct import pack
import math
RIFF_CHUNK_ID = 0x52494646 # 'RIFF'
FILE_FORMAT_TYPE = 0x57415645 # 'WAVE'
FMT_CHUNK_ID = 0x666D7420 # 'fmt '
DATA_CHUNK_ID = 0x64617461 # 'data'
class MonoPcm:
def __init__(self, fs, bits, length=None):
self.fs = fs
self.bits = bits
if length is None:
self.length = fs * 1
else:
self.length = length
self.s = [0] * self.length
def wave_write_8bit(self, filename):
riff_chunk_id = pack(">I", RIFF_CHUNK_ID)
riff_chunk_size = pack("<I", 36 + self.length)
file_format_type = pack(">I", FILE_FORMAT_TYPE)
fmt_chunk_id = pack(">I", FMT_CHUNK_ID)
fmt_chunk_size = pack("<I", 16)
wave_format_type = pack("<H", 1)
channel = pack("<H", 1)
samples_per_sec = pack("<I", self.fs)
bytes_per_sec = pack("<I", self.fs * self.bits // 8)
block_size = pack("<H", self.bits // 8)
bits_per_sample = pack("<H", self.bits)
data_chunk_id = pack(">I", DATA_CHUNK_ID)
data_chunk_size = pack("<I", self.length)
with open(filename, "wb") as f:
f.write(riff_chunk_id)
f.write(riff_chunk_size)
f.write(file_format_type)
f.write(fmt_chunk_id)
f.write(fmt_chunk_size)
f.write(wave_format_type)
f.write(channel)
f.write(samples_per_sec)
f.write(bytes_per_sec)
f.write(block_size)
f.write(bits_per_sample)
f.write(data_chunk_id)
f.write(data_chunk_size)
for n in range(0, self.length):
tmp = (self.s[n] + 1.0) / 2.0 * 256.0
if tmp > 255.0:
tmp = 255.0
elif tmp < 0.0:
tmp = 0.0
data = pack("<B", round(tmp + 0.5))
f.write(data)
if (self.length % 2) == 1:
data = pack("<B", 0)
f.write(data)
def wave_write_16bit(self, filename):
riff_chunk_id = pack(">I", RIFF_CHUNK_ID)
riff_chunk_size = pack("<I", 36 + self.length * 2)
file_format_type = pack(">I", FILE_FORMAT_TYPE)
fmt_chunk_id = pack(">I", FMT_CHUNK_ID)
fmt_chunk_size = pack("<I", 16)
wave_format_type = pack("<H", 1)
channel = pack("<H", 1)
samples_per_sec = pack("<I", self.fs)
bytes_per_sec = pack("<I", self.fs * self.bits // 8)
block_size = pack("<H", self.bits // 8)
bits_per_sample = pack("<H", self.bits)
data_chunk_id = pack(">I", DATA_CHUNK_ID)
data_chunk_size = pack("<I", self.length * 2)
with open(filename, "wb") as f:
f.write(riff_chunk_id)
f.write(riff_chunk_size)
f.write(file_format_type)
f.write(fmt_chunk_id)
f.write(fmt_chunk_size)
f.write(wave_format_type)
f.write(channel)
f.write(samples_per_sec)
f.write(bytes_per_sec)
f.write(block_size)
f.write(bits_per_sample)
f.write(data_chunk_id)
f.write(data_chunk_size)
for n in range(0, self.length):
tmp = (self.s[n] + 1.0) / 2.0 * 65536.0
if tmp > 65535.0:
tmp = 65535.0
elif tmp < 0.0:
tmp = 0.0
data = pack("<h", round(tmp + 0.5) - 32768)
f.write(data)
def sine_wave(pcm, a, f0):
for n in range(0, pcm.length):
pcm.s[n] = a * math.sin(2.0 * math.pi * f0 * n / pcm.fs)
def sine_wave_scale(pcm, f0, a, offset, duration):
dur = int(duration)
tmp = [0]*dur
for n in range(0, dur):
tmp[n] = a * math.sin(2.0 * math.pi * f0 * n / pcm.fs)
for n in range(0, int(pcm.fs * 0.01)):
tmp[n] *= n / int(pcm.fs * 0.01)
tmp[dur - n - 1] *= n / int(pcm.fs * 0.01)
for n in range(0, dur):
pcm.s[int(offset + n)] += tmp[n]
def saw_wave(pcm, f0):
for i in range(1, 45):
for n in range(0, pcm.length):
pcm.s[n] += 1.0 / i * math.sin(
2.0 * math.pi * i * f0 * n / pcm.fs
)
gain = 0.1
for n in range(0, pcm.length):
pcm.s[n] *= gain
def saw_wave_2(pcm, f0):
for i in range(1, 45):
for n in range(0, pcm.length):
pcm.s[n] += 1.0 / i * math.cos(
2.0 * math.pi * i * f0 * n / pcm.fs
)
gain = 0.1
for n in range(0, pcm.length):
pcm.s[n] *= gain
def square_wave(pcm, f0):
for i in range(1, 45, 2):
for n in range(0, pcm.length):
pcm.s[n] += 1.0 / i * math.sin(
2.0 * math.pi * i * f0 * n / pcm.fs
)
gain = 0.1
for n in range(0, pcm.length):
pcm.s[n] *= gain
def triangle_wave(pcm, f0):
for i in range(1, 45, 2):
for n in range(0, pcm.length):
pcm.s[n] += 1.0 / i / i * math.sin(
math.pi * i / 2.0) * math.sin(
2.0 * math.pi * i * f0 * n / pcm.fs)
gain = 0.1
for n in range(0, pcm.length):
pcm.s[n] *= gain
if __name__ == '__main__':
# 8bit sine wave
pcm_sine_8bit = MonoPcm(44100, 8)
sine_wave(pcm_sine_8bit, 0.1, 500.0)
pcm_sine_8bit.wave_write_8bit("sine8.wave")
# 16bit sine wave
pcm_sine_16bit = MonoPcm(44100, 16)
sine_wave(pcm_sine_16bit, 0.1, 500.0)
pcm_sine_16bit.wave_write_16bit("sine16.wave")
# 16bit saw wave
pcm_saw_16bit = MonoPcm(44100, 16)
saw_wave(pcm_saw_16bit, 500.0)
pcm_saw_16bit.wave_write_16bit("saw16.wave")
# 16bit saw wave 2
pcm_saw_2_16bit = MonoPcm(44100, 16)
saw_wave_2(pcm_saw_2_16bit, 500.0)
pcm_saw_2_16bit.wave_write_16bit("saw2_16.wave")
# 16bit sine scale
pcm = MonoPcm(44100, 16, 44100 * 2)
sine_wave_scale(pcm, 261.63, 0.1, pcm.fs * 0.00, pcm.fs * 0.25)
sine_wave_scale(pcm, 293.66, 0.1, pcm.fs * 0.25, pcm.fs * 0.25)
sine_wave_scale(pcm, 329.63, 0.1, pcm.fs * 0.50, pcm.fs * 0.25)
sine_wave_scale(pcm, 349.23, 0.1, pcm.fs * 0.75, pcm.fs * 0.25)
sine_wave_scale(pcm, 392.00, 0.1, pcm.fs * 1.00, pcm.fs * 0.25)
sine_wave_scale(pcm, 440.00, 0.1, pcm.fs * 1.25, pcm.fs * 0.25)
sine_wave_scale(pcm, 493.88, 0.1, pcm.fs * 1.50, pcm.fs * 0.25)
sine_wave_scale(pcm, 523.25, 0.1, pcm.fs * 1.75, pcm.fs * 0.25)
pcm.wave_write_16bit("sine_scale_16.wave")
# 16bit square wave
pcm_square = MonoPcm(44100, 16)
square_wave(pcm_square, 500.0)
pcm_square.wave_write_16bit("square.wave")
# 16bit triangle wave
pcm_tri = MonoPcm(44100, 16)
triangle_wave(pcm_tri, 500.0)
pcm_tri.wave_write_16bit("triangle.wave")
このコードでは標準ライブラリのstruct
使っていますので、ちょっと説明しようと思います。WAVEフォーマットでは〇〇をビッグorリトルエンディアンで〇バイトというような形でバイトコードを記述します。これは決まりごとなので、それに従わなくてはなりません。struct
を使うと、ビッグorリトルエンディアンの指定、バイト数の指定ができるようになります。例えばビッグエンディアンで符号なし整数4バイトの場合、>I
、リトルエンディアンで符号なし短整数2バイトの場合、<H
と指定します。
今回私はwaveファイルの読み込みは使わないので、wave_read_8bitおよびwave_read_16bit、StereoPcmは今後必要になったら追記しようと思います。
上記のプログラムを実行すると、7つのwaveファイルが作成されます。参考書籍の3章あたりまでに相当します。
実際に音が作れると、なんだかとても楽しい気分になります。ぜひお試しください。また、より詳しく勉強したい方はぜひ参考書籍「サウンドプログラミング入門」を読んでみてください。