Scheduling
一些相关的帮助文档
[SystemClock]
[TempoClock]
[AppClock]
// 载入我先——这个声音将被用于记下来的调度小样
// 我们现在运用SynthDef和Synth,这很重要
// 注意doneAction!
(
SynthDef(\bleep,{ arg out=0, note=60, amp=0.5, pan=0.0;
var freq, env;
freq = note.midicps;
env = EnvGen.ar(
Env([0,1,1,0],[0.01, 0.1, 0.2]),
levelScale:amp,
doneAction:2
);
Out.ar(out,
Pan2.ar(Blip.ar(freq) * env, pan)
)
}).load(s);
)
这个SynthDef使用了一个doneAction为2的包络,这使得一个合成器由定义制造,并且在包络结束后释放(deallocate)自身。这至关重要,否则在我们载入它后它会永远挂在那!
你可以非常方便的建立一个重复机制。全局的SystemClock以秒进行调度。SystemClock是最值得信赖的时钟。
// 从距现在0秒开始一个进程,之后每1秒重复一次
(
SystemClock.sched(0.0,//从距现在0秒开始一个进程,即,立马
{//一个表明你希望调度什么的函数
Synth(\bleep);
1 //每10秒重复一次
}
)
)
(最后从函数返回的东西是函数被调用的间隔时间;你可以返回一个数字,或nil;nil将停止调度)
在你按下cmd+句号键的同时,所有调度也都将停止。
下边是一个类似的例子,将引数传递入每个新的Synth:
(
SystemClock.sched(1.0,//1秒后开始
{
Synth(\bleep,
[ //为Synth传递引数
\note, (#[0,2,4,5,7,9] + 48).choose,
\pan, 1.0.rand2
]
);
// 随机选择重复时间
[0.25,0.3,0.7,0.1].choose
})
)
#[0,2,4,5,7,9]造了一个固定的(非动态)数组,因此稍微便宜些(cheaper)。
如果你乐意以秒来调度一切,并自己搞出任何节奏表述,那么你已经准备好了。
欲获取当前SystemClock时间,你可以使用这段代码:
Main.elapsedTime; //给一个从程序开启到现在的时间
schedAbs遵守一个绝对时间
(
SystemClock.schedAbs(Main.elapsedTime+1.0,// 开始于绝对系统时钟时间+1秒
{
Synth(\bleep,
[ //为Synth传递引数
\note, (#[0,2,4,5,7,9] + 48).choose,
\pan, 1.0.rand2
]
);
// 随机选择重复时间
[0.25,0.3,0.7,0.1].choose
})
)
在很多情况下,你喜欢在一定的tempo下工作,(例如,你希望根据拍子调度事件)
SuperCollider以每秒的拍数(bps)而不是每分钟几拍(bpm)来恒量tempo,例如:
1 bps = 60 bpm
1.6666667 bps = 100 bpm
2 bps = 120 bpm
2.4 bps = 144 bpm
3 bps = 180 bpm
(二者的相互转换是乘60除60的关系,很简单)
基于拍子的调度,我们使用TempoClock:
(
var t;
t = TempoClock(2); // 以120bpm(2bps)的tempo构造一个速度钟
t.schedAbs(0, { arg ... args; // 立马开始
args.postln; // 向我们的事件函数post输入引数
// (will post logical time in beats, elapsed time
// in seconds of enclosing thread and this clock)
Synth(\bleep);// 制造哔哔声
1.0 // 重新调度每一拍
})
)
TempoClock的作用是对速度和节奏型的改变做出适当的反应。
还有一个自动可用的TempoClock
t= TempoClock.default;
你可以问一个速度时钟它在哪;这个默认的时钟可能到目前为止已经运行了很长一段时间。
t.elapsedBeats; // 我们确切位于什么逻辑拍时间(logical beat time) t.bar; // 我们在第几小节 (默认节奏型为4/4) t.elapsedBeats.ceil; // 找到下一拍 t.elapsedBeats.floor; // 找到最后一拍
(
var t;
t = TempoClock.default;
// 默认的TempoClock可能已经运行了很久
// 所以你应当从下一拍开始
t.schedAbs(t.elapsedBeats.ceil, // 从下一个重拍开始
{
Synth(\bleep, [\note, [36,40,43].choose, \pan, 1.0.rand2]);
[0.25,0.5,1.0, nil].wchoose([0.5,0.4,0.05,0.05]); // 加权选择
// 从数组选择数字重复- nil意味着停止
})
)
wchoose允许我们做一个加权选择,偏重第二个数组(所有数字相加不能超过1,并且加权数的数量必须与待选取数数量相同),从第一个数组中选取数组。
[1,2,3,4].wchoose([0.8,0.1,0.07,0.03]);
如果你请求一个已经发生的拍子,调度器将尽快抓取事件队列:
(
var u;
u = TempoClock(1.2); // 以1.2bps的速度构造我们的TempoClock
// 在5拍前开始!
u.schedAbs(-5.0, { Synth(\bleep); 0.5 });
// 因为抓取的缘故,你会听到事件的爆音
SystemClock.sched(5.0, {u.clear}); // 从现在起5秒后调度一个停止.
)
如果你在某个点改变速度,过度将是平滑的:
(
var u;
u = TempoClock(3.5);
u.schedAbs(0.0, { arg beat, sec;
[beat,sec].postln;
Synth(\bleep, [\note, rrand(60.0,67.0)]);
0.5
});
u.schedAbs(8.0, { u.tempo_(2); nil }); // 调度速度改变
u.schedAbs(12.0, { u.tempo_(7); nil }); // 调度速度改变
u.schedAbs(17.2, { u.tempo_(1); nil }); // 调度速度改变
SystemClock.sched(7.0, { u.clear; }); // 从现在起7秒后调度一个停止.
)
逐行运行下列代码:
t=TempoClock(2); t.tempo; // 获取当前速度 t.tempo_(4); //设立当前速度(唯一的不同是下划线) t.tempo; t.tempo= 2.3; //同样赋值新的速度 t.tempo;
通过UI控制改变速率
// 滑杆的范围始终是0.0~1.0,如此映射到代码
(
var w,u,slid, button;
w = SCWindow("tempo control test", Rect(100,100,200,40));
slid = SCSlider(w, Rect(0,0,200,20));
button = SCButton(w, Rect(60,20,40,20));
button.states_([["kill"]]);
w.front;
slid.action_({u.tempo_(2*(slid.value)+1)});
button.action_({u.clear; w.close;});
u = TempoClock(1);
u.schedAbs(0.0, { arg beat,sec;
[beat,sec].postln;
Synth(\bleep, [\note, rrand(60, 100)]);
1.0
});
)
你可以通过一次性建立两个时钟做多速度
// 速度率为12:13
(
var u,v;
u = TempoClock(1.2);
v = TempoClock(1.3);
u.schedAbs(0, { Synth(\bleep, [\note, rrand(41.8,47.5), \pan, -0.5]); 1.0 });
v.schedAbs(0, { Synth(\bleep,[\pan, 0.5]); 1.0 });
SystemClock.sched(10.9, { u.clear; v.clear; }); // 从现在起10.9秒后调度一个停止.
)
GUI和时钟的笔记
UI们不能通过从SystemClock的直接调用而更薪——操作系统的AppClock必须被使用。下边是一个移动窗口的示范:
// 在试图关闭窗口前,记得先按下cmd+句号键
(
var w, i;
i = 0;
w = SCWindow("My Window", Rect(100, 0, 200, 50));
// 一个200*200的窗口出现在萤幕坐标(100, 0)的位置
w.front;
// 调度移动并改变窗口的尺寸
AppClock.sched(0.0, {
w.bounds_(Rect(100, (10 * i) % 500, rrand(200,400), 50));
i=i+1;
0.125
});
)
这将失败:
// SystemClock.sched(0.0,{w.bounds_(Rect(100, (10*i)%500, 200,200)); i=i+1; 0.125});
这是一个很有用把戏;如果你在一个SystemClock中,你可以像这样在尖括号内约束任何GUI调用:
{
//GUI代码
}.defer
在后台,这重新分配了到AppClock的命令。
这样没问题,通过使用defer将命令传递到一个AppClock:
(
var w, i;
i = 0;
w = SCWindow("My Window", Rect(100, 0, 200, 200));
// 一个200*200的窗口出现在萤幕坐标(100, 500)的位置
w.front;
SystemClock.sched(0.0, {
{ w.bounds_(Rect(100, (10*i)%500, 200, 200)) }.defer; // defer扮演了SystemClock和AppClock之间的桥梁
i=i+1;
0.125
});
)
