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;
);