控制消息“if”
人工智能和计算机辅助创作始于逻辑控制。即,告诉机器在特定的情况下做什么。如果你饿了,do打开冰箱。如果里边没有面包,那么do去商店。如果你钱够多,do买四块烤肉。回家并再次打开冰箱。如果里边有果冻或果酱,do选其一做个三明治。不要用腌豆腐。有很多诸如while, for, 和 forBy 等迭代和控制的方法,但是do和if是最普遍的。
if消息或函数有三个引数:一个被评估的表达式,一个true函数,以及一个false函数。它评估表达式是真是假并返回结果。
if(表达式, {真函数}, {假函数})
真、假常由运算符比如大于、小于来判断(注意一个等号和两个等号间的区别。“=”意思是将某个值存入变量,”==”意思是“相等吗?”)。运行下例每一行。第一行评估并返回真(因为1的确等于1)因此它运行第一个函数。第二例是假,因此假函数被运行。
21.1. if 范例
if(1 == 1, {"true statement";},{"false statement";}) if(1 == 4, {"true statement";},{"false statement";}) // 注释版: if( 1 == 1, //被评估的表达式; "1等于1" 对还是错? {"true statement";}, //如果是对,运行此行 {"false statement";} //如果是错,运行此行 )
下边是其他的布尔运算符
< 小于
> 大于
<= 小于或等于
>= 大于或等于
!= 不等于
== 等于
消息or 合并两个声明,如果两者之一是对的则返回真:or(a > 20, b > 100)。消息and 合并两个声明,只有当两者都对时返回真:and(a > 20, b > 100)。单词true是真,false是假。
21.2. if 范例
if((1 == 1).and(5 < 7), {"全部都对"},{"只有一个对";}) if((1 == 20).and(5 < 7), {"全部都对";},{"一个错或全错";}) if((1 == 4).or(true), {"对永远是对";},{"1不等于4";}) if(false.or(true), {"对永远是对";},{"对用或者(or)赢了";}) if(false.and(true), {"对永远是对";},{"但错用和(and)赢了";}) if(or(10 > 0, 10 < 0), {34},{78}) if((1 == 1).and((10 > 0).or((5 < 0).or(100 < 200))), {78},{88})
这些孤立的数例在没有上下文关系对照时显得莫名其妙(比如,为什么我会用到表达式 10>0?为什么你仅仅是列印“真”或“假”?)if函数常被用于与一些迭代过程比如do结合使用。下边是一个真实的音乐范例。下边的代码由MIDI值60(C4)开始。然后它选取一个新的MIDI音程,将它加到m并返回那个新值。观察结果。
21.3. do 50 MIDI音程
( m = 60; 50.do( { m = m + [6, 7, 4, 2, 11, 8, -2, -6, -1, -3].choose; m.postln; } ) )
我倾向于更多高低音程起伏的选择。因此最后MIDI值超过了一个对于绝大多数乐器来说合理的范围。即使我已小心的平衡了这个选择,但在一列内一个正值将被选择20次是很明显的。下边的if表述检查每次循环后的值,如果超过84则减去两个八度,如果低于36则增加两个八度。
21.4. do 50 MIDI音程
( m = 60; 50.do( { var next; next = [6, 17, 14, 2, 11, 8, -12, -16, -1, -3].choose; "next interval is : ".post; next.postln; m = m + next; "before being fixed: ".post; m.post; if(m > 72, {m = m - 24}); if(m < 48, {m = m + 24}); "after being fixed: ".post; m.postln; } ) )
当写类似这样的小片段时,在SC里边闲逛一下,看看是否已经有某个函数存在并能做你想做的事情是值得的。wrap,它会将一个值包裹起来,但在本例中,我们做的是缓冲(buffering)而不是包裹(wrapping)。因此在这个状况下,我们必须得要加入额外的代码。
下例展示了在一个音类数组中的do循环与if协同测试查找C,D或E。电脑不可能按实际的音去理解这些(看下边弦乐的讨论),它们看到的只是文字。即使如此,它仍能比较它们是否相等。
21.5. 音类 do
( ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"].do( {arg item, count; if((item == "C").or(item == "E").or(item == "G"), //布尔测试 {item.post; " is part of a C chord.".postln;}, //对函数 {item.post; " is not part of a C chord".postln;} //错函数 ) } ) )
你可以说我们将会了计算机认识C和弦。这便是人工智能开始的地方。
if表达式也可用于在荧幕上定义一个区域。当鼠标进入指定的区域生成一个正值,或一个触发器。在本例中,* 相当于和(and)。这里没有对错,只有值1(开启)和0(关闭)。
21.6. 鼠标区域触发器
( { var aenv, fenv, mgate, mx, my; mx = MouseX.kr(0, 1); my = MouseY.kr(0, 1); mgate = if((mx>0.3) * (mx<0.5) * (my>0.3) * (my<0.7), 1, 0); aenv = EnvGen.kr(Env.asr(0.1, 0.5, 2), mgate); fenv = EnvGen.kr(Env.asr(1, 1, 2), mgate, 1000, 100); RLPF.ar(Saw.ar(100)*aenv, fenv, 0.1) }.play )
一个使用if很棒的小技巧控制何时postln而不是post,这并不列印新的一行:
21.7. 新行
( 100.do( { arg count; 100.rand.post; if(count%10 == 9, //每个第9次 {" new line: ".postln;}, //列印一个回车 {" : ".post;} //仅是" * ",没有回车 ); } ) )
while
while函数在第一个引数(一个评估函数)返回错误前重复第二引数(一个函数)。它对新手来说是一个危险的工具。如果你写一个永远不会返回假的条件,SC将永远运行下去(这是多数情况下造成程序锁死的原因)。下边第一例展示了一个常见的错误。代码在第二个函数在变量a中存储变量10前会持续重复。但10.rand只能选取0到9间的数字,因此它永远选不到10,因此在你强行停止前会无限运行下去。也正因此,也可以利用它给一个而外的条件使程序停止。
最后一行展示了while的典型用法。这行代码英告诉你它从100中选到数字13所用的时间长短。
21.8. while
// 典型的错误,因为a永远不会等于10 a = 0; while({a != 10}, {a = 10.rand}) // 死循环 // 在特定次数后停止显得更安全一些? // 注意and的写法 c = 0; a = 0; while({and(a != 10, c < 100)}, {a = 10.rand; c = c + 1;}) //while的典型用法。告诉你从100中挑到13需要多长时间。 ( a = 0; c = 0; while({(a != 13).and(c < 10000)}, {a = 100.rand; c = c + 1;}); c )
for, forBy
for允许你指定循环的开始与结束点,forBy包含一个步进值引数。在之前的章节,我们用奇数谐波建立了一个patch。计数器用于计算每个谐波,但由于它从0开始,我们不得不加1(counter+1).我们同样不得不仅为奇数谐波做一点数学计算。但有了forBy,这个计算可以被整合。
21.9. for, forBy
34.for(84, {|i| i.midicps.postln}); 34.forBy(84, 5, {|i| [i, i.midicps].postln}); 1.forBy(24, 2, {|i| [i, i*500].postln}); // 奇数谐波 ( // 加法方波 { o = 0; 1.forBy(24, 2, {|i| o = o + SinOsc.ar(i*500, mul: 1/i)}); o*0.4 }.scope )
MIDIIn
MIDIIn从一个MIDI输入读取数据。有很多可用的方法。首先是触发一个你之前设计的乐器:
21.10. MIDI输入触发SynthDef
( // 先定义一个乐器 SynthDef("simpleInst", { arg midi = 60, vel = 60, inst; inst = { SinOsc.ar( freq: (midi.midicps)*(LFNoise0.kr(12) + 2).round(1), mul: vel/256) }.dup(5) * EnvGen.kr(Env.perc(0, 3), doneAction: 2); Out.ar(0, inst) } ).play ) MIDIIn.connect; ( // 然后将它链到MIDI输入 MIDIIn.noteOn = { arg src, chan, midiNum, midiVelo; Synth("simpleInst", [\midi, midiNum, \vel, midiVelo]); }; )
要控制其他SynthDef乐器,只需用乐器名替换"simpleInst"并确保挂上该乐器对应的引数即可。
实时篡改(Real-Time Interpolation)
MIDI输入的另一用法是在经过某些修改之后,将输入反映到你的MIDI设备(或其他设备)。乍一看,这看起来像一个很好的恶作剧,但同样可以用于篡改(interplotion)。篡改利用现存组件比如一个即兴器的结构或直觉的成就,将之映射到一部分或全部新材料的音乐元素上。比如说,一个钢琴家可能在即兴时用爵士共有的全音阶展示技巧,但如果被要求包含不同的音阶或构思、12音体系、合成音阶,或复杂的轮唱技术时就傻逼了。通过读取他使用他所熟知的材料进行的实时即兴,你可以将这流畅的直觉思维映射到更复杂的实验上。
在篡改数据时,你常不得不使用一个if表达式添加一个逻辑过滤器,允许一些材料不作改变就通过,同时改变其他的。下例用一种新奇的方式使用了过滤器。第一是一个简单的延迟,但它仅在力度高于60的情况下被添加,使用一个if表达式作为过滤器。力度60以下不作改变然后通过。
第二取MIDI输入并在C4轴反向后将之反射到MIDI设备。然而,if表达式只搜寻白键([0, 2, 4, 5, 7, 9, 11]%12)并仅将反向添加到那些MIDI值上。黑键不被反向。因此布鲁斯音阶C4, D4, Eb4, E4, F4, F#4, G4, A4, Bb4, C5 将变为 C4, Bb3, Eb4, Ab3, G3, F#4, F3, Eb3, Bb4, C3。不消说,这真的可以摆脱一个演奏者,因此为了效率,他们应该脱离监听正确MIDI信息的耳机,在听众仅听到篡改版的时候,激发精确反馈的直觉即兴。用多层、古典、即兴、不寻常音阶、调谐、改良的节奏等等尝试这个。可能性是无限的。
21.11. 通过if() 过滤器进行MIDI输入篡改
// 为力度高于60的做简单延迟 ( MIDIIn.noteOn = {arg src, chan, num, vel; var delay = 0.5; thisThread.clock.sched(delay, {m.noteOn(1, num, vel)}); }; MIDIIn.noteOff = {arg src, chan, num, vel; var delay = 0.5; thisThread.clock.sched(delay, {m.noteOff(1, num, vel)}); }; ) // 篡改: 仅反向白键 ( var white; white = [0, 2, 4, 5, 7, 9, 11]; MIDIIn.noteOn = {arg src, chan, num, vel; if(white.includes(num%12), {m.noteOn(1, (60 - num) + 60, vel);}, {m.noteOn(1, num, vel)}); }; MIDIIn.noteOff = {arg src, chan, num, vel; if(white.includes(num%12), {m.noteOff(1, (60 - num) + 60, vel);}, {m.noteOff(1, num, vel)}); }; )
分析
MIDI输入最后的用处是做分析。下例读取MIDI值,计算距上个值的音程,然后将那个值存入一个数组,因此可以持续记录一个音乐家或即兴演奏家使用每个音程的频度。有两个版本。第一个以移动到任何音的动作来计数音程,不管是升是降,即使倒置(上边这段话的原文:The first counts an interval as motion to any pitch, whether up or down, as the same interval since they are inversions。翻译至此,忽然一阵头晕,再加上Cottle说话爱绕的写作风格,因此不确定能忠实反映原文意思。先放着,想到好的翻译再修正。):C4升到G4(五度)被记录得与C4降到G3(降4度)一样。第二版本,在例子中被注释了,以相等计数音程升降:C4升到G4(五度)与C4降到F3(降5度)一样。
21.12. 用if()过滤器做MIDI输入篡改
( // 运行此以开始读取输入 var previousPitch = 62, thisInterval; ~intervals = Array.fill(12, {0}); MIDIIn.noteOn = {arg src, chan, num; // 计数倒置为一样 Counts inversions as the same thisInterval = (12 - (previousPitch - num))%12; // 计数音程升降为一样 Counts intervals up or down as the same // thisInterval = abs(previousPitch - num); // 反注释以监视值 // [previousPitch, num, thisInterval].postln; ~intervals.put(thisInterval, ~intervals.at(thisInterval) + 1); previousPitch = num; // 反注释以实时查看 // ~intervals.postln; }; ) // 一切就绪后,运行此行 ~intervals;
篡改似应译为插值.
Reply
或许此处”篡改”较易理解.
Reply
ww1way Reply:
4月 24th, 2011 at 15:13
@kakyo, 感谢你的指正,由于我并非计算机专业出身,因此在某些单词的翻译上肯定会存在问题,因此在对于我没有把握的词汇翻译后,一般我都会标注英文原文。是的,译为“篡改”完全出于我个人认为较易理解的初衷。
Reply