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

(1)に話が戻る感じかな。一応連続性があるんでシリーズの続きとして書くけれど、もはや wave モジュールはどーでもいいかったりもする。

なんであれ「データのバリエーション欲しさに自分で作れちゃうこと」って必要であろうよ、と。(1)の動機の半分はそれである。無論そこで書いたように、「PCM データって結局のところ何を表現しとんのよ」の理解から始めたかったわけで、なので音色もなんも頓着せずにただの正弦波だ、と。

けどさ、これ、簡単なはずよね。とはいえ ADSR envelope ジェネレータを自力で考えるのはさすがにキツい。そして探せば即見つかる。ADSR だけやってくれるライブラリ、ではないので、正直「Python の wave モジュール例の殴り書き」全部を台無しにしてしまいかねないもんではあったりする。けど今の興味は「これ、簡単なはずよね」な想像が正しいのかどうか、つまりアタシの理解が正確かどうかについてだけ。

audiolazy による ADSR envelope ジェネレータは想像通りとてつもなく簡単で、ライブラリをインストールせずにここだけパクって使ってしまえる。結局こんだけよな:

tiny_wave_wrapper については Python の wave モジュール例の殴り書き(5) を見てちょ
 1 import struct
 2 import math
 3 from tiny_wave_wrapper import WaveWriter
 4 
 5 _SCALES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
 6 _SCALES = {s: i for i, s in enumerate(_SCALES)}
 7 
 8 def _on2nnrange(on):
 9     """octave no to range of note numbers"""
10     s = (on + 2) * 12
11     return range(s, s + 12)
12 
13 def _freq(d):
14     """
15     note number to frequency.
16 
17     Converting from midi note number (d) to frequency (f) is given by the following formula:
18 
19     .. math::
20 
21         f = 2^{\frac{(d - 69)}{12}} \times 440 [Hz]
22 
23     See `MIDI tuning standard <https://en.wikipedia.org/wiki/MIDI_tuning_standard>`_.
24     """
25     return math.pow(2, (d - 69) / 12.) * 440
26 
27 
28 _RATE = 44100
29 _SAMPW = 2
30 
31 #
32 maxvol = 2**15 - 1.0  #maximum amplitude
33 
34 _score = [
35     [
36         # (duration, ((octave number 1, scale 1), ...))
37         (8, ((5, "C"), (5, "E"), (5, "G"))),  # C
38         (8, ((5, "D"), (5, "F"), (5, "A"))),  #
39         ],
40     [
41         # (duration, ((octave number 1, scale 1), ...))
42         (1, ((6, "C"),)),
43         (1, ((6, "E"),)),
44         (1, ((6, "G"),)),
45         (1, ((6, "E"),)),
46         (4, ((6, "C"),)),
47 
48         (1, ((6, "D"),)),
49         (1, ((6, "F"),)),
50         (1, ((6, "A"),)),
51         (1, ((6, "F"),)),
52         (4, ((6, "D"),)),
53         ]
54     ]
55 
56 def _onsc2f(on, sc):
57     return _freq(_on2nnrange(on)[_SCALES[sc]])
58 
59 def adsr(dur, a, d, s, r):
60     """
61     Linear ADSR envelope. (from `audiolazy <https://pypi.python.org/pypi/audiolazy>`_.)
62 
63       Duration, in number of samples, including the release time.
64       "Attack" time, in number of samples.
65       "Decay" time, in number of samples.
66       "Sustain" amplitude level (should be based on attack amplitude).
67       "Release" time, in number of samples.
68     """
69     m_a = 1. / a
70     m_d = (s - 1.) / d
71     m_r = - s * 1. / r
72     len_a = int(a + .5)
73     len_d = int(d + .5)
74     len_r = int(r + .5)
75     len_s = int(dur + .5) - len_a - len_d - len_r
76     for sample in xrange(len_a):
77         yield sample * m_a
78     for sample in xrange(len_d):
79         yield 1. + sample * m_d
80     for sample in xrange(len_s):
81         yield s
82     for sample in xrange(len_r):
83         yield s + sample * m_r
84 
85 envelope = list(adsr(_RATE, 100, 30000, maxvol, _RATE - 100 - 30000))
86 
87 _pcm = [[], []]
88 
89 for chn, scorech in enumerate(_score):
90     for dur, chord in scorech:
91         for i in range(0, dur * _RATE):
92             amp = envelope[min(i, _RATE - 1)] if chn == 1 else (maxvol / 2)
93             _pcm[chn].append(
94                 amp * sum(
95                     [math.sin(i * math.pi * 2 * _onsc2f(on, sc) / _RATE)
96                      for on, sc in chord]) / len(chord))
97 
98 with WaveWriter('test_CEG_with_adsr.wav', 2, _SAMPW, _RATE) as fo:
99     fo.writechannels(_pcm)

ふむ、思った通りだった。(正弦波、の振幅の「形」なわけよな、エンベロープ。)

だからどーした、ってネタだけれども。