私が調べた範囲では汎用的にオシレータシンクを行う方法が見当たりませんでした。ここでは場当たり的な実装をいくつか紹介します。
正確に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;
levels と times の前後に [0] を加えている理由を説明します。まずは Env のループで使われる引数の releaseNode と loopNode の挙動を確認します。それぞれ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;