回答編集履歴
5
日本語が下手すぎる点を訂正
test
CHANGED
@@ -6,13 +6,13 @@
|
|
6
6
|
|
7
7
|
- 最大振幅からいきなり振幅0にしても同じ
|
8
8
|
|
9
|
-
- 同時
|
10
|
-
|
11
|
-
同時
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
波形を直接生成するような場合、こうした音の特性に注意する必要があ
|
9
|
+
- 同時発音数が変化した際にampを突然変化させると波に連続性がなくなるため音割れする
|
10
|
+
|
11
|
+
同時発音数というのは和音を再生する際には結構変化するので、本式のシンセサイザー(あるいはミキサー)には波形を調整するコンプレッサーとかリミッターと呼ばれるものが備わっています。これはご質問のコードにあるように単に範囲外をclampするという方法では実現できません。そうやってしまうと波形がひずみ結局音割れします。
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
波形を直接生成するような場合、こうした音の特性に注意する必要があります。自然界の音は[エンベロープ](https://www.g200kg.com/jp/docs/dic/envelope.html)に従って振幅が徐々に変化するのが普通ですので、ソフトウェアで波形を生成する場合でも最小限のエンベロープ制御を行い波形が不連続にならないようにするとよいでしょう。
|
16
16
|
|
17
17
|
|
18
18
|
|
@@ -44,7 +44,7 @@
|
|
44
44
|
|
45
45
|
|
46
46
|
|
47
|
-
なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時
|
47
|
+
なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時発音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単でしょう。同時発音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間振幅を徐々に小さくしながら音が鳴り続けるため「音が完全に消えるまでには若干時間がかかる」からです。
|
48
48
|
|
49
49
|
|
50
50
|
|
@@ -54,7 +54,7 @@
|
|
54
54
|
|
55
55
|
|
56
56
|
|
57
|
-
下記コードでは0.4秒ごとにC4~B#4までの音を順に自動演奏していますが、その限りでは雑音はでないようでした。しかし・・・エンベロープを滑らかにしたつもりかつ同時
|
57
|
+
下記コードでは0.4秒ごとにC4~B#4までの音を順に自動演奏していますが、その限りでは雑音はでないようでした。しかし・・・エンベロープを滑らかにしたつもりかつ同時発音数10固定なんですが、自動演奏で和音にしたとたん演奏開始時点で雑音が・・・
|
58
58
|
|
59
59
|
|
60
60
|
|
4
誤記訂正・コードコメント訂正
test
CHANGED
@@ -44,7 +44,7 @@
|
|
44
44
|
|
45
45
|
|
46
46
|
|
47
|
-
なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間
|
47
|
+
なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間振幅を徐々に小さくしながら音が鳴り続けるため「音が完全に消えるまでには若干時間がかかる」からです。
|
48
48
|
|
49
49
|
|
50
50
|
|
@@ -138,7 +138,7 @@
|
|
138
138
|
|
139
139
|
# 以下のように、3度の和音にしたら同時発音数=10でも音割れした。(Win10 64bit)
|
140
140
|
|
141
|
-
#
|
141
|
+
# =>PyCharmのデバッグ実行では音割れしたが通常実行にすると音割れしなくなった
|
142
142
|
|
143
143
|
# ---------------------------------------------
|
144
144
|
|
3
追記2
test
CHANGED
@@ -62,6 +62,12 @@
|
|
62
62
|
|
63
63
|
|
64
64
|
|
65
|
+
追記2:PyCharm上で実行していたのですが、ふと思いついて「デバッグ実行」ではなく普通の「実行」にしたら和音にしても雑音でなくなりました!
|
66
|
+
|
67
|
+
想像ですが、PyCharmでデバッグ実行していたためインタープリタの実行スピードが音声再生においついてなかったのだと思います・・・
|
68
|
+
|
69
|
+
|
70
|
+
|
65
71
|
```Python
|
66
72
|
|
67
73
|
import math
|
2
コード追記
test
CHANGED
@@ -45,3 +45,355 @@
|
|
45
45
|
|
46
46
|
|
47
47
|
なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間神父が徐々に小さくなるため「音が完全に消えるまでには若干時間がかかる」からです。
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
追記:実際に単純なエンベロープにしてやってみた例を挙げてみます。少々長いのでちょっと気が引けますが。自分の環境ではMIDI-INがすぐに使えないので、Key_inputの部分は自動演奏に置き換えています。質問者さんの環境ではこの関数を書き替えずに実行すれば演奏の通りに音が鳴らせると思います。(すみませんが、自動演奏に変えたため元のコードからpygame.midiの部分のコードは削除してあります)
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
下記コードでは0.4秒ごとにC4~B#4までの音を順に自動演奏していますが、その限りでは雑音はでないようでした。しかし・・・エンベロープを滑らかにしたつもりかつ同時撥音数10固定なんですが、自動演奏で和音にしたとたん演奏開始時点で雑音が・・・
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
単なるバグなのかも知れませんし、Pythonのスピードとか自分の配慮が不足している点が何かあるのかも知れません・・・
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
```Python
|
66
|
+
|
67
|
+
import math
|
68
|
+
|
69
|
+
import pyaudio
|
70
|
+
|
71
|
+
import struct
|
72
|
+
|
73
|
+
import time
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
# PyAudio制御関連=============================
|
78
|
+
|
79
|
+
SAMPLE_RATE = 44100
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
if __name__ == '__main__':
|
84
|
+
|
85
|
+
p = pyaudio.PyAudio()
|
86
|
+
|
87
|
+
# ストリームを開く
|
88
|
+
|
89
|
+
stream = p.open(format=pyaudio.paInt16, channels=1, rate=SAMPLE_RATE, output=1, stream_callback=None)
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
def End(): # 終了処理(必ず入れる)
|
96
|
+
|
97
|
+
stream.close()
|
98
|
+
|
99
|
+
p.terminate()
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
# Key管理================================
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
key_list = [] # 押されてるキーを保持するリスト
|
110
|
+
|
111
|
+
start_time = time.time() # MIDI-INなしで自動演奏するための小細工
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
def Key_input():
|
118
|
+
|
119
|
+
# MIDI-INなしで自動演奏するための小細工
|
120
|
+
|
121
|
+
# MIDI-INが使えるならオリジナルのコードそのままでよいはず・・・
|
122
|
+
|
123
|
+
global key_list
|
124
|
+
|
125
|
+
t = (time.time() - start_time)
|
126
|
+
|
127
|
+
if 3 < t < 20:
|
128
|
+
|
129
|
+
key = 60 + int((t - 3) / 0.4) % 12
|
130
|
+
|
131
|
+
key_list = [[key, 110]]
|
132
|
+
|
133
|
+
# 以下のように、3度の和音にしたら同時発音数=10でも音割れした。(Win10 64bit)
|
134
|
+
|
135
|
+
# なぜそうなるか分かりませんでした... ><
|
136
|
+
|
137
|
+
# ---------------------------------------------
|
138
|
+
|
139
|
+
# key_list = [[key, 120], [key + 4, 100]]
|
140
|
+
|
141
|
+
else:
|
142
|
+
|
143
|
+
key_list = []
|
144
|
+
|
145
|
+
return
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
# 周波数とか作るところ=======================
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
class Oscillator:
|
158
|
+
|
159
|
+
def __init__(self, key, max_level, sustain_level, attack_time, decay_time, release_time):
|
160
|
+
|
161
|
+
self.key = key
|
162
|
+
|
163
|
+
freq = 440.0 * (2 ** ((key - 69) / 12))
|
164
|
+
|
165
|
+
self.dt = math.pi * 2 * freq / SAMPLE_RATE
|
166
|
+
|
167
|
+
self.max = max_level
|
168
|
+
|
169
|
+
self.sustain = sustain_level
|
170
|
+
|
171
|
+
self.attack_t = int(attack_time * SAMPLE_RATE)
|
172
|
+
|
173
|
+
self.decay_t = int(decay_time * SAMPLE_RATE)
|
174
|
+
|
175
|
+
self.release_t = int(release_time * SAMPLE_RATE)
|
176
|
+
|
177
|
+
self.released = None
|
178
|
+
|
179
|
+
self.time = 0
|
180
|
+
|
181
|
+
self.amp = 0
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
def calc_amp(self):
|
186
|
+
|
187
|
+
if self.released is None:
|
188
|
+
|
189
|
+
# まだNOTE_ON状態
|
190
|
+
|
191
|
+
if self.time < self.attack_t:
|
192
|
+
|
193
|
+
return Oscillator.interpolate(self.time, self.attack_t, 0.0, self.max)
|
194
|
+
|
195
|
+
else:
|
196
|
+
|
197
|
+
return Oscillator.interpolate(self.time - self.attack_t, self.decay_t, self.max, self.sustain)
|
198
|
+
|
199
|
+
else:
|
200
|
+
|
201
|
+
# NOTE_OFF状態
|
202
|
+
|
203
|
+
return Oscillator.interpolate(self.time - self.released, self.release_t, self.sustain, 0.0)
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
# 振幅の直線補間用
|
208
|
+
|
209
|
+
@staticmethod
|
210
|
+
|
211
|
+
def interpolate(cur_t, max_t, start_level, end_level):
|
212
|
+
|
213
|
+
t = cur_t / max_t if cur_t < max_t else 1
|
214
|
+
|
215
|
+
return start_level * (1 - t) + end_level * t
|
216
|
+
|
217
|
+
|
218
|
+
|
219
|
+
def wave(self):
|
220
|
+
|
221
|
+
self.amp = self.calc_amp()
|
222
|
+
|
223
|
+
w = self.amp * math.sin(self.time * self.dt)
|
224
|
+
|
225
|
+
self.time += 1
|
226
|
+
|
227
|
+
return w
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
def note_off(self):
|
232
|
+
|
233
|
+
if self.released is None:
|
234
|
+
|
235
|
+
self.sustain = self.amp # attack/decay中にNOTE_OFFされた場合への配慮
|
236
|
+
|
237
|
+
self.released = self.time
|
238
|
+
|
239
|
+
return True
|
240
|
+
|
241
|
+
else:
|
242
|
+
|
243
|
+
return False
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
def is_playing(self):
|
248
|
+
|
249
|
+
return self.released is None or self.time < self.released + self.release_t
|
250
|
+
|
251
|
+
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
class Synthesizer:
|
256
|
+
|
257
|
+
def __init__(self, polyphony=8):
|
258
|
+
|
259
|
+
self.oscillators = {}
|
260
|
+
|
261
|
+
self.polyphony = polyphony
|
262
|
+
|
263
|
+
buffer_size = 1048 // 2 # 44100 1sec 4410 0.1sec 441 0.01sec
|
264
|
+
|
265
|
+
latency = buffer_size / SAMPLE_RATE
|
266
|
+
|
267
|
+
self.wave = [0] * int(latency * SAMPLE_RATE)
|
268
|
+
|
269
|
+
self.pack_format = 'h' * len(self.wave)
|
270
|
+
|
271
|
+
print("polyphony=", self.polyphony, "latency=", latency)
|
272
|
+
|
273
|
+
|
274
|
+
|
275
|
+
def generate_wave(self, keys):
|
276
|
+
|
277
|
+
"""実際の波形を生成"""
|
278
|
+
|
279
|
+
self.update_oscillators(keys)
|
280
|
+
|
281
|
+
wave = self.wave
|
282
|
+
|
283
|
+
# print(keys)
|
284
|
+
|
285
|
+
amp = 32767 / self.polyphony # 同時発音数を固定とする
|
286
|
+
|
287
|
+
for i in range(len(self.wave)):
|
288
|
+
|
289
|
+
s = int(amp * sum(map(Oscillator.wave, self.oscillators.values())))
|
290
|
+
|
291
|
+
wave[i] = s # max(-32767, min(s, 32767)) # clampしない
|
292
|
+
|
293
|
+
wave = struct.pack(self.pack_format, *wave)
|
294
|
+
|
295
|
+
return wave
|
296
|
+
|
297
|
+
|
298
|
+
|
299
|
+
def update_oscillators(self, keys):
|
300
|
+
|
301
|
+
"""NOTE_ONのキー集合によりオシレーター群を更新する"""
|
302
|
+
|
303
|
+
|
304
|
+
|
305
|
+
# もはや発音しないオシレーターを削除
|
306
|
+
|
307
|
+
osc_exists = len(self.oscillators) > 0
|
308
|
+
|
309
|
+
self.oscillators = {osc.key: osc for osc in filter(Oscillator.is_playing, self.oscillators.values())}
|
310
|
+
|
311
|
+
if osc_exists and len(self.oscillators) == 0:
|
312
|
+
|
313
|
+
print("all oscillators removed")
|
314
|
+
|
315
|
+
|
316
|
+
|
317
|
+
# 新たにNOTE_ONとなったオシレーターを追加
|
318
|
+
|
319
|
+
new_keys = filter(lambda key: key not in self.oscillators, keys)
|
320
|
+
|
321
|
+
for key in new_keys:
|
322
|
+
|
323
|
+
max_level = keys[key] / 127
|
324
|
+
|
325
|
+
sustain_level = max_level * 0.9
|
326
|
+
|
327
|
+
attack_time = 0.3
|
328
|
+
|
329
|
+
decay_time = 0.1
|
330
|
+
|
331
|
+
release_time = 0.1
|
332
|
+
|
333
|
+
self.oscillators[key] = Oscillator(key, max_level, sustain_level, attack_time, decay_time, release_time)
|
334
|
+
|
335
|
+
print("note on", key)
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
# NOTE_OFFとなったオシレーターの状態を変更(リリース制御のため)
|
340
|
+
|
341
|
+
for osc_key in self.oscillators:
|
342
|
+
|
343
|
+
if osc_key not in keys:
|
344
|
+
|
345
|
+
if self.oscillators[osc_key].note_off():
|
346
|
+
|
347
|
+
print("note off", osc_key)
|
348
|
+
|
349
|
+
|
350
|
+
|
351
|
+
# 再生================================
|
352
|
+
|
353
|
+
|
354
|
+
|
355
|
+
|
356
|
+
|
357
|
+
def play(stream, data): # 再生用関数、ストリームと波形データを引数に
|
358
|
+
|
359
|
+
# チャンク単位でストリームに出力し音声を再生
|
360
|
+
|
361
|
+
chunk = 1048
|
362
|
+
|
363
|
+
sp = 0 # 再生位置ポインタ
|
364
|
+
|
365
|
+
buffer = data[sp:sp+chunk]
|
366
|
+
|
367
|
+
while buffer:
|
368
|
+
|
369
|
+
stream.write(buffer)
|
370
|
+
|
371
|
+
sp = sp + chunk
|
372
|
+
|
373
|
+
buffer = data[sp:sp+chunk]
|
374
|
+
|
375
|
+
|
376
|
+
|
377
|
+
# ==========================================
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
|
382
|
+
|
383
|
+
if __name__ == '__main__':
|
384
|
+
|
385
|
+
synthesiser = Synthesizer(10)
|
386
|
+
|
387
|
+
while True:
|
388
|
+
|
389
|
+
Key_input() # キーの取得
|
390
|
+
|
391
|
+
keys = {k[0]: k[1] for k in key_list}
|
392
|
+
|
393
|
+
wave = synthesiser.generate_wave(keys)
|
394
|
+
|
395
|
+
play(stream, wave) # 波形の再生
|
396
|
+
|
397
|
+
End() # このままでは決して呼び出されませんが...
|
398
|
+
|
399
|
+
```
|
1
文章不備訂正
test
CHANGED
@@ -12,11 +12,11 @@
|
|
12
12
|
|
13
13
|
|
14
14
|
|
15
|
-
こうした音の特性に注意する必要がある
|
15
|
+
波形を直接生成するような場合、こうした音の特性に注意する必要があると思います。自然界の音は[エンベロープ](https://www.g200kg.com/jp/docs/dic/envelope.html)に従って振幅が徐々に変化するのが普通です。MIDIのシンセサイザーにはピンキリがあると思いますが、どんな単純なものでも最小限のエンベロープ制御を行っており波形が不連続にならないように(音割れしないように)なっていると思います。
|
16
16
|
|
17
17
|
|
18
18
|
|
19
|
-
基本体なエンベロープ
|
19
|
+
なお、基本体なエンベロープ制御は以下のような感じに考えるとよいと思います。
|
20
20
|
|
21
21
|
|
22
22
|
|