2017/07/27

SuperColliderでZynAddSubFXのPADsynth

SuperColliderでZynAddSubFXPADsynth 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;
);