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