2017/05/10

SuperColliderでSync

私が調べた範囲では汎用的にオシレータシンクを行う方法が見当たりませんでした。ここでは場当たり的な実装をいくつか紹介します。

正確にHard syncできるのはphaseを入力できるgeneratorを使う方法です。次点でPlaybufですが位相リセット時にクリックノイズが乗ります。EnvGenは波系が歪んでいます。LoopBufはBufferのループ回数にばらつきが出ます。

Deterministic generatorでsync

phase を入力できるgeneratorは周波数を0にして LFSaw などで位相を回すことでsyncできます。

次の例の slave は上のどれかを使う必要があります。

{
  var freq = MouseY.kr(30, 3000, 1);
  var ratio = MouseX.kr(1, 100, 1);
  var master = LFPulse.ar(freq);
  var syncPhase = LFSaw.ar(freq, 0, pi * ratio);
  var slave = SinOsc.ar(0, syncPhase % 2pi, 1);
  var osc = master.range(0, 1) * slave;
  OffsetOut.ar(0, Pan2.ar(osc, 0, 0.5))
}.play;

EnvGenでsync

EnvGen もゲートを使ってsyncできます。ゲートについてはヘルプに解説があります。IDEでは EnvGen にカーソルを合わせてCtrl+Dでヘルプを開けます。

{
  var freq = MouseY.kr(30, 3000, 1);
  var ratio = MouseX.kr(1, 100, 1);
  var gate = LFPulse.ar(freq, 0, 0.5, 2, -1);

  var levels = [0] ++ [1, 0.9, 0, -0.9, -1] ++ [0];
  var waveSize = levels.size - 3;
  var times = [0] ++ { 1 / (ratio * freq) / waveSize }.dup(waveSize) ++ [0];
  var env = Env(levels, times, \lin, levels.size - 2, 0);
  var envGen = EnvGen.ar(env, gate);

  OffsetOut.ar(0, Pan2.ar(envGen, 0, 0.5))
}.play;

levelstimes の前後に [0] を加えている理由を説明します。まずは Env のループで使われる引数の releaseNodeloopNode の挙動を確認します。それぞれ4番目と5番目の引数です。以下の例を順に再生してみてください。

Env([1, 0, -0.1, 0], [1, 1, 1], \lin, 2, 0).test(5).plot; // (1)
Env([1, 0, -0.1, 0], [1, 1, 1], \lin, 2, 1).test(5).plot; // (2)
Env([1, 0, -0.1, 0], [1, 1, 1], \lin, 3, 0).test(5).plot; // (3)

(1)と(2)を再生すると、ループ開始点の値は levels[loopNode + 1] 、ループ終了点の値は levels[releaseNode] であることがわかります。

(3)を再生するとループしません。 releaseNode の値に levels.size - 1 を指定するとリリース後のエンベロープが無くなってしまうのでループしないようです。

つまり [0] を前に加えるのはループ開始点を正しく設定するため、後に加えるのは Env をループさせるため、ということです。

この方法の元ネタは DemandEnvGen のヘルプにあった例です。あわせて参照してみてください。

Bufferでsync

Buffer を用意して PlayBuf をトリガすることでsyncできます。

~buffer = Buffer.alloc(s, 512).sine1(1.0, true, false, true);
{
  var freq = MouseY.kr(30, 3000, 1);
  var rate = ~buffer.numFrames / SampleRate.ir * freq * MouseX.kr(1, 32, 1);
  var trigger = Impulse.ar(freq) - 0.5;
  var loopBuf = PlayBuf.ar(
    ~buffer.numChannels,
    ~buffer.bufnum,
    rate,
    trigger,
    0,
    1
  );
  var master = LFPulse.ar(freq).range(0, 1);
  OffsetOut.ar(0, Pan2.ar(master * loopBuf, 0, 0.5))
}.play;

// ~buffer.free;

LoopBuf でもsyncできます。 LoopBuf の利点はループ開始点を指定できる点です。

~buffer = Buffer.alloc(s, 512).sine1(1.0, true, false, true);
{
  var freq = MouseY.kr(30, 3000, 1);
  var rate = ~buffer.numFrames / SampleRate.ir * freq * MouseX.kr(1, 32, 1);
  var gate = LFPulse.ar(freq);
  var loopBuf = LoopBuf.ar(
    ~buffer.numChannels,
    ~buffer.bufnum,
    rate,
    gate,
    0,
    0,
    ~buffer.numFrames - 1,
    4
  );
  OffsetOut.ar(0, Pan2.ar(loopBuf, 0, 0.5))
}.play;

// ~buffer.free;