teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

5

日本語が下手すぎる点を訂正

2018/02/16 10:54

投稿

KSwordOfHaste
KSwordOfHaste

スコア18404

answer CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  - 無音からいきなり最大振幅にすると(多分アンプの特性によって?)音割れする
4
4
  - 最大振幅からいきなり振幅0にしても同じ
5
- - 同時音数が変化した際にampを突然変化させると波に連続性がなくなるため音割れする
5
+ - 同時音数が変化した際にampを突然変化させると波に連続性がなくなるため音割れする
6
- 同時音数というのは和音を再生する際には結構変化するので、本式のシンセサイザー(あるいはミキサー)には波形を調整するコンプレッサーとかリミッターと呼ばれるものが備わっていると思います。これはご質問のコードにあるように単に範囲外をclampするという方法では実現できません。そう方式では波形がひずむため結局音割れします。
6
+ 同時音数というのは和音を再生する際には結構変化するので、本式のシンセサイザー(あるいはミキサー)には波形を調整するコンプレッサーとかリミッターと呼ばれるものが備わっています。これはご質問のコードにあるように単に範囲外をclampするという方法では実現できません。そうやってしま波形がひず結局音割れします。
7
7
 
8
- 波形を直接生成するような場合、こうした音の特性に注意する必要があると思います。自然界の音は[エンベロープ](https://www.g200kg.com/jp/docs/dic/envelope.html)に従って振幅が徐々に変化するのが普通です。MIDIシンセサイザーにはピンキリがあると思いますがどんな単純なものでも最小限のエンベロープ制御を行っており波形が不連続にならないように(音割れしないように)なっているとます
8
+ 波形を直接生成するような場合、こうした音の特性に注意する必要があます。自然界の音は[エンベロープ](https://www.g200kg.com/jp/docs/dic/envelope.html)に従って振幅が徐々に変化するのが普通ですのソフトウェア波形を生成する場合でも最小限のエンベロープ制御を行波形が不連続にならないようにるとでしょう
9
9
 
10
10
  なお、基本体なエンベロープ制御は以下のような感じに考えるとよいと思います。
11
11
 
@@ -21,12 +21,12 @@
21
21
  プログラムの設計的な面について考えると、一つ一つのNOTE(特定の音色の特定のピッチ)に対して独立したオシレーターインスタンスを用いることにして、NOTE_ONの瞬間に新たな初期状態のオシレーターを生成し、オシレーターが時間変化によって適切な振幅で波形を生成し、NOTE_OFFでリリースが始まり振幅が完全に0になったらオシレーターインスタンスを消去するといった方法論が考えられます。(自分はJavaの標準ライブラリーのシンセサイザーぐらいしか見たことないですが、それはこういう手法を取っていました)
22
22
  そのようなオシレーターを複数保持しておき、一定時間ごと(ご質問にあるコードですと0.01秒ごと)にその時点で存在している全てのオシレーターの波形を合成して結果の波形を生成するイメージです。
23
23
 
24
- なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間振幅を徐々に小さくしながら音が鳴り続けるため「音が完全に消えるまでには若干時間がかかる」からです。
24
+ なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単でしょう。同時音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間振幅を徐々に小さくしながら音が鳴り続けるため「音が完全に消えるまでには若干時間がかかる」からです。
25
25
 
26
26
  ---
27
27
  追記:実際に単純なエンベロープにしてやってみた例を挙げてみます。少々長いのでちょっと気が引けますが。自分の環境ではMIDI-INがすぐに使えないので、Key_inputの部分は自動演奏に置き換えています。質問者さんの環境ではこの関数を書き替えずに実行すれば演奏の通りに音が鳴らせると思います。(すみませんが、自動演奏に変えたため元のコードからpygame.midiの部分のコードは削除してあります)
28
28
 
29
- 下記コードでは0.4秒ごとにC4~B#4までの音を順に自動演奏していますが、その限りでは雑音はでないようでした。しかし・・・エンベロープを滑らかにしたつもりかつ同時音数10固定なんですが、自動演奏で和音にしたとたん演奏開始時点で雑音が・・・
29
+ 下記コードでは0.4秒ごとにC4~B#4までの音を順に自動演奏していますが、その限りでは雑音はでないようでした。しかし・・・エンベロープを滑らかにしたつもりかつ同時音数10固定なんですが、自動演奏で和音にしたとたん演奏開始時点で雑音が・・・
30
30
 
31
31
  単なるバグなのかも知れませんし、Pythonのスピードとか自分の配慮が不足している点が何かあるのかも知れません・・・
32
32
 

4

誤記訂正・コードコメント訂正

2018/02/16 10:54

投稿

KSwordOfHaste
KSwordOfHaste

スコア18404

answer CHANGED
@@ -21,7 +21,7 @@
21
21
  プログラムの設計的な面について考えると、一つ一つのNOTE(特定の音色の特定のピッチ)に対して独立したオシレーターインスタンスを用いることにして、NOTE_ONの瞬間に新たな初期状態のオシレーターを生成し、オシレーターが時間変化によって適切な振幅で波形を生成し、NOTE_OFFでリリースが始まり振幅が完全に0になったらオシレーターインスタンスを消去するといった方法論が考えられます。(自分はJavaの標準ライブラリーのシンセサイザーぐらいしか見たことないですが、それはこういう手法を取っていました)
22
22
  そのようなオシレーターを複数保持しておき、一定時間ごと(ご質問にあるコードですと0.01秒ごと)にその時点で存在している全てのオシレーターの波形を合成して結果の波形を生成するイメージです。
23
23
 
24
- なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間神父が徐々に小さくなるため「音が完全に消えるまでには若干時間がかかる」からです。
24
+ なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間振幅を徐々に小さくがら音が鳴り続けるため「音が完全に消えるまでには若干時間がかかる」からです。
25
25
 
26
26
  ---
27
27
  追記:実際に単純なエンベロープにしてやってみた例を挙げてみます。少々長いのでちょっと気が引けますが。自分の環境ではMIDI-INがすぐに使えないので、Key_inputの部分は自動演奏に置き換えています。質問者さんの環境ではこの関数を書き替えずに実行すれば演奏の通りに音が鳴らせると思います。(すみませんが、自動演奏に変えたため元のコードからpygame.midiの部分のコードは削除してあります)
@@ -68,7 +68,7 @@
68
68
  key = 60 + int((t - 3) / 0.4) % 12
69
69
  key_list = [[key, 110]]
70
70
  # 以下のように、3度の和音にしたら同時発音数=10でも音割れした。(Win10 64bit)
71
- # なぜそうなるか分かりませんでした... ><
71
+ # =>PyCharmのデバッグ実行は音割れしたが通常実行にすると音割れしなくなった
72
72
  # ---------------------------------------------
73
73
  # key_list = [[key, 120], [key + 4, 100]]
74
74
  else:

3

追記2

2018/02/16 10:49

投稿

KSwordOfHaste
KSwordOfHaste

スコア18404

answer CHANGED
@@ -30,6 +30,9 @@
30
30
 
31
31
  単なるバグなのかも知れませんし、Pythonのスピードとか自分の配慮が不足している点が何かあるのかも知れません・・・
32
32
 
33
+ 追記2:PyCharm上で実行していたのですが、ふと思いついて「デバッグ実行」ではなく普通の「実行」にしたら和音にしても雑音でなくなりました!
34
+ 想像ですが、PyCharmでデバッグ実行していたためインタープリタの実行スピードが音声再生においついてなかったのだと思います・・・
35
+
33
36
  ```Python
34
37
  import math
35
38
  import pyaudio

2

コード追記

2018/02/16 09:44

投稿

KSwordOfHaste
KSwordOfHaste

スコア18404

answer CHANGED
@@ -21,4 +21,180 @@
21
21
  プログラムの設計的な面について考えると、一つ一つのNOTE(特定の音色の特定のピッチ)に対して独立したオシレーターインスタンスを用いることにして、NOTE_ONの瞬間に新たな初期状態のオシレーターを生成し、オシレーターが時間変化によって適切な振幅で波形を生成し、NOTE_OFFでリリースが始まり振幅が完全に0になったらオシレーターインスタンスを消去するといった方法論が考えられます。(自分はJavaの標準ライブラリーのシンセサイザーぐらいしか見たことないですが、それはこういう手法を取っていました)
22
22
  そのようなオシレーターを複数保持しておき、一定時間ごと(ご質問にあるコードですと0.01秒ごと)にその時点で存在している全てのオシレーターの波形を合成して結果の波形を生成するイメージです。
23
23
 
24
- なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間神父が徐々に小さくなるため「音が完全に消えるまでには若干時間がかかる」からです。
24
+ なお、前述したコンプレッサーとかリミッターをソフトウェア的に実現するのは少々面倒だと思います。振幅がオーバーフローしそうになったときだけ滑らかに波形を調整しないといけないのですが、**滑らかに調整**というのがなかなかめんどくさいのです。そこで、とりあえずは同時撥音数を適当な値に固定してしまい(例えば8とか)、演奏する際にその限界を超えて発音するような事態を避けるのが簡単と思います。同時撥音数というのは同時にNOTE_ONとなっている数の最大数よりも大きくなる点に注意してください。NOTE_OFFしてもリリースタイムの期間の間神父が徐々に小さくなるため「音が完全に消えるまでには若干時間がかかる」からです。
25
+
26
+ ---
27
+ 追記:実際に単純なエンベロープにしてやってみた例を挙げてみます。少々長いのでちょっと気が引けますが。自分の環境ではMIDI-INがすぐに使えないので、Key_inputの部分は自動演奏に置き換えています。質問者さんの環境ではこの関数を書き替えずに実行すれば演奏の通りに音が鳴らせると思います。(すみませんが、自動演奏に変えたため元のコードからpygame.midiの部分のコードは削除してあります)
28
+
29
+ 下記コードでは0.4秒ごとにC4~B#4までの音を順に自動演奏していますが、その限りでは雑音はでないようでした。しかし・・・エンベロープを滑らかにしたつもりかつ同時撥音数10固定なんですが、自動演奏で和音にしたとたん演奏開始時点で雑音が・・・
30
+
31
+ 単なるバグなのかも知れませんし、Pythonのスピードとか自分の配慮が不足している点が何かあるのかも知れません・・・
32
+
33
+ ```Python
34
+ import math
35
+ import pyaudio
36
+ import struct
37
+ import time
38
+
39
+ # PyAudio制御関連=============================
40
+ SAMPLE_RATE = 44100
41
+
42
+ if __name__ == '__main__':
43
+ p = pyaudio.PyAudio()
44
+ # ストリームを開く
45
+ stream = p.open(format=pyaudio.paInt16, channels=1, rate=SAMPLE_RATE, output=1, stream_callback=None)
46
+
47
+
48
+ def End(): # 終了処理(必ず入れる)
49
+ stream.close()
50
+ p.terminate()
51
+
52
+
53
+ # Key管理================================
54
+
55
+ key_list = [] # 押されてるキーを保持するリスト
56
+ start_time = time.time() # MIDI-INなしで自動演奏するための小細工
57
+
58
+
59
+ def Key_input():
60
+ # MIDI-INなしで自動演奏するための小細工
61
+ # MIDI-INが使えるならオリジナルのコードそのままでよいはず・・・
62
+ global key_list
63
+ t = (time.time() - start_time)
64
+ if 3 < t < 20:
65
+ key = 60 + int((t - 3) / 0.4) % 12
66
+ key_list = [[key, 110]]
67
+ # 以下のように、3度の和音にしたら同時発音数=10でも音割れした。(Win10 64bit)
68
+ # なぜそうなるか分かりませんでした... ><
69
+ # ---------------------------------------------
70
+ # key_list = [[key, 120], [key + 4, 100]]
71
+ else:
72
+ key_list = []
73
+ return
74
+
75
+
76
+ # 周波数とか作るところ=======================
77
+
78
+
79
+ class Oscillator:
80
+ def __init__(self, key, max_level, sustain_level, attack_time, decay_time, release_time):
81
+ self.key = key
82
+ freq = 440.0 * (2 ** ((key - 69) / 12))
83
+ self.dt = math.pi * 2 * freq / SAMPLE_RATE
84
+ self.max = max_level
85
+ self.sustain = sustain_level
86
+ self.attack_t = int(attack_time * SAMPLE_RATE)
87
+ self.decay_t = int(decay_time * SAMPLE_RATE)
88
+ self.release_t = int(release_time * SAMPLE_RATE)
89
+ self.released = None
90
+ self.time = 0
91
+ self.amp = 0
92
+
93
+ def calc_amp(self):
94
+ if self.released is None:
95
+ # まだNOTE_ON状態
96
+ if self.time < self.attack_t:
97
+ return Oscillator.interpolate(self.time, self.attack_t, 0.0, self.max)
98
+ else:
99
+ return Oscillator.interpolate(self.time - self.attack_t, self.decay_t, self.max, self.sustain)
100
+ else:
101
+ # NOTE_OFF状態
102
+ return Oscillator.interpolate(self.time - self.released, self.release_t, self.sustain, 0.0)
103
+
104
+ # 振幅の直線補間用
105
+ @staticmethod
106
+ def interpolate(cur_t, max_t, start_level, end_level):
107
+ t = cur_t / max_t if cur_t < max_t else 1
108
+ return start_level * (1 - t) + end_level * t
109
+
110
+ def wave(self):
111
+ self.amp = self.calc_amp()
112
+ w = self.amp * math.sin(self.time * self.dt)
113
+ self.time += 1
114
+ return w
115
+
116
+ def note_off(self):
117
+ if self.released is None:
118
+ self.sustain = self.amp # attack/decay中にNOTE_OFFされた場合への配慮
119
+ self.released = self.time
120
+ return True
121
+ else:
122
+ return False
123
+
124
+ def is_playing(self):
125
+ return self.released is None or self.time < self.released + self.release_t
126
+
127
+
128
+ class Synthesizer:
129
+ def __init__(self, polyphony=8):
130
+ self.oscillators = {}
131
+ self.polyphony = polyphony
132
+ buffer_size = 1048 // 2 # 44100 1sec 4410 0.1sec 441 0.01sec
133
+ latency = buffer_size / SAMPLE_RATE
134
+ self.wave = [0] * int(latency * SAMPLE_RATE)
135
+ self.pack_format = 'h' * len(self.wave)
136
+ print("polyphony=", self.polyphony, "latency=", latency)
137
+
138
+ def generate_wave(self, keys):
139
+ """実際の波形を生成"""
140
+ self.update_oscillators(keys)
141
+ wave = self.wave
142
+ # print(keys)
143
+ amp = 32767 / self.polyphony # 同時発音数を固定とする
144
+ for i in range(len(self.wave)):
145
+ s = int(amp * sum(map(Oscillator.wave, self.oscillators.values())))
146
+ wave[i] = s # max(-32767, min(s, 32767)) # clampしない
147
+ wave = struct.pack(self.pack_format, *wave)
148
+ return wave
149
+
150
+ def update_oscillators(self, keys):
151
+ """NOTE_ONのキー集合によりオシレーター群を更新する"""
152
+
153
+ # もはや発音しないオシレーターを削除
154
+ osc_exists = len(self.oscillators) > 0
155
+ self.oscillators = {osc.key: osc for osc in filter(Oscillator.is_playing, self.oscillators.values())}
156
+ if osc_exists and len(self.oscillators) == 0:
157
+ print("all oscillators removed")
158
+
159
+ # 新たにNOTE_ONとなったオシレーターを追加
160
+ new_keys = filter(lambda key: key not in self.oscillators, keys)
161
+ for key in new_keys:
162
+ max_level = keys[key] / 127
163
+ sustain_level = max_level * 0.9
164
+ attack_time = 0.3
165
+ decay_time = 0.1
166
+ release_time = 0.1
167
+ self.oscillators[key] = Oscillator(key, max_level, sustain_level, attack_time, decay_time, release_time)
168
+ print("note on", key)
169
+
170
+ # NOTE_OFFとなったオシレーターの状態を変更(リリース制御のため)
171
+ for osc_key in self.oscillators:
172
+ if osc_key not in keys:
173
+ if self.oscillators[osc_key].note_off():
174
+ print("note off", osc_key)
175
+
176
+ # 再生================================
177
+
178
+
179
+ def play(stream, data): # 再生用関数、ストリームと波形データを引数に
180
+ # チャンク単位でストリームに出力し音声を再生
181
+ chunk = 1048
182
+ sp = 0 # 再生位置ポインタ
183
+ buffer = data[sp:sp+chunk]
184
+ while buffer:
185
+ stream.write(buffer)
186
+ sp = sp + chunk
187
+ buffer = data[sp:sp+chunk]
188
+
189
+ # ==========================================
190
+
191
+
192
+ if __name__ == '__main__':
193
+ synthesiser = Synthesizer(10)
194
+ while True:
195
+ Key_input() # キーの取得
196
+ keys = {k[0]: k[1] for k in key_list}
197
+ wave = synthesiser.generate_wave(keys)
198
+ play(stream, wave) # 波形の再生
199
+ End() # このままでは決して呼び出されませんが...
200
+ ```

1

文章不備訂正

2018/02/16 09:33

投稿

KSwordOfHaste
KSwordOfHaste

スコア18404

answer CHANGED
@@ -5,9 +5,9 @@
5
5
  - 同時撥音数が変化した際にampを突然変化させると波に連続性がなくなるため音割れする
6
6
  同時撥音数というのは和音を再生する際には結構変化するので、本式のシンセサイザー(あるいはミキサー)には波形を調整するコンプレッサーとかリミッターと呼ばれるものが備わっていると思います。これはご質問のコードにあるように単に範囲外をclampするという方法では実現できません。そういう方式では波形がひずむため結局音割れします。
7
7
 
8
- こうした音の特性に注意する必要があるのではなでしょうか。自然界の音は[エンベロープ](https://www.g200kg.com/jp/docs/dic/envelope.html)に従って振幅が徐々に変化するのが普通です。MIDIのシンセサイザーにはピンキリがあると思いますが、どんな単純なものでも最小限のエンベロープの配慮により波形が不連続になることに音割れを避けるようになっていると思います。
8
+ 波形を直接生成するような場合、こうした音の特性に注意する必要があると思ます。自然界の音は[エンベロープ](https://www.g200kg.com/jp/docs/dic/envelope.html)に従って振幅が徐々に変化するのが普通です。MIDIのシンセサイザーにはピンキリがあると思いますが、どんな単純なものでも最小限のエンベロープ制御を行っており波形が不連続にならないうに(音割れしないようになっていると思います。
9
9
 
10
- 基本体なエンベロープのしかたとしては以下のような感じに考えるとよいと思います。
10
+ なお、基本体なエンベロープ制御は以下のような感じに考えるとよいと思います。
11
11
 
12
12
  アタック:振幅0から最大までになる時間
13
13
  ディケイ:振幅最大から定常状態の振幅(最大よりちょっと小さい)まで下がる時間