函数和消息使用引数。又是引数之一是另一个函数。函数可被赋到一个变量,然后这个变量被用到引数列,或者说它可以被嵌套如引数列。全部例子见下。
.
.
.
.
20.1. 被用作引数传递的函数
// 被用作变量传递的函数 var myFunc; myFunc = { (10*22).rand }; max(45, myFunc.value); // 被嵌套的函数 max(45, {(10*22.rand)})
do
do函数或消息被用于将一个进程重复一定的次数。第一引数是一串将被“做”的元素,一个数组是比较典型的。第二引数是一个函数,为列表中的每个元素进行循环。列表中的元素需要被传递给函数,因此它们才能在函数内被使用。这由一个引数完成。你可以将这个引数命名为任何你喜欢的名字。但它需要是第一个引数。
20.2. do范例
do(["这", "是", "一个", "字符串", "的", "列表"], {arg 每个元素; 每个元素.postln;})
或者
do([46, 8, 109, 45.8, 78, 100], {arg 无论什么; 无论什么.postln;})
如果第一引数是一个数字,那么它代表do将重复的次数。因此do(5, {叽叽歪歪})将“做”0,1,2,3和4。
20.3. do范例
do(5, {arg theNumber; theNumber.postln;})
确保第二引数是一个放置于花括号内的函数。把上例的花括号去掉后运行,看看有什么不同。
也可以使用接收器表示法,5,是对象或接收器,do是消息。两种语法是一致的。
20.4. 接收器中的do
do(5, {"boing".postln}) //相同的结果 5.do({"boing".postln;})
在之前的章节中,我们发现保持对每个进程重复的记录是很有帮助的。do函数也有一个这么做的方法。被搞定的元素被传递给函数,do同样计数每次循环并将数字作为函数的第二引数传递。你同样可以将它命名为任何你喜欢的名字,但两者应位于正确的顺序上。这第二个概念有一点点令人混淆,因为如果在10.do的情况下,第一和第二引数是一样的:它以此“做”的数字从0~9,同时,计数器在计数循环是同样也是从0移动到9.
20.5. 有引数的do(10)
do(10, {arg eachItem, counter; eachItem.postln; counter.postln})
将一个数组用于第一引数更清楚一些,或被“做”的对象。记住引数的位置,而不是名字,确定哪个是哪个。注意第二例,名字看起来不对,但被反复循环的元素是第一引数,计数是第二个。使用数字(5.do),区别是理论上的。但使用一个数组,结果可能大不相同,像第三例示范的。同样注意在这个数组内的最后一个元素同样是一个数组:一个嵌套数组。
你可以使用条款inf以得到一个无限的do。当你尝试前保存一切!
20.6. 用引数的array.do
[10, "hi", 12.56, [10, 6]].do({arg eachItem, counter; [counter, eachItem].postln}) [10, "hi", 12.56, [10, 6]].do({arg count, list; [count, list].postln}) //错误 [10, 576, 829, 777].do({arg count, items; (items*1000).postln}); [10, 576, 829, 777].do({arg items, count; (items*1000).postln}); inf.do({arg i; i.postln}) //这当然会使SC崩溃
MIDIOut
尽管区别是模糊的,我仍将电脑音乐划分为两大类:合成,也就是乐器或声音设计;第二类是事件/结构设计。事件/结构设计涉及音的组织、持续、下一事件安排、乐器选择…等等。如果你对那种创作类型更感兴趣,那么真没太多理由去设计你自己的乐器,你几乎可以使用任何东西。这便是MIDI的用武之处。
MIDI因本地安装的变化而显得复杂。本子上使用SimpleSynth的时候,你可能需要四个通过一个MIDI接口连接的外部键盘。因此,我将细节留给了每个个体,但下例展示了我如何得到MIDI回放。
要开始和停止音符,我们使用noteOn和onteOff,每个都以MIDI通道、音高和力度作为引数。onteOff有一个力度引数,但音符停止命令真的仅是一个力度为零的音符开始,因此我认为它是多余的:m.noteOn(1, 60, 100); m.noteOn(1, 60, 0)。以下使用MIDI的范例假设你已初始化MIDI,分配了一个叫做“m”的输出。
第二例展示了另一个简单的乐器。
20.7. MIDI out
( MIDIClient.init; m = MIDIOut(0, MIDIClient.destinations.at(0).uid); ) m.noteOn(1, 60, 100); //通道, MIDI音符, 力度 (最大值127) m.noteOff(1, 60); //通道, MIDI音符, 力度 (最大值127) //同样的东西: m.noteOn(1, 60, 100); //通道, MIDI音符, 力度 (最大值127) m.noteOn(1, 60, 0); //通道, MIDI音符, 力度 (最大值127) // 或者说假如你没有MIDI的话 ( SynthDef("SimpleTone", { //UGen函数的开始 arg midiPitch = 60, dur = 0.125, amp = 0.9; var out; out = SinOsc.ar(midiPitch.midicps, mul: amp); out = out*EnvGen.kr(Env.perc(0, dur), doneAction:2); Out.ar(0, out) } ).play(s); ) //然后将例子中的这行 m.noteOn(arguments) //用这行代替 Synth("SimpleTone", arguments)
下一例是概念的或假想的音乐。在我开始进行计算机辅助创作的头几年,我开始思考写出所有可能的旋律。我确信这在一个计算机程序的范围内。在实现所有可能的变数(节奏,长度,变奏,register,清晰的结合)之后,我决定仅尝试音高的有限学习:每个可能的12音列(12-tone row)。这个实验也是我若干次将主机搞崩中的一个。然后我开始觉得是否真的有必要真的play每一列,或能否将它看作创作来写?构成创作的是什么?列印(print out)会吗?一个软拷贝(soft copy)如何呢?(后来我认为这是真正使机器崩溃的原因)为什么不写每个旋律?具备每个12音列的代码本身,难道不能变成每个12音列旋律的表现?概念音乐:这是我离开它的地方。
然后这里是最新的化身,它实际上演奏每一列,给予足够的时间。它不包含反向,倒退,或反转后退。因为理论上,那些将如原来般显现出来。它从中部的某处开始,然后环绕,因此它将在最后演奏出每一个变奏。
total.do将循环每个可能的变奏。它区别于一个loop,因为我可以传递count作为一个计数器,它能促进每个新的置换(permutation)。permute消息去一个单独的引数,置换的数量。它返回一个所有被记录为没有循环的元素。首先试试简短的例子作为一个说明。
我计算12的阶乘479,001,600:全部可能的变奏。一旦置换被选定,瞬间do将逐句通过那个数组演奏每个音符。r.start和r.stop开启和停止Task。最后的127.do是一个“全部音符停止”的信息,它被用来停止如果你在一个时间中部执行r.stop后所有挂起的音。thisThread.clock.sched被用来关闭MIDI音。它为next*art安排停止时间,这里art是清晰结合(articulation)。因此,如果结合被设置为0.9,相当于连奏(legato)。如果它被设置为0.1,它将是断奏(staccato)。
练习,do,MIDIout,每12音列
20.8. 每列
// 置换 25.do({arg count; postf("Permutation %: %\n", count, [1, 2, 3, 4].permute(count));}) //每列 ( //运行这个先 var original, total, begin, next, art; original = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; total = 479001600; begin = total.rand; next = 0.125; art = 0.9; ("Total playback time = " ++ (total*next/3600).asString ++ " hours.").postln; r = Task({ total.do({arg count; var thisVar; thisVar = original.permute(count+begin); thisVar.postln; (thisVar + 60).do({arg note; m.noteOn(1, note, 100); thisThread.clock.sched(next*art, {m.noteOff(1, note, 100); nil}); (next).wait }); }) }) ) //然后这些 r.start; r.stop; 127.do({arg i; m.noteOff(1, i, 0)})
这在概念上是正确的,因为它遍历每个可能的置换。它是,无论如何,有一点卖弄学问的,因为从一个置换到另一个的变奏差别是很微弱的。
接下来的代码随机选择一列。并不严格限制于12音,因为它允许两个音被循环。理论上这将需要更多时间来呈现一列中每个可能的置换,因为它可以循环置换。
20.9. 每个随机列
( var original, total, begin, next, art; original = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; total = 479001600; next = 0.125; art = 0.9; ("Total playback time = " ++ (total*next/3600).asString ++ " hours.").postln; r = Task({ total.do({ var thisVar; thisVar = original.permute(total.rand); thisVar.postln; (thisVar + 60).do({arg note; m.noteOn(1, note, 100); thisThread.clock.sched(next*art, {m.noteOff(1, note, 100); nil}); (next).wait }); }) }) ) r.start; r.stop; 127.do({arg i; m.noteOff(1, i, 0)})