Python の wave モジュール例の殴り書き

なぜこれをしたくなったかは聞かないで。

和音ドミソの裏でメロディーがドミソ、な WAV を作る(ステレオ):

ワタシのサイトにしては珍しく Python 3.x でしか動かない。
 1 import struct
 2 import itertools
 3 import math
 4 import wave
 5 
 6 _SCALES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
 7 _SCALES = {s: i for i, s in enumerate(_SCALES)}
 8 
 9 def _on2nnrange(on):
10     """octave no to range of note numbers"""
11     s = (on + 2) * 12
12     return range(s, s + 12)
13 
14 def _freq(d):
15     """
16     note number to frequency.
17 
18     Converting from midi note number (d) to frequency (f) is given by the following formula:
19 
20     .. math::
21 
22         f = 2^{\frac{(d - 69)}{12}} \times 440 [Hz]
23 
24     See `MIDI tuning standard <https://en.wikipedia.org/wiki/MIDI_tuning_standard>`_.
25     """
26     return math.pow(2, (d - 69) / 12.) * 440
27 
28 
29 _RATE = 44100
30 _SAMPW = 2
31 
32 #
33 maxvol = 2**15 - 1.0  #maximum amplitude
34 
35 _score = [
36     [
37         # (duration, ((octave number 1, scale 1), ...))
38         (8, ((6, "C"), (5, "E"), (5, "G"))),  # C
39         (8, ((6, "D"), (5, "F"), (5, "A"))),  #
40         ],
41     [
42         # (duration, ((octave number 1, scale 1), ...))
43         (1, ((6, "C"),)),
44         (1, ((6, "E"),)),
45         (1, ((6, "G"),)),
46         (1, ((6, "E"),)),
47         (4, ((6, "C"),)),
48 
49         (1, ((6, "D"),)),
50         (1, ((6, "F"),)),
51         (1, ((6, "A"),)),
52         (1, ((6, "F"),)),
53         (4, ((6, "D"),)),
54         ]
55     ]
56 
57 _pcm = [[], []]
58 for chn, scorech in enumerate(_score):
59     for dur, chord in scorech:
60         for i in range(0, dur * _RATE):
61             _pcm[chn].append(
62                 maxvol * sum(
63                     [math.sin(i * _freq(_on2nnrange(on)[_SCALES[sc]]) / _RATE)
64                      for on, sc in chord]) / len(chord))
65 
66 pcm = list(map(int, itertools.chain.from_iterable(zip(*_pcm))))
67 
68 with wave.open('test_CEG.wav', 'w') as fo:
69     fo.setparams((len(_pcm), _SAMPW, _RATE, 0, 'NONE', 'not compressed'))
70     fo.writeframes(struct.pack('h' * len(pcm), *pcm))


一応言っておくけれど「格調拡張性高くない」んで、これをベースにごにょごにょしようとは思わないでおくれ。頑張れば MML 形式を読んで演奏する、なんつーことも目指せるはずだけれど、その元ネタには向かない。

「なぜこれをしたくなったか」は言うつもりは一切ないけれど、「何を知りたかったのか」についてだけは。

まず wave モジュールの例が案外見つからんてこととともに、「PCM データの実体ってなんなのよ」が色んなもん読んでもいまひとつ掴めず、それを理解すること、が目的だった。調べ方間違えるとどこ行ってもだいたい WAV ファイルのフォーマットについての情報が大量にヒットする。んなもんわかっとるし、wave モジュールが全部やってくれるし…。(無論バイナリデータのパッキングについては「データ構造」理解の一味なので、そこは知らないと最後の「struct.pack」の意味はわからないであろうけれども。)

わかんなかったこと、については自分はわかっちゃったので説明はしない。これは高校数学かなぁ、高校物理、か? そのレベルの理解で良いのだ、という確証をつかむまで時間かかっちゃった。

2017-07-03 22:10追記:
「自分はわかっちゃった」言うといて、わかったところを間違えてどーする、ってハナシ。

これも同じく殴り書き
 1 import struct
 2 import itertools
 3 import math
 4 import wave
 5 
 6 _SCALES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
 7 _SCALES = {s: i for i, s in enumerate(_SCALES)}
 8 
 9 def _on2nnrange(on):
10     """octave no to range of note numbers"""
11     s = (on + 2) * 12
12     return range(s, s + 12)
13 
14 def _freq(d):
15     """
16     note number to frequency.
17 
18     Converting from midi note number (d) to frequency (f) is given by the following formula:
19 
20     .. math::
21 
22         f = 2^{\frac{(d - 69)}{12}} \times 440 [Hz]
23 
24     See `MIDI tuning standard <https://en.wikipedia.org/wiki/MIDI_tuning_standard>`_.
25     """
26     return math.pow(2, (d - 69) / 12.) * 440
27 
28 
29 _RATE = 44100
30 _SAMPW = 2
31 
32 #
33 maxvol = 2**15 - 1.0  #maximum amplitude
34 
35 _score = [
36     [
37         # (duration, ((octave number 1, scale 1), ...))
38         (8, ((5, "C"), (5, "E"), (5, "G"))),  # C
39         (8, ((5, "D"), (5, "F"), (5, "A"))),  #
40         ],
41     [
42         # (duration, ((octave number 1, scale 1), ...))
43         (1, ((6, "C"),)),
44         (1, ((6, "E"),)),
45         (1, ((6, "G"),)),
46         (1, ((6, "E"),)),
47         (4, ((6, "C"),)),
48 
49         (1, ((6, "D"),)),
50         (1, ((6, "F"),)),
51         (1, ((6, "A"),)),
52         (1, ((6, "F"),)),
53         (4, ((6, "D"),)),
54         ]
55     ]
56 
57 def _onsc2f(on, sc):
58     return _freq(_on2nnrange(on)[_SCALES[sc]])
59 
60 _pcm = [[], []]
61 
62 for chn, scorech in enumerate(_score):
63     for dur, chord in scorech:
64         print([
65                 int(_freq(_on2nnrange(on)[_SCALES[sc]]))
66                 for on, sc in chord])
67         for i in range(0, dur * _RATE):
68             _pcm[chn].append(
69                 maxvol * sum(
70                     [math.sin(i * math.pi * 2 * _onsc2f(on, sc) / _RATE)
71                      for on, sc in chord]) / len(chord))
72 
73 pcm = list(map(int, itertools.chain.from_iterable(zip(*_pcm))))
74 
75 with wave.open('test_CEG.wav', 'w') as fo:
76     fo.setparams((2, _SAMPW, _RATE, 0, 'NONE', 'not compressed'))
77     fo.writeframes(struct.pack('h' * len(pcm), *pcm))

何が変わったかは…読めばわかるよね、と信じておく。