基础
当你第一次运行SC,一个印有当前程序信息的窗口会随即打开。你可以忽略这一堆貌似莫名其妙的文字,但请不要关闭它,而将它移往一边,因为接下来,在这个窗口内会不断列印有用的信息,比如你的代码的错误及运行结果。这个窗口叫做post窗口。用这个窗口编辑和输入代码是可行的,但我更喜欢新开一个窗口。
SuperCollider 是一个集文本编辑器、编程语言、编译器以及数字合成器于一身的东西。程序有两个方面。client(客户端) 是关键的窗口,在这里你可以输入和发展代码。最后命令的结果被传送到 server(伺服器)端,在这里生成实际的声音。文本编辑功能(选择,复制,移动 等等)则与任何基础的编辑器类似。但在编辑代码时,有几个非常独特和有用的快捷键。
Com-, 走到行号
Com-/ 将选中的部分变成注释
Opt-Com-/ 选中部分取消注释
Shift-Com-B 平衡包围
Com-] 将代码右移
Com-[ 将代码左移
Com-. 停止一切回放
Shift-Com-/ (Com-?) 打开选中项目的帮助文档
Com-' 语法着色
双击包围 平衡包围(Balance enclosures)
Com-\ 前置post窗口
Shift-Com-K 清空post窗口内容
Shift-Com-\ 前置所有
评估所选代码按 enter 键(不是 return,而是 enter)。如果你仅需评估单行代码,则无需选中它,而仅需将鼠标光标放到这一行的任意位置。
所有编程语言都遵循使用“hello world”作为第一示例的滑稽规则。试试下面的代码,用我上边教你的方法。
//8.1. 你好世界 "Hello World";
"Hello World".speak;
"I've just picked up a fault in the AE35 unit. It's going to go a hundred percent failure within seventy-two hours.".speak;
注意每行的结尾都有一个分号,它表示一行的结尾。例 8.2 展示了更细节一些的例子。
//8.2. 音乐化数学 5 + 4;
(5 + 4).asString.speak;
(440 * 3/2).asString.speak;
("The frequency for E5 is" ++ (440 * 3/2).asString).speak;
("The frequency for middle C is " ++ (60.midicps.round(0.1).asString)).speak;
有一个称这个为”代码“的很好的理由。初学者觉得其隐晦难懂。这确实是一门全新的语言,就像法语或,中文,可能更恰当。但如同语言的发展,Smalltalk 很简单,读起来常常感觉就是英文。圆括号既意味着”这是一串值“又表明”先做它“,引号表示”这是一个字符串,或文本“。句号 表示”将这个消息传送到那个对象“。++表示”将这个和那个文本结合“。这里是上边示例最后一行的解释:将 MIDI数60 转化为midicps(每秒周数),然后 round(不好意思实在翻不出来…)那个值到0.1,将它转化为字符串,然后连接之前的”The frequency…”字符串,最后说出结果。
上边的代码不产生任何合成的声音,而这是我们的目的。但首先你得打开synth server(合成伺服器),它将代码命令解释为声音。有两个伺服器(荧幕左下角的两个小窗口),内部的(internal)和局部的(local)。现在暂时不用去管它们的区别。
每次使用中你仅需要开启伺服器一次即可。你可以点击伺服器上的boot按钮开启它们,也可以通过运行下边的代码实现。我偏爱用代码,因为它将伺服器命名为”s.“。除非特殊说明,否则本书全部范例将假设伺服器已运行,并为默认伺服器,并且它的名字是”s.”。运行下例试试。
//8.3. 开启伺服器 Server.default = s = Server.internal.boot;
在你运行第一个patch之前,你需要知道如何停止声音。Command+句号键 将停止所有回放(但不会关闭伺服器)。
//8.4. 第一个patch {SinOsc.ar(LFNoise0.ar([10, 15], 400, 800), 0, 0.3)}.play;
本例更复杂些,把代码全部选中(从 { 到 play)后按 enter 试听。
//8.5. 第二个patch { RLPF.ar( LFSaw.ar([8, 12], 0, 0.2), LFNoise1.ar([2, 3].choose, 1500, 1600), 0.05, mul: 0.4 ) }.play
错误信息
你会立刻注意到,编译器对于语法、拼写和标点的要求非常苛刻。每个句点、逗号、大小写和分号都需要被纠正。如果有不对劲的地方,程序将在数据窗口列印错误信息。又是,错误信息很难被解码。但一种信息往往非常清楚:它会准确的表明它错误的地方。这是一个典型的错误信息。
• ERROR: Parse error
in file 'selected text'
line 1 char 45 :
{SinOsc.ar(LFNoise0.ar([10, 15], 400, 800), 0 0.3•)}.play
-----------------------------------
• ERROR: Command line parse failed
”解析错误“(parse error)往往意味着误写。错误信息第三行告诉你文件中发生错误的位置。在本例中,第一行第45个字符是”•”。这便是解析错误的地方。
缺少了一个逗号,不要对错误信息失去信心。他们很常见。当我在写下边有个例子(大概12行代码)的时候,我不得不反复运行并修正了六次错误(不仅六个错误,而是运行它,修正它然后再运行它,如此反复六次)。我应该可能更仔细,但我想说的只是即使是我也会犯错。运行代码改正错误很简单,我并不认为这是一种错误,但我更愿意让程序为我检查代码。
对象,信息,引数,变量,函数和数组
学会在代码中识别这些元素很重要。
消息:小写的单词,有时独立存在,但通常用一个句点(.)与对象连接:play, scope, ar, max, rand, midicps。
引数:一串跟随消息之后、括入圆括号、以逗号间隔开的项目:(1, 2, 4), (“string”, 45, myVar), ({function}, 0.1, [1, 2, 3])
变量:用户定义的存储单元名称,以小写字母开始(非数字),然后接(无空格)大写字母开头的新词:pitchClass, nextCount, freqArray, myVar, a1, start4 等等。
包围:分别定义数组、引数和函数的方括号、圆括号和花括号:(0, 100, 555), {arg next; next = 100}, [100, 200, 500]。
函数:花括号内的一切东西:{arg next; next = 100}, {possibly many pages of code}, {SinOsc.ar(60.midicps)}
数组:一串括于方括号内被逗号分开的项目:[1, 2, 3], [“C”, “D”], [a, b, c]
对象:实际上,SC里的一切都是对象,但在接下来的几章里边,我们将专注于那些大写开头的万一,数字,括号包围的以及引号内的:Synth, SinOsc, LFNoise, EnvGen, [1, 2, 4], 5, 6.7, “string” 等等。
表达式:以引号结尾的一行代码。
Ugen:部件产生器(Unit Generator)是可以生成特定类型输出的引数串、对象和消息的结合体。Ugen 相互连接以创建一个patch:LFNoise0.ar(0.5), SinOsc.ar(440, 0, 0.4), Sequencer.ar(s, r, j)
包围(圆括号,花括号,方括号)
包围被用于将东西分组并表明执行顺序。括号必须成对出现,就是无论任何括号,在代码内只要有正括号就必须有其对应的反括号。待你编程一段时间之后,你便会习惯于配对括号。编译器从最里边的括号开始由内而外运行或评估代码。如果你的括号不配对的话,你将得到一条错误信息。当很多括号成对出现时,你会完全崩溃如下图:
一个辨清各个括号段的方式是使用缩排将各级区分,如例8.7。一对括号应该在缩排的同一层级。
// 8.7. 用缩排表示的平衡包围 { // 1 SinOsc.ar ( // 2 LFNoise0.ar ( // 3 100, 400, 800 ), // 3 0, 0.3 ) // 2 } // 1 .play
或者
{ // 1 SinOsc.ar( // 2 LFNoise0.ar( // 3 100, 400, 800 // 4 ), // 3 0, 0.3 ) // 2 } // 1 .play
按下快捷键 Shift-Com-B 或 双击任意一个包围是另一个区分括号区域的办法。SC将加深显示匹配包围内的内容。
再看一眼上边那个例子并练习识别我们上述讨论过的项目。我会先做第一个:对象是 SinOsc, LFNoise0, 函数以及所有数字。消息是 play 和 ar。函数和引数有点难认。全部{SinOsc … 0.3)}都是函数(所有在花括号之内的东西)。引数如果被嵌套的话将很难被辨识。认出它们的方法是,找到一个正括号后的消息,比如 .ar。所有在这对括号间的内容即引数。代码LFNoise0.ar(10, 400, 800)中,.ar 是消息,因此(10, 400, 800)即 ar消息 的三个引数。
引数
引数代表控制参数。它们的功能与模拟合成器上改变频率范围、滤波截止、振幅、音序器速度等等东西的旋钮。你可以将引数认为是旋钮想要指向的值。
以下面这个patch为例,LFNoise0.ar 的引数是 (10, 400, 800)。改变这三个值后运行代码,看看他们是如何改变声音的。为第一个引数试试15, 25, 30, 或40。为后两个引数试试100 和 700, 30 和 100, 1600 和 1700。
//8.8. 引数 {SinOsc.ar(abs(LFNoise0.ar(10, 400, 800)), 0, 0.3)}.play
你如何才能知道引数们代表的、以及对声音产生怎样的影响?第一步是查阅帮助文档:高亮你想要获得帮助的项目然后按下 Shift-Com-/ (比如 Com-?)。
现在我们已辨认了LFNoise0.ar的引数,跟着SinOsc的ar消息后的引数又是什么呢?他们都在SinOsc.ar之后被圆括号包围。注意这个串的第一引数是我们刚刚研究的全部代码:LFNoise0.ar(10, 400, 800)。第二引数是0,第三引数是0.3。这叫做嵌套。LFNoise0.ar(10, 400, 800) 被嵌入为 SinOsc.ar 的第一引数。
还有一种对象和消息需要识别。函数是一个对象,play是消息。
Sandwich.make
面向对象语言的术语更难,因为对象名和消息常常是隐晦的缩写(Synth, PMOsc, LFNoise0, midicps, rrand 等)。因此我将使用熟悉的假想对象和消息来解释它们是如何工作的。(这些例子无法在SC中使用!)如果你觉得这些关系没问题的话,你可以跳过这一节。
假设我们有一台虚拟三明治机以及豆腐肉,它们全都懂得 smalltalk 命令。我将称这俩假想对象为 Sandwich 和 Tofu(三明治和豆腐)。
每个对象都懂得一个消息集。消息告诉对象做什么。同样地,同样有很多对象懂得一切给予它的指令。面向对象语言即可以让你混合及匹配消息和对象。
比如说,让我们假设三明治懂得三条消息:make(做),cut(切) 和 bake(烤)。豆腐也明白三个消息:bake,fry(炸)和 marinate(卤)。把消息做送至三明治的语法像这样:
Sandwich.make;
举一反三,如果你要烤豆腐的话,应该写:
Tofu.bake;
你可能会考虑给make和bake加上一些引数以描述你想让三明治如何做,豆腐如何烤。实际上不需要。绝大多数消息都在代码内有自建值,因此你可以留空它们。试着分别运行下面两行。
// 8.9. 使用默认值和引数的SinOsc {SinOsc.ar}.play
{SinOsc.ar(800, 0, 0.1)}.play(s, 0, 10)
第一个结果是一个440Hz,振幅1.0,声相0,在服务器“s”上,输出线0,10秒衰减的正弦音。这些是SinOsc和play的默认值。通常你会使用一两个默认值,但很少会全部使用默认值。
在添加引数前,我们要知道他们分别是什么意思。你可以使用帮助文档。
在每个帮助文档中,都是全部被对象懂得的消息的原型,以及一串消息使用的引数。Sandwich 和 Tofu 可能会被这样记录:
Sandwich
*make(vegArray, bread, meat)
*cut(angle, number)
*bake(temp, rackLevel)
Tofu
*bake(temp, baste, rackLevel)
*fry(temp, length, pan, oil)
*marinate(sauce, time)
了解引数随不同对象而改变是很重要的。即,用于三明治的bake消息有两个引数,当用于豆腐,便有三个。不了解此点,并对不同对象的相同消息使用同样的引数是一个普遍的初学者错误,并将产生意料外之外的结果。当bake被用于Sandwich,比如在Sandwich.bake(20, 2)中,2是 rack level(不知道是什么东西),但在Tofu.bake(20, 2)中,2 是 抹调料的时间。记住:引数串在对象间是不可互换的。
现在我们明白了Sandwich.make的引数是什么,可以在这个模拟代码内做一个三明治了。
Sandwich.make([lettuce, tomato, pickle], rye, chicken)
或
Sandwich.cut(90, 1)
以及
Tofu.marinate(peanut, 160)
第一行将用一个蔬菜的数组、面包和鸡做三明治。第二行将以90度切三明治一刀。然后豆腐在花生酱里浸泡160分钟。
SC 和面向对象语言的另一强大的方面是所有东西都是一个对象,并且你可以任意替代它们。我可以用整个Tofu的代码块替代chicken。
Sandwich.make([lettuce, tomato, pickle], rye, Tofu.marinate(peanut, 160))
同样的,marinate的第二引数可以用rrand(20, 100)替代,这会选择一个20到100之间的值。
Sandwich.make(
[lettuce, tomato, pickle],
rye,
Tofu.marinate(peanut, rrand(20, 100))
)
当程序评估代码时,它是从最里边的部分开始,然后用它的值继续外部嵌套。用语言阐述,上例可以如此解读:将豆腐在花生酱里浸泡20到100之间的某个值。之后,将它用作三明治的第三引数,并与莴苣、番茄和泡菜、黑面包一起使用。
联系消息是可能的。比如你可以这么搞:Sandwich.make.bake.cut。一个对象可以被用作同样对象另一实例的一个引数。比如说,你可以写 Tofu.marinate(Tofu.marinate(peanut, 60), 60)。假若这样,一批豆腐将在花生酱内浸泡60分钟,另一批豆腐将在这批豆腐内浸泡60分钟(崩溃吗?)。
用一个Patch做试验
下边有两个patch。查阅每个对象的帮助文档。你也许并不能完全领会全部属于,比如 mul 和 add,以及它们将如何影响整个patch,但这没太大关系。现在,我们知道,LFNoise0 以每秒10次的速度产生400到1200之间的值。这将如何适应这个patch?查阅一下 SinOsc 的文档你将看到第一引数为 freq。整个LFNoise0.ar(etc.)正被用作 SinOsc 的频率引数。为证实这点,试着用一个静态值比如300替换掉LFNoise0.ar(10, 400, 800)。这样的话,你将听到一个单一的音高,300Hz。
查阅第二patch的帮助文档。试试你能否改变实际的声音。试着在你运行代码前预测你的改变将如何改变声音。
//8.10. 用一个patch做试验 {SinOsc.ar(abs(LFNoise0.ar(10, 400, 800)), 0, 0.3)}.play
( // 选取圆括号内的所有东西 { RLPF.ar( LFSaw.ar([8, 12], 0, 0.2), abs(LFNoise1.ar([2, 3].choose, 1500, 1600)), 0.05 ) }.play )
第二个patch里的LFSaw将一个数组([8, 12])用作频率引数。第一个值用于左声道,另一个用于右声道(稍后详述)。LFNoise1 生成 100 到 3100 间的值并被用于 RLPF(共振低通滤波器)的截止频率(cutoff)。LFSaw是输入频率。
任意地试验吧。这是我喜欢用SC进行教学的原因。当用老的模拟合成器工作时,输入不正确的值或造成连接错误(比如输出到输出)会破坏设备。但在SC中,你可以尝试几乎所有东西。的确存在由于音量过大毁坏机器或得到错误信息或可能损伤你的耳朵的风险。但除此以外,你可以随心所欲。
这里是另外一个用代码工作的好处:它是文件。如果你在变化的过程中发现结果有趣你可以轻易的将之存储为一个文件。用下面这个patch进行试验试试。
练习
//8.11. 上升的气泡 ( // 选中两个圆括号中的一切 { CombN.ar( SinOsc.ar( LFNoise1.kr( 4, // 低频振荡器,LFO 24, // MIDI里的范围 LFSaw.kr( [8,7.23],// 第二个LFO 0, 3, // MIDI里的范围 80 // MIDI里的偏移 ) ).midicps, 0, 0.04 ), 0.2, // 最大延迟 0.2, // 实际延迟 4 // 衰减 ) }.play )