Groups
server에서의 synth들의 순서에 대한 이야기는 자연스럽게 group에 대한 주제로 연결됩니다. server에 있는 synth들은 node라고 불리는 형태(type)입니다. node라는 형태에는 또다른 것이 있는데, 그것이 바로 group입니다. group은 단순히 말해 node들의 모음집입니다. group은 synth들을 담을 수도 있고, group들을 담을 수도 있고, 그 둘을 혼합해서 담을 수도 있습니다. group은 보통 두 가지 유용한 점이 있습니다. 첫째로, 순서를 제어하는데 매우 도움이 됩니다. 둘째로, group을 통해 여러분은 여러 node들을 하나로 묶을 수 있고, 그 group에 많은 메세지를 한 번에 보낼 수 있습니다. 어쩌면 이미 여러분이 떠올리셨듯, Server에 있는 group이라는 node를 재현하는, client 측 어플리케이션이 있습니다. 바로 Group입니다.
Groups as Ordering Tools
Group은 순서를 바꾸거나 제어하는데 매우 유용합니다. synth와 마찬가지로 Group도 타겟과 addAction을 arg로 가집니다. 그럼으로써 Group을 제 위치에 넣기 쉽게 해주죠.
g = Group.new;
h = Group.before(g);
g.free; h.free;
이것은 이펙트나 다른 프로세스들을 사운드 소스로부터 분리시키고, 그것들에 알맞은 순서를 배정하는 등의 일을 할 때, 매우 유용하지요. 지난번 튜토리얼에서 보았던 리버브 예제를 다시 한 번 봅시다.
(
// a stereo version
SynthDef(\tutorial_DecaySin2, { arg outBus = 0, effectBus, direct = 0.5, freq = 440;
var source;
// 1.0.rand2 returns a random number from -1 to 1, used here for a random pan
source = Pan2.ar(Decay2.ar(Impulse.ar(Rand(0.3, 1), 0, 0.125), 0.3, 1,
SinOsc.ar(SinOsc.kr(0.2, 0, 110, freq))), Rand(-1.0, 1.0));
Out.ar(outBus, source * direct);
Out.ar(effectBus, source * (1 - direct));
}).send(s);
SynthDef(\tutorial_Reverb2, { arg outBus = 0, inBus;
var input;
input = In.ar(inBus, 2);
16.do({ input = AllpassC.ar(input, 0.04, Rand(0.001,0.04), 3)});
Out.ar(outBus, input);
}).send(s);
)
// now we create groups for effects and synths
(
~sources = Group.new;
~effects = Group.after(~sources); // make sure it's after
~bus = Bus.audio(s, 2); // this will be our stereo effects bus
)
// now synths in the groups. The default addAction is \addToHead
(
x = Synth(\tutorial_Reverb2, [\inBus, b], ~effects);
y = Synth(\tutorial_DecaySin2, [\effectBus, ~bus, \outBus, 0], ~sources);
z = Synth(\tutorial_DecaySin2, [\effectBus, ~bus, \outBus, 0, \freq, 660], ~sources);
)
// we could add other source and effects synths here
~sources.free; ~effects.free; // this frees their contents (x, y, z) as well
~bus.free;
// remove references to ~sources and ~effects environment variables:
currentEnvironment.clear;
우리가 group들 안에 있는 소스나 이펙트들이 어떤 순서였는지 신경쓰지 않았을 수도 있었다는 것에 유의하세요. 중요한 것은 모든 이펙트 synth들이 모든 소스 synth들보다 뒤에 온다는 것입니다.
여러분은 어쩌면 ‘~source’나 ‘~effect’처럼 변수의 이름 앞에 ‘~’를 붙인 것에 대해 궁금해 하실 수도 있겠습니다. 이렇게 변수명 앞에 ‘~’를 붙이는 것은 ‘환경변수(environment variable)’를 만드는 것입니다. 지금 단계에서 환경변수에 대해서는 딱 두 가지만 기억하시면 됩니다. 환경변수가 인터프리터 변수와 동일한 방식으로 사용된다(사용에 앞서 선언될 필요도 없고, 영구적이지요.)는 것과 보다 이해하기 쉬운 이름을 붙일 수 있다는 것이죠. (자세한 내용이 필요하시면 variable definitions 문서와 Functions 문서를 참조하십시오.) 사용하지도 않는 환경변수를 너무 많이 만들어놓으면, 그로 인한 버그가 발생할 수 있고, 이런 경우 찾기가 매우 어려워지지요. 그러니 프로젝트를 끝낼 때에는 혼선을 방지하기 위해 반드시 위의 예제에서와 같이 currentEnvironment.clear를 해주십시오.
// to be sure, create a new Environment:
Environment.new.push;
// some code..
// restore old environment
currentEnvironment.pop;
All the addActions
남아있는 addAction들을 살펴보기에는 지금이 좋은 시점일 듯 싶습니다. \addBefore와 \addAfter에 더하여, (거의 잘 쓰이지 않는) \addReplace와, Group에 적용하는 \addToHead와 \addToTail이 있습니다. \addToHead는 group 안에서 가장 처음에 위치시켜, 가장 처음으로 실행될 수 있게 해줍니다. \addToTail은 group의 마지막에 위치시켜, 가장 마지막에 실행될 수 있게 해주지요. \addToHead와 \addToTail은 각각 head와 tail이라는 편리한 method에 대응됩니다.
g = Group.new;
h = Group.head(g); // add h to the head of g
x = Synth.tail(h, \default); // add x to the tail of h
s.queryAllNodes; // this will post a representation of the node hierarchy
x.free; h.free; g.free;
‘queryAllNodes’ and node IDs
server는 queryAllNodes라는 이름의 method를 가지고 있습니다. 이것은 post window에 server에 있는 node들을 트리형식으로 보여줍니다. 위의 예제를 실행시켰을 때, 여러분의 post window에는 아래와 비슷한 형태의 결과가 나타났어야 합니다.
nodes on localhost:
a Server
Group(0)
Group(1)
Group(1000)
Group(1001)
Synth 1002
post window에 프린트된 Group을 보시면, 오른쪽으로 띄어쓰기가 들어간 모든 것이 그 안에 들어있다는 뜻입니다. node의 순서는 위에서부터 아래로 가지요. node 옆에 있는 숫자를 가리켜 node ID라고 합니다. node ID를 통해 server는 node들을 추적할 수 있습니다. 보통 Server 안의 객체들에 대해서만 작업할 때에는 node ID를 몰라도 적당한 때에 변수에 할당하거나 놓아주기만 하면 되지요.
그런데, 우리는 두 개의 group만을 만들었을 뿐인데, 어째서 post window에는 네 개의 group이 있는 것일까요? 앞의 두 개 즉, ID가 0인 것과 1인 것은 특별한 group들입니다. 첫째 것을 RootNode라 하고, 둘째 것을 default group이라 합니다.
The Root Node and the Default Group
server 어플리케이션이 부트되면 node ID가 0인 특별한 group이 만들어집니다. 이것은 server가 가진 node 트리의 최상위를 나타냅니다. 이 특별한 server의 객체를 재현하는 것이 바로 RootNode라 불리는 것입니다. 또한, node ID가 1인, default group이라는 것도 만들어집니다. 이것은 모든 node들의 기본 타겟이 됩니다. 그러므로 여러분이 Server를 타겟으로 잡을 때, 바로 이것을 가리키게 되지요. 여러분이 만약 타겟을 명시하지 않으면, default Server의 default group을 얻게 됩니다.
Server.default.boot;
a = Synth.new(\default); // creates a synth in the default group of the default Server
a.group; // Returns a Group object. Note the ID of 1 (the default group) in the post window
default group이 있음으로써, 예측 가능한 기본적인 node 트리를 제공할 수 있습니다. 이러한 기본적인 node 트리는 Server.scope나 Server.record 등과 같은 method들이 실행순서와 관련된 문제와 상관없이 실행될 수 있게 합니다. (이러한 method들은 모든 것들 뒤에 와야할 node들을 만들어내죠.) 아래의 예제에서 scope의 node는 default group 뒤에 오게 됩니다.
Server.internal.boot;
{ SinOsc.ar(mul: 0.2) }.scope(1);
// watch the post window;
Server.internal.queryAllNodes;
// our SinOsc synth is within the default group (ID 1)
// the scope node ('stethoscope') comes after the default group, so no problems
Server.internal.quit;
일반적으로 여러분은 default group이나 default group 안에 포함된 group들에만 node를 더할 수 있습니다. default group 전이나 그 후에 넣을 수는 없지요. 예를 들어 이펙트 synth를 넣을 때, 여러분은 이 synth를 default group 뒤에 넣고 싶은 유혹을 떨쳐내야 합니다. 오히려 default group 안에 서로 다른 두 개의 group을 만들어야겠지요. 이렇게 해야 scope나 record에서 일어날 수 있는 문제들을 미리 방지할 수 있습니다.
default group [
source group [
source synth1
source synth2
]
effects synth
]
recording synth
Groups as, well, groups…
또다른 group의 대표적인 사용법은, 다양한 synth들을 한번에 묶어 사용하는 것입니다. 이렇게 묶은 group에 여러분이 만약 set이라는 메세지를 보낸다면, group 안에 있는 모든 node들에게 그 메세지가 보내집니다.
g = Group.new;
// make 4 synths in g
// 1.0.rand2 returns a random number from -1 to 1.
4.do({ { arg amp = 0.1; Pan2.ar(SinOsc.ar(440 + 110.rand, 0, amp), 1.0.rand2) }.play(g); });
g.set(\amp, 0.005); // turn them all down
g.free;
Groups, their Inheritance, and More on Tracking Down Help
이제 객체지향 프로그래밍 언어의 이론을 좀더 들여다 봅시다. Group과 Synth는 둘다 subclass라 불리우는 것들의 예들입니다. 여러분은 subclass를 부모클래스의 자녀라고 생각할 수 있습니다. 부모클래스를 subclass의 superclass라 부르지요. 모든 subclass들은 자신의 superclass들의 method들을 상속받습니다. subclass들은 (다형성의 장점을 취해, 자신만의 완성으로써) 어떤 method들을 재정의하기도 합니다. 그러나 일반적으로 subclass들은 자신들의 superclass의 method들에 대응합니다. 어떤 클래스들은 추상 클래스(abstract class)입니다. 추상 클래스는 여러분이 실제로 그 클래스의 instance를 만들지 않는다는 뜻입니다. 그 대신, 추상 클래스는 단지 자신들의 subclass들에게 method들이나 변수들을 제공하기 위해 존재합니다.
예를 들어, 우리는 몇 개의 subclass들을 가진, Dog라는 이름의 추상 클래스를 상상해 볼 수 있습니다. Dog의 subclass들 중에는 Terrier나 BassetHound 등과 같은 것이 있을 수 있겠지요. 이들 클래스들은 공통적으로 ‘달리기(run)’이라는 method를 가질 수 있습니다. 하지만 모두가 ‘양치기(herdSheep)이라는 method를 가질 필요는 없지요.
이러한 방법은 확실한 장점이 있습니다. 만약 여러분이 상속된 method를 변경할 필요가 있을 때, 한 번만 바꿈으로써 그 아래의 모든 subclass들도 한꺼번에 바꿀 수 있다는 것입니다. 마찬가지로, 어떠한 클래스를 여러분 자신만의 버전으로 변화시키거나 확장시키고 싶을 때, 여러분은 자동적으로 superclass의 모든 기능성을 얻을 수 있다는 것이지요.
상속은 몇 단계 뒤로 거슬러 올라갈 수 있습니다. 어떠한 클래스의 superclass는 자신의 superclass를 가진다는 것입니다. (그러나 하나의 subclass가 바로 윗단계에서 여러 개의 superclass를 가질 수는 없습니다.) 사실, SC에서의 모든 객체는 Object라는 클래스로부터 상속된 것입니다. 이 Object는 그 아래의 subclass들이 상속받거나 재정의한, 일정한 method들을 정의하고 있지요.
Group과 Synth는 Node라는 추상클래스의 subclass입니다. 그 때문에, Group과 Synth의 method들 중 일부는 Node에 정의되어 있습니다. 그리고 (사실 이것이 가장 중요한데) Node의 도움말 파일에서 문서를 찾을 수 있지요.
그러니 만약 여러분이 도움말을 보았는데도, 어떠한 method나 class에 대한 설명을 찾을 수 없다면, 여러분은 어쩌면 그 클래스의 superclass의 도움말을 보아야할 수도 있습니다. 물론 연쇄적으로 더 거슬러 올라가야할 수도 있지요. 대부분의 경우, 해당 클래스들의 superclass에 대한 리스트를 해당 클래스의 도움말 맨 위에 적어놓습니다. 여러분은 또한, 다음과 같은 방법으로 도움말을 추적해갈 수 있습니다. (post window를 보세요.)
Group.superclass; // this will return 'Node'
Group.superclass.openHelpFile;
Group.findRespondingMethodFor('set'); // Node-set
Group.findRespondingMethodFor('postln'); // Object-postln;
Group.helpFileForMethod('postln'); // opens class Object help file