SuperCollider:调度

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秒重复一次
	}
)
)

(最后从函数返回的东西是函数被调用的间隔时间;你可以返回一个数字,或nilnil将停止调度)

在你按下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
	});
)
Be Sociable, Share!

发表评论

电子邮件地址不会被公开。 必填项已用*标注