SuperCollider:前戏 by Bruce Ding
SuperCollider是什么? SuperCollider是⼀一个⾯面向对象的(object-oriented)的编程语⾔言,⽤用来进⾏行声⾳音合成(sound synthesis)和数据信号处理(digital signal processing)。 SuperCollider包括两个部分:语⾔言(⽤用来输⼊入代码)+服务器(声⾳音合成和计算实际发⽣生的地 ⽅方)。这两个部分通过在UDP或者TCP上⼀一个叫做Open Sound Control(OSC)的协议进⾏行通 讯。也就是说,SuperCollider天⽣生就有在多个设备上协作的能⼒力,这对于现场表演来说显然很 具优势。当然,⼤大多数的时候这两个部分是在同⼀一个机器上运⾏行的。
开启服务器 如上所说,为了能够看到代码的效果,我们需要传输代码到服务器进⾏行运算,⼀一个最为⽅方便的 办法就是建⽴立和本地服务器的通讯。开启的方法是在菜单栏点击“Language-Boot Server”或 者使用快捷键“Cmd+B”。 因为SC默认的本地服务器变量是s,所以我们还可以⽤用如下的代码打开 s.boot; 或者停 ⽌止 s.quit; 服务器。当然,你也可以更加直⽩白⼀一点,将本地服务器称为“Server.local”,所以 上⾯面的代码也可以写成 Server.local.boot; Server.local.quit 。 在编译代码的时候,我们⼀一般会让本地服务器⼀一直处于运⾏行的状态。
Hello, World! 开启服务器之后,我们就可以开始尝试编译代码啦!这⾥里先简单讲讲SC3.6的编译界⾯面和运⾏行 代码的⽅方法。 打开程序之后,你看到的窗⼝口⾥里左边是代码输⼊入的地⽅方,右上⾓角是帮助窗⼝口,右下⾓角是Post Window(就是发布程序结果的地⽅方)。 如果要运⾏行⼀一⾏行代码,需要在代码⾏行⾥里的任何位置单 击“Enter”键。注意:Enter和Return键是不同的,在Mac上,Enter=Shift+Return。另外,需要 留意,SC并不会运行所有的代码,而只是运行你自己选中的代码或光标所在行。 我特别喜欢和别⼈人说的⼀一个笑话是,⽂文科⽣生学编程就是学会了⽤用⼀一百种⽅方法写“Hello, World!”。 可是不管怎么样,这仍然是⼀一个很好的练习,在SC⾥里,这仍然不出意外地简单: "Hello, World!".postln
如果⼀一切正常,你会在Post Window看到
Hello, World! Hello, World!
你之所以会看到两次,是因为SC总是会汇报它所进⾏行的最后⼀一个动作。
Make Some Nosies 既然我们已经和世界打过招呼,就不如继续go ahead and make some noise. :) 在SC⾥里,最⽅方便的造声办法就是使⽤用函数。⽐比如,尝试在SC⾥里运⾏行下⾯面的代码(记得先开启 本地服务器): { SinOsc.ar(440, 0, 0.1)}.play;
好了,如果⼀一切顺利,你应该能听到⼀一些声⾳音。如果想要停⽌止,使⽤用快捷键“cmd+.”。
面向对象的编程(OPP) 我们不妨在此时回过头看看之前提到过的“⾯面向对象(Object-oriented)”这个概念。“⾯面向对象 编程”是⼀一种计算机编程架构,它的⼀一条基本原则就是将计算机程序视作由单个能够起到⼦子程 序作⽤用的单元或对象组合⽽而成。为了实现整体运算,每个对象都能够接收信息、处理数据和向 其它对象发送信息。这样讲是不是有点难懂。⼀一个简单的对⽐比可能可以帮助你。我们常把⾯面向 对象和⾯面向过程这两种编程哲学进⾏行对⽐比。如果描述狼吃⽺羊这件事,⾯面向过程的写法将强调 吃,会写成 吃(狼,⽺羊) ,⽽而⾯面向对象的将强调对象,写成 狼.吃(⽺羊) 。当然,这两种写法都把 这件事情说清楚了,并没有什么本质区别。但如果我们要表述整个⾷食物链,两者的区别就出来 了。⾯面向过程的写法需要我们定义每⼀一种吃的可能,⽽而⾯面向对象的写法需要我们定义每⼀一个对 象。⾯面向对象的优势在于可以对对象进⾏行分类,每次增加对象的时候只要找到它在分类⾥里的位 置,便可以⾃自动“继承”某类对象的属性,⼤大⼤大⽅方便了我们对这种复杂系统的描述。也就是所谓 的“封装、继承、多态”的特点。(当然,⾯面向过程的写法也有其⾃自⾝身的优势,但这⾥里就不做展 开讨论了。)
函数 ⼀一般⽽而⾔言,⼀一个函数就是⼀一段可以被重复使⽤用的代码。在SC⾥里,函数也是⼀一种对象。⼀一个对 象可以接受我们给它的指令,这些指令被称为method(⽅方法,或称成员函数),能被某个 message调⽤用,它的基本格式是 someObject.someMessage 。对任⼀一对象来说,每个 message召唤/运⾏行⼀一种特定的method。但不同类型的对象也许会有同名的method,但可能会 对同样的message做出不同的回应。这⾥里我们涉及到了OPP⾥里⾮非常重要的特性。以“value”这个 消息为例,所有SC⾥里的对象都可以接受这个消息,这就意味着通过使⽤用这个消息,不同的对 象之间可以互换(⾄至少在能够返回⼀一些有意义的值这个意义上来说)。⽐比如说:
f={arg a; a.value+3}; f.value(3); g={3.0.rand;}; f.value(g)
另外,函数还有引数,也就是运⾏行时pass给函数的值。⽐比如说我们要做⼀一个加法的函数,我们 可以这样写 ( f={arg a,b; a+b;}; f.value(1,2); )
引数在函数的开始时⽤用“arg”来declare,之后你就可以像使⽤用变量那样引⽤用它们。 在SC⾥里,你 可以使⽤用关键词来指定引数值。⽐比如: f={arg a, b, c, d; (a-b)*c+d}; f.value(b:2, d:4, a:1, c:6);
注意:SC并不遵守四则运算的优先顺序,⽽而是按照先后顺序进⾏行运算的。 在SC⾥里,另⼀一个declare引数的⽅方法是使⽤用“|argument|”,⽐比如 f={arg a, b; a+b}; 和 g= {|a,b|a+b}; 是⼀一样的。 在函数⾥里,我们也可以使⽤用“var”来定义变量,⽐比如: ( f = { arg a, b; var firstResult, finalResult; firstResult = a + b; finalResult = firstResult * 2; finalResult; }; f.value(2, 3); // (2 + 3) * 2 = 10 )
变量只在⼀一定范围内有效。在函数内定义的变量只在该函数内有效,也就是定义函数时{}之间 的部分。⽐比如: f = { var xixi; xixi = 3; xixi; }; f.value;//有效 xixi; //⽆无效
当然,如果你在⼀一个代码块的开头定义了⼀一个变量,那么这个变量的有效范围就是这个块,也 就是()之间的部分。⽐比如: ( var myFunc; myFunc = { |input| input.postln; }; myFunc.value("foo"); myFunc.value("bar"); ) myFunc; // 超出范围,⽆无效
函数与类(class) 有了以上的基本知识之后,我们就可以回头来看之前的那段代码 { SinOsc.ar(440, 0, 0.1)}.play;
{}之间包括的是⼀一个函数,我们对这个函数进⾏行了play这个method,对这个函数来说,play意 味着evaluate⾃自⾝身并将结果在服务器上播放出来。如果没有指定特定的服务器,那么这个动作 将在默认服务器上进⾏行,也就是本地服务器。 让我们仔细看⼀一下{}⾥里发⽣生了什么:我们召唤了⼀一个叫“SinOsc”的东西,然后给它发了⼀一个 叫“ar”的消息,并给了⼏几个引数。实际上,“SinOsc”是⼀一个类,⼀一个令⼈人兴奋的概念。 ⼀一个对象就是⼀一些数据以及⼀一系列可对数据进⾏行的操作。也许有很多对象属于同⼀一个类型,它 们被叫做instance。类型本⾝身就是对象的类。⼀一个对象的类会定义它的数据(⼜又称instance变 量)和⽅方法。另外,它也可能会定义⼀一些专属的⽅方法和⼀一些被所有instance共享的数据,它们 被称作类⽅方法和类变量。 在SC⾥里,类的名字以⼤大写字母开头,所以是很容易辨认的。 类是你⽤用来⽣生成对象的,就像是某种模板。你可以⽤用⼀一些类⽅方法,⽐比如".new"来⽣生成新的对 象,在“SinOsc”⾥里,这个类⽅方法是".ar"(和".kr")。".ar"将返回⼀一个对象,⼀一个instance,⽽而 引数将影响这个对象的数据以及它的⾏行为。 好了,那么上边的代码的意思就是:命SinOsc⽣生成⼀一个⾃自⼰己的instance,这个instance的频率 是440赫兹,phase是0,mul是0.1。所有这样的对象在SC⾥里都被称作“unit of generators” 或 者“UGens”,也就是⽣生成声⾳音或者控制信号的对象。