SuperColliderでZynAddSubFXのPADsynth algorithmを実装します。細かい部分についてはPADsynth algorithmのページにあるC/C++でのリファレンス実装が参考になりました。
オリジナルもそうですが、レンダリングに時間がかかります。
( // 正規分布のharmonic profile。 ~profile = { |fi, bwi| var x = fi / bwi; exp(x.neg * x) / bwi }; // デフォルト値はリファレンス実装のc_basicと同じ。 ~padTable = { | server(s), size(2**18), f0(261.0), bw(40.0), number_harmonics(64) , ampFunc({ |i| (if ((i % 2) == 0) {2.0} {1.0}) / i }) | var sampleRate = server.sampleRate; var freq_amp = Signal.newClear(size / 2); var amps = Array.fill(number_harmonics + 1, ampFunc); var complex, smp; for (1, number_harmonics, { |nh| var bw_Hz = (pow(2, bw / 1200) - 1.0) * f0 * nh; var bwi = bw_Hz / (2.0 * sampleRate); var fi = f0 * nh / sampleRate; freq_amp.do{ |f_amp, index| var hprofile = ~profile.value((index / size) - fi, bwi); freq_amp[index] = f_amp + (hprofile * amps[nh]); } }); // 直流を除去。 freq_amp[0] = 0; freq_amp.plot; // ナイキスト周波数以上の成分を付け加える。 freq_amp = freq_amp ++ Signal.fill(freq_amp.size, 0); // 位相のランダマイズ。 complex = Polar( freq_amp, freq_amp.class.fill(freq_amp.size, {2pi.rand}) ); smp = complex.real.ifft(complex.imag, Signal.fftCosTable(freq_amp.size)); smp.real.normalize }; ); ( ~sig = ~padTable.value(s, 2**18); // 乗数が 10 以下のときにピッチがおかしくなる。 ~sig.plot; ~waveTable = Buffer.loadCollection(s, ~sig); { Pan2.ar(PlayBuf.ar(1, ~waveTable, 1, 1, 0, 1)) }.play; );