SC:用if做控制,接着do,Arrays,MIDIIn,计算机辅助分析

控制消息“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;

作者: ww1way

http://about.me/ww1way

《SC:用if做控制,接着do,Arrays,MIDIIn,计算机辅助分析》有3个想法

  1. 或许此处”篡改”较易理解.

    Reply

    ww1way Reply:

    @kakyo, 感谢你的指正,由于我并非计算机专业出身,因此在某些单词的翻译上肯定会存在问题,因此在对于我没有把握的词汇翻译后,一般我都会标注英文原文。是的,译为“篡改”完全出于我个人认为较易理解的初衷。

    Reply

发表评论

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.