变量和注释
定义和使用你自己的属于和函数在代码中很常见。变量就为此目的而生。记住变量名可以是任何东西,但必须以小写字母(数字不行)开头并且是连续的。使用隐秘的名字比如 tri,beg,pfun,freq 甚至单字母比如 a,b,c 更吸引人。更具体的名字虽然写起来耗时更多但长远来看,这样的做法更好:firstPitch, legalDurations, maximumAttack 等等。
申明(被程序识别)变量的方式:语法 var 后跟一串变量名,之间以逗号分开,以冒号结尾。分配变量值(值被存储到变量相应的存储位置)的方法:使用“=”号,后接你想要存储的值和冒号。之后变量(或更准确些,包含于变量中的值)便可在程序里的任何地方使用。
变量使用范围取决于申明它的函数。一个变量无法被用于函数外。你可以用 波浪号(~)申明全局变量。这无需使用 var 语法。
下边第二例同样引入了表达式。表达式及以分号打断的一行代码。它勾画执行顺序:基本上是“做这个”,“然后这个”。然后执行顺序是:变量被申明,然后三个变量被赋值,然后 SinOsc 被放到一起,使用那些变量并演奏。变量可以在声明是赋值,也可以被下一patch使用。
11.1. 申明变量, 赋值 和 注释
//第一个patch {SinOsc.ar(LFNoise0.ar(10, mul: 400, add: 800), 0, 0.3)}.play
//用变量重写第一个patch ( { var freqRate, freqRange, lowValue; freqRate = 10; //挑选新值的比率 freqRange = 1200; //选取频率的范围 lowValue = 60; //最低频 SinOsc.ar( LFNoise0.ar(freqRate, freqRange/2, freqRange + lowValue), 0, 0.3) }.play )
//更简明版 ( { var freqRate = 10, freqRange = 1200, lowValue = 60; SinOsc.ar( LFNoise0.ar(freqRate, freqRange/2, freqRange + lowValue), 0, 0.3) }.play )
一个使用变量的原因是简明。早先这个例子需要解释 LFNoise0 的引数。但变量已做了自我说明。使用具体的变量名,程序员不仅可以在代码中展示哪里有值在使用还可以告诉别人他们的意思。
变量同样可以被用于连续性。一个值,比如说一个基础频率可能在程序或patch内被使用上百次。使用变量确保当那个值被改变,也等于改变了整个patch中其他使用它的部分。
变量另一个重要的用途是连接引数。比如说,你可能希望高频响起时减低音量,而在低频响起时加大音量。你可能也需要一个基于基础频率成比例改变的滤波截止频率。你可能想要一条包络线的衰减率随着高频而减小(这发生在很多乐器身上)。如果没有一个普通变量的话,你只能手动更改它们。比如说,这里是对于同一声音的两组设置。
100 //低频
0.7 // 振幅
600 //滤波截止
2.0 //衰减率
1200 //高频
0.3 //较低振幅
7200 //较高截止
0.1 //衰减率
使用一个变量,它们便能通过表达式连接在一起。当频率改变,其他变量也相应改变。
var freq;
freq = 100 //频率可能改变
amp = freq/1200 //振幅对频率改变
cut = freq*600 //截止随频率改变
rate = 100/freq //衰减随频率改变
这是一个用一个变量串联两个参数的例子。SinOsc 正在控制第一个 SinOsc 的振幅。这个patch的想法是闪动的频率与我们听到的音调相关:更高的音会有更快的闪动。但之前三例并未使用变量,因此它们需要被明确地指出。第四例中,你仅需改变一个值,串联到它的变量便会改变所有参数。最终,将它们全部混合。
11.2. 申明变量, 赋值 和 注释
{SinOsc.ar(400, mul: max(0, SinOsc.ar(4)))}.play
{SinOsc.ar(100, mul: max(0, SinOsc.ar(1)))}.play
{SinOsc.ar(1000, mul: max(0, SinOsc.ar(10)))}.play
( { var frequency = 1000; // 改变这个值 SinOsc.ar(frequency, mul: max(0, SinOsc.ar(frequency/100))) }.play )
( { Mix.ar( { var frequency; frequency = exprand(50, 1000); Pan2.ar( SinOsc.ar(frequency, mul: max(0, SinOsc.ar(frequency/100))), 1.0.rand2 ) }.dup(5) )*0.5 }.play )
使用 Mul 和 Add 做 偏移(offset)和缩放(scale)
绝大多数 Ugen 生成 1 到 -1 或 0 到 1 之间的值。我们可以使用引数 mul 将波的振幅从+/-1 改变到 +/- 0.5, +/- 0.3 等等。结果是振幅或波的大小的改变。引数 mul 倍增或缩放波形。放大意味着更响的声音,缩小意味着更静。
引数 add 便宜 Ugen 的输出。它将中心从 0 改变到 add 的值。0.2 的 mul 将缩放范围,因此波将不再产生 +/-1 的值而是 +/–0.2 范围的值,中心值是 0。如果再加上 0.5 的 offset 结果会怎样呢?中间值将变为0.5 (0 + 0.5),底部将是0.3 (-0.2 + 0.5),顶部是0.7 (0.5 + 0.2)。下边是三个图示,分别展示了 add 和 mul 是如何改变一条 SinOsc 波形的。
为什么你要偏移一条波呢?先前我们学过高于1.0的值是不适用于振幅的。但你可能会注意到 LFNoise 有个值为400的 mul。通常,它会生成-1到1之间的值。放大400倍之后,它们变为+/-400。这么大的值如何用于一个patch中?也许给频率?
回忆一下我们之前那个 mul 的例子,SinOsc 只是吐出一系列数字。那些数字可以被数学表达式修改。下边是一个粗略描述三角波(以低采样率)的数字数组:
或
[ 0, 0.2, 0.4, 0.6, 0.8, 1, 0.8, 0.6, 0.4, 0.2, 0, -0.2, -0.4, -0.6, -0.8, -1, -0.8, -0.6, -0.4, -0.2, 0]
注意数字的走向是 0 到 1 到 0 到 -1,再到 0。这与 LFTri 生成的一系列数字类似。如果这些数字通通乘以500,则结果将变为:[ 0, 20, 40, 60, 80, 100, 80, 60, 40, 0, -20, -40, -60, -80, -100, -80, -60, -40, -20, 0]。实际上,我们可以让SC帮我们做数学:
11.3. 偏移和缩放
// 缩放一个数组 [0, 0.2, 0.4, 0.6, 0.8, 1, 0.8, 0.6, 0.4, 0.2, 0, -0.2, -0.4, -0.6, -0.8, -1, -0.8, -0.6, -0.4, -0.2, 0] * 100
// 偏移并缩放一个数组 [0, 0.2, 0.4, 0.6, 0.8, 1, 0.8, 0.6, 0.4, 0.2, 0, -0.2, -0.4, -0.6, -0.8, -1, -0.8, -0.6, -0.4, -0.2, 0] * 100 + 600
它有同样的形状和大小,但更大的缩放带来了更大的值。如果所有值都被通过加600进行偏移,结果将变为:[600, 620, 640, 660, 680, 700, 680, 660, 640, 600, 580, 560, 540, 520, 500, 520, 540, 560, 580, 600]。
为什么做偏移和缩放?这最后一串数组的数字不能被用给振荡器,因为我们的扬声器只接受-1到1之间的值。它给另外的Ugen的频率输入也许是对的,因为它们都被缩放和偏移为了正确的音高值。这是如何使用 mul 和 add 的一个例子。
缩放和偏移可能会引起混淆,因为Ugen输出的结果范围与实际的偏移和缩放是不同的。你用 0.5 的偏移 和 0.2 的缩放,但值的范围却是0.3 – 0.7。
下边的代码显示了300的偏移和100的缩放。但范围是200到400。在本例中,一个 SinOsc 被用作另一个 SinOsc 频率的引数。要搞清楚它是如何被应用的,首先要算出内部这个 SinOsc 生成的值。不是默认的-1到1,它被缩放到100到-100。偏移把100变作400,然后把-100变作200。因此范围是200到400。当频率为4时,Ugen将每秒从200到400移动4次。这将如何影响外部 SinOsc 的频率呢?
这一效果的术语叫做电压控制,今后将会详述。
11.4. 使用 Mul 和 Add 做 偏移和缩放
{SinOsc.ar(SinOsc.ar(4, 100, 300), 0, 0.3)}.play
这是SinOsc.ar(4, 100, 300) 一秒的图示。
偏移和缩放在某些Ugen上甚至可以变得更加使人混淆,比如 Env 和 EnvGen, 或者 LFPulse,因为它们的默认范围是0到1而不是-1到1。还可以更混淆,Max/MSP 有很少几个对象的默认范围是0.5到-0.5。因此缩放和偏移的影响是不同的。缩放200、偏移100的 EnvGen 最后的值是 100 到 300。如果这读起来很晕那么可以多读几遍。你不能仅是瞟一眼就继续往下读。缩放和偏移的概念被无限次的反复使用,不仅在SC中,而且还在其他合成程序中。
一个学生发现如下理解缩放是很有帮助的:
对于默认范围是1到-1的Ugen:
– add 是范围内的中心值。
– mul 是 高于、低于 中心的总量(并非二者之和,二者之和是范围)。
– 最低值是 add 减去 mul。
– 最高值是 add 加上 mul。
– 范围是 mul 乘以2。
对于默认范围是0到1的Ugen:
– add 是范围的低端(low end)
– mul 是低端之上的差值。
– mul 加上 add 是范围的高点。
– mul 即范围。
下边是可以帮你做这个计算的消息。它被称为 range(范围),它可以转换一个值的范围到另一个。引数即高和低的范围。大部分情况下,心算也很简单。
11.5. 映射一个范围
{ SinOsc.ar( SinOsc.kr(4).range(300, 500) ) }.play(s)
我反复重复第一个patch作为 mul 和 add 的例子,但这次拆分开因此你可以看到组件。第一行绘制了 LFNoise0 的波形。我们看到它是随机的阶梯噪音。用鼠标在plot窗口的阶梯上点点以证明它们全都是1到-1的值。第二行演奏这个patch。它听上去应该类似低频噪音。下一行是偏移1000缩放400后的同一patch图示。它与第一个图示看起来类似,但这次的值有更广的分布。用鼠标在plot窗口的阶梯上点点以证明它们全都是600(1000-400)到1400(1000+400)的值。这个范围对振幅没用,因为我们的扬声器仅能从-1运动到1,但600到1400的范围可以被拿给频率用。patch的最后一版便将 LFNoise0 放到了 SinOsc 频率的位置。那么 LFNoise0 多久传递一个新值给 SinOsc 呢?LFNoise0 在左声道频率为10 右声道频率为15,因此这也是在每个声道内移动到一个新值或新的音高的比率。
11.6. 第一个展示 mul 和 add 的例子
{LFNoise0.ar(2000)}.plot
{LFNoise0.ar(2000)}.play
{LFNoise0.ar(2000, mul: 400, add: 1000)}.plot
{SinOsc.ar(LFNoise0.ar([10, 15], mul: 400, add: 800), 0, 0.3)}.play;
练习
这个练习展示了变量是如何被使用的,以及一个Ugen如何可以在被偏移和缩放到适于特殊控制的方式。这个patch用20条正弦波塑造一个单独的声音。如果没有变量 fundamental,你将不得不为每个波输入频率。但这个变量却允许你生成基于一个频率的递增值。试着将它的值在10到300的范围内改变。同样试着改变泛音(partial)的数量。这个patch同样使用了一个引数 i,它与变量类似。它计数每个和声/谐波(harmonic)。要将每个闪动(flashing)谐波的速度与其数字相连,尝试将LFNoise1 (6) 第一引数换到(i + 1)。结果将是快速闪动的高层谐波以及慢速闪烁率的低层谐波。这是 James 写的,只是作为一个例子,被早期版本的SC收录。这是我第一次把SC作玩具时吸引我的很多个例子其中之一。
第二例同样描述了一个变量如何被用于连接两个参数:频率和衰减。每条正弦波都具备一个衰减时间的包络(下一章会讲)。一个随机频率被挑出,然后那个频率的衰减被计算为频率的函数,比如高音衰减短,低音衰减长。
11.7. 范例文件夹内的谐波漫游(harmonic swimming), 变量衰减铃音
// 取消注释部分做变化 ( // 谐波漫游 play({ var fundamental, partials, out, offset; fundamental = 100; // 基础频率 partials = 20; // 每轨泛音的数量 out = 0.0; // start of oscil daisy chain offset = SinOsc.kr(1/60, 0.5pi, mul: 0.03, add: 0.01); partials.do({ arg i; var p; p = fundamental * (i+1); // 泛音的频率 // p = exprand(20, 3000); // 变化 out = FSinOsc.ar( p, 0, max(0, // 将负的振幅转换到0 LFNoise1.kr( 6 + [4.0.rand2, 4.0.rand2], // 振幅率 0.02, // 振幅缩放 offset // 振幅偏移 ) ), out ) }); out // * EnvGen.kr(Env.perc(0, 4), Impulse.kr(1/4)) // 变化 }) )
// 用一个变量将衰减率与频率相连。 (低频; 长衰减. 高频; 短衰减.) ( { Mix.fill(15, { var freq; freq = exprand(100, 3000); Pan2.ar( SinOsc.ar( freq * LFNoise1.kr(1/6, 0.4, 1), mul: EnvGen.kr( Env.perc(0, (freq**(-0.7))*100), Dust.kr(1/5)) ), LFNoise1.kr(1/8) ) })*0.3 }.play )