SC:用do做迭代,MIDIOut

函数和消息使用引数。又是引数之一是另一个函数。函数可被赋到一个变量,然后这个变量被用到引数列,或者说它可以被嵌套如引数列。全部例子见下。

.
.
.
.
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)})
Be Sociable, Share!

Published by

ww1way

http://about.me/ww1way

Leave a Reply

Your email address will not be published. Required fields are marked *