Busses
자, 이제 bus에 대해 좀더 살펴봅시다. bus는 아날로그 믹싱 데스크 안에서의 bus나 send에 따라 이름지어집니다. 물론, 비슷한 역할을 하지요. 한 곳에서 다른 곳으로 신호 루팅하는 것입니다. SC에서 ‘한 곳에서 다른 곳으로’라 함은, 오디오 하드웨어로부터의 루팅이나, 오디오 하드웨어로의 루팅 뿐만 아니라, 서로 다른 synth들 사이에서의 루팅 역시 일컫습니다. bus는 보통 두 개의 형태로 옵니다. 하나는 ar이고, 나머지 하나는 kr입니다. 여러분이 짐작하시듯, 전자는 audio rate signal들을 루팅하고, 후자는 control rate signal들을 루팅하지요.
kr bus들은 이해하기 쉽습니다. 각각이 번호표(index number)를 의미합니다. 그 번호표는 0에서 시작하지요.
ar bus들도 비슷합니다만, 조금의 부가설명이 필요합니다. server 어플리케이션들은 정해진 숫자만큼의 output/input 채널들이 있습니다. 이것들은 첫번째 audio bus들에 대응합니다. 항상 output이 input보다 앞에 옵니다.
예를 들어, 스테레오 채널을 떠올려봅시다. server에 두 개의 output과 두 개의 input이 있는 것이죠. 그렇다면, 첫번째 두 audio bus(index 0과 index 1)가 output이 되고, 그 바로 다음의 두 audio bus(index 2와 index 1)이 input이 되는 것입니다. 오디오 출력을 하나의 output bus에 쓰는 것은, 스피커를 통해 소리가 나게 합니다. input bus를 통해 오디오 입력을 SC에 읽어들임으로써 녹음이나, 실시간 프로세싱 같은 것들을 할 수 있게 되죠.
bus에서 아직 설명하지 않은 것이 남았습니다. 바로 private bus입니다. private bus는 다양한 synth들 사이에서 ar이나 kr을 주고 받습니다. 오디오 출력을 private bus로 보내면, 같은 출력을 다른 output bus로 보내지 않는 이상, 스피커에서는 아무런 소리도 나지 않습니다. 이 private bus는 종종 ‘effects send’ 처럼 사용됩니다. output으로 가기 전에 다른 과정을 거쳐야 하는 경우처럼요.
server가 boot 될 때, input과 output의 갯수가 정해지는데, 그 갯수만큼의 bus를 사용할 수 있습니다. (ServerOptions 라는 문서를 보시면 input, output의 갯수 즉 bus의 갯수를 정하는 방법에 대한 정보를 보실 수 있을 겁니다.)
Writing to or Reading from Busses
우리는 이미 Out.ar을 보아왔습니다. Out.ar을 통해 우리는 오디오 출력을 bus에 쓸 수 있었지요. Out.ar이 두 개의 arg를 갖는다는 것 기억하세요? 첫번째 것은 번호표 즉, index이구요, 둘째것은 출력입니다. 출력에는 하나의 UGen이 올 수도 있고, UGen들을 묶은 배열이 올 수도 있습니다. 배열이 올 경우는 멀티 채널을 의미하는 것이구요.
bus로부터 입력을 읽어들이려면 다른 UGen이 필요하겠죠? 바로 In입니다. In의 ar이라는 method도 Out.ar에서와 같이 두 개의 arg를 받습니다. 첫번째는 Out.ar에서와 같이 번호표입니다. 두번째 것은 입력 채널의 갯수입니다. 만약 입력 채널의 갯수가 1보다 크다면, In이라는 UGen의 출력은 배열로 나옵니다. 다음의 예를 실행해보세요. 그리고 post window를 지켜보세요.
In.ar(0, 1); // this will return 'an OutputProxy'
In.ar(0, 4); // this will return an Array of 4 OutputProxies
OutputProxy는 특별한 종류의 UGen입니다. 이 UGen은 어떤 신호에게 빈 공간을 마련해주는 역할을 합니다. 그래서 나중에 synth가 실행될 때 나올 수 있도록 말이지요. 여러분은 어쩌면 앞으로도 절대 이 UGen에 대해 직접적으로 공부해야할 필요가 없을 수 있습니다. 그러니 OutputProxy에 대해선 걱정마세요. 다만 여러분이 이 UGen을 post window나 다른 곳에서 만나게 되었을 때, ‘그런 게 있었지’ 정도로만 알 수 있을 정도면 됩니다.
In과 Out 두 UGen은 kr이라는 method도 가집니다. kr은 control rate 신호들을 bus들 사이에서 읽어들이거나 쓸 때 사용되지요. Out.kr은 audio rate 신호를 control rate 신호로 변환합니다. 이것을 다운샘플링(downsampling)이라고 합니다. 주의하실 것은, control rate 신호는 audio rate 신호로 변환될 수 없습니다. Out.ar의 두번째 arg에는 kr은 못 오고, ar만 올 수 있다는 것입니다.
// This throws an error. Can't write a control rate signal to an audio rate bus
{Out.ar(0, SinOsc.kr)}.play;
// This will work as the audio rate signal is downsampled to control rate
Server.internal.boot;
{Out.kr(0, SinOsc.ar)}.scope;
(그러나 이런 제한은 audio rate의 UGen들 사이에서 일반적인 것은 아닙니다. 오히려 대부분의 UGen들은 control rate 신호들을 자신의 arg로 받아들입니다. 어떤 UGen은 심지어, 보간법(interpolation)이라 불리는 방법을 통해 여분의 값들을 채워넣으면서, control rate 입력을 audio rate 입력으로 변환하기도 합니다.)
여러 개의 Synth들을 하나의 bus에 쓰게 될 경우, 출력이 합쳐진다는 것을 즉, mix된다는 것을 보시게 될 겁니다.
(
SynthDef("tutorial-args", { arg freq = 440, out = 0;
Out.ar(out, SinOsc.ar(freq, 0, 0.2));
}).send(s);
)
// both write to bus 1, and their output is mixed
x = Synth("tutorial-args", ["out", 1, "freq", 660]);
y = Synth("tutorial-args", ["out", 1, "freq", 770]);
Creating a Bus Object
SC에는, server의 bus를 재현하는, 아주 편리한 client쪽 객체가 있습니다. 바로 Bus입니다. bus에 대해 여러분이 필요한 것은 결국 In, Out 두 UGen과 번호표(index)지요. 여러분은, 매우 세심하게 발전된 Bus라는 UGen이 왜 필요한지 의아해하실 수도 있을 것입니다. 물론, 만약 여러분이 하시는 것이 오직 오디오 입력과 출력만을 연주하는 것이라면, 대부분의 경우 여러분은 필요하지 않을 것입니다. 그러나 Bus라는 UGen은 유용한 기능을 제공합니다. 우리는 조금 있다가 이 기능에 대해 살펴볼 것입니다. 하지만 지금은 먼저 이 객체를 어떻게 만드는 지부터 살펴봅시다.
많은 UGen들이 ar과 kr을 method로 가지고 있듯, Bus도 매우 일반적으로 사용되는 두 개의 method가 있습니다. Bus.audio와 Bus.control입니다. 이 두 method들은 각각 두 개의 arg를 가집니다. 첫째것은 Server 객체이구요, 둘째것은 채널의 갯수입니다.
b = Bus.control(s, 2); // Get a two channel control Bus
c = Bus.audio(s); // Get a one channel private audio Bus (one is the default)
여러분은 어쩌면 ‘two channel’ bus가 무엇인지 궁금하실 수 있겠네요. 아직 한 번도 다루지 않았으니까요. 하지만 기억 나세요? Out.ar이나 Out.kr의 두번째 arg에 배열이 올 경우, 배열이 멀티채널을 의미하게 된다는 것 말입니다. 아래의 예제를 보시면 확실히 기억나실 겁니다.
(
SynthDef.new("tutorial-SinOsc-stereo", { var outArray;
outArray = [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)];
Out.ar(0, outArray); // writes to busses 0 and 1
}).play;
)
사실 멀티채널 버스에 해당하는 하나의 무언가가 있는 것은 아닙니다만, Bus는 연속된 bus들의 묶음을 재현할 수 있습니다. 인접한 몇 개의 server 측 bus들을 하나의 Bus 객체로 집어넣어, 하나의 그룹을 다루는 것처럼 다룰 수 있게 해주죠. 그래서 편리하다고 한 것입니다.
private bus라고 불리는 것들(input과 output 채널 외의 모든 것, 모든 control bus들은 모두 private이겠지요)을 다룰 때, 여러분이 사용하는 그 bus가 어디에 어떻게 사용되는지 확실히 하고 싶으실 겁니다. 그렇게 하려면 서로 분리를 시켜야겠지요. 여러분은 이렇게 분리시키는 것을 세심하게 고려하여 각각의 색인(indices)을 붙여가며 사용하실 수 있습니다. 하지만 Bus는 이것들을 자동적으로 해버립니다. 각각의 Server 객체는 bus 할당기(allocator)를 가지고 있습니다. 그리고 여러분이 Bus 객체를 만들 때, 이 할당기가 private 색인을 Bus를 위해 예약해둡니다. 그리고 Bus로부터 놓이기 전에는 예약해둔 색인을 놓지 않습니다. 여러분은 Bus가 할당받은 색인이 무엇인지 확인하기 위해 index라는 method를 사용할 수 있습니다. 그러나 보통은 이 값을 저장해둘 필요가 없습니다. Bus 객체의 instance로서 UGen의 입력이나 Synth의 arg로 바로 들어갈 수 있거든요.
s.reboot; // this will restart the server app and thus reset the bus allocators
b = Bus.control(s, 2); // a 2 channel control Bus
b.index; // this should be zero
b.numChannels // Bus also has a numChannels method
c = Bus.control(s);
c.numChannels; // the default number of channels is 1
c.index; // note that this is 2; b uses 0 and 1
이렇게, Bus 객체를 사용하여 인접한 bus들을 재현(represent)함으로써, 여러분은 bus들이 서로 충돌하지 않으리란 걸 확실히 할 수 있습니다. 할당된 색인은 동적이기 때문에, 여러분은 여러분의 code 안에서 Bus의 채널들의 갯수를 바꿀 수도 있습니다. 그렇다 하더라도 여전히 bus들은 절대로 충돌하지 않습니다. 만약 여러분이 번호표(index number)를 이용하여 수동으로 bus를 할당했다면, 인접한 채널에 다른 버스를 할당하기 위해, 모든 bus들의 번호표를 수정해야겠지요. 색인은 연속되어야 하니까요. 이것은 객체의 특별한 장점에 대한 훌륭한 예입니다. 번호표 할당과 같은 것들을 하나로 묶고, 계층적으로 추출할 수 있게 함으로써, 여러분의 코드를 좀더 유연하게 만들어준다는 것입니다.
여러분은 Bus가 할당받아 사용하고 있는 색인을 free라는 method를 호출함으로써 놓아줄 수 있습니다. 놓아진 색인은 다시금 할당될 수 있겠지요.
b = Bus.control(s, 2);
b.free; // free the indices. You can't use this Bus object after that
이렇게 free라는 method를 호출하는 것이, 실질적으로 server에서 bus가 사라지게 하는 것이 아니라는 것에 주의하세요. bus는 여전히 server에 있습니다. free는 bus 할당기에게, 이 순간 해당 bus에 대해서는 더이상 사용할 필요가 없다는 사실을 알려줄 뿐입니다. 그리고 다른 번호표에 다시 할당될 자유를 bus에 주는 것이기도 하지요.
지금 여기에 private audio rate 버스를 이용하는 것에 대한 또다른 장점이 있습니다. 위에서 말씀드렸다시피, 처음 몇 개의 bus들은 입출력 채널들입니다. 그러니 만약 우리가 첫번째 private bus를 사용하고 싶다면, 우리는 이걸 다 더해야 되는 거죠? 우리의 server 어플리케이션이 2개의 출력과 2개의 입력의 채널들을 가졌다고 생각해봅시다. 첫번째 private audio bus는 4번입니다. (0, 1, 2, 3 … 4번인거죠!) 그러니 코드를 쓸 때, Out UGen에 적절한 index arg는 4라는 것입니다.
하지만 만약 우리가 나중에 출력 채널을 6개로 늘이기로 마음 먹었다면 어떻게 하지요? 이제 우리의 private bus에 쓰인 모든 것은 출력 채널 중 하나로 나가게 되어버렸습니다! Server의 audio bus 할당기는 오직 private 색인(indices, not index)만을 할당합니다. 그러니 만약 여러분이 입출력 채널의 갯수를 바꾼다 해도, 여러분이 코드를 실행할 때, 할당기가 알아서 bus를 할당할 것이라는 거죠. 다시 한 번 말씀드릴게요. 여러분의 코드를 더 유연하게 만들어주는 것입니다.
Busses in Action
자, 아래에 bus들을 이용하는 두 예제가 있습니다. 처음 것은 control rate bus를 사용하는 것입니다.
(
SynthDef("tutorial-Infreq", { arg bus, freqOffset = 0;
// this will add freqOffset to whatever is read in from the bus
Out.ar(0, SinOsc.ar(In.kr(bus) + freqOffset, 0, 0.5));
}).send(s);
SynthDef("tutorial-Outfreq", { arg freq = 400, bus;
Out.kr(bus, SinOsc.kr(1, 0, freq/40, freq));
}).send(s);
b = Bus.control(s,1);
)
(
x = Synth.new("tutorial-Outfreq", [\bus, b]);
y = Synth.after(x, "tutorial-Infreq", [\bus, b]);
z = Synth.after(x, "tutorial-Infreq", [\bus, b, \freqOffset, 200]);
)
x.free; y.free; z.free; b.free;
변수 y와 z 모두 하나의 bus에서 읽어들입니다. 후자는 200이라는 상수를 주파수 제어 신호에 더해넣음으로써 간단히 수정을 합니다. 이것은 두 개의 분리된 제어 오실레이터를 이용해 주파수를 제어하는 것보다 효과적입니다. 이렇게 synth들을 서로 연결하는 이러한 전략은 큰 프로젝트에서 매우 효과적일 수 있습니다.
이제 오디오 bus와 관련된 예제를 하나 보시겠습니다. 이 예제는 우리가 이제껏 보아왔던 예제들 중 가장 복잡한 것입니다. 하지만 이 예제는 여러분에게, 우리가 이제까지 배워온 것을 어떻게 통합할 것인가에 대한 아이디어를 선사할 것입니다. 아래에 있는 코드는 두 개의 Synth를 소스로 사용할 것입니다. 하나는 PinkNoise의 펄스를 만들어냅니다. (PinkNoise는 노이즈의 일종으로서, 높은 주파수대의 신호가 낮은 주파수대의 신호보다 약합니다.) 다른 하나는 사인파의 펄스를 만들어내지요. 펄스들은 Impulse와 Decay2라는 두 UGen을 이용해 만들어집니다. 그렇게 만들어진 펄스들은 AllpassC라는 이름의 딜레이의 연쇄를 통해 울려 퍼지게 됩니다.
아래에 있는 16.do({…})라는 구문을 눈여겨 보십시오. 이것은 함수를 16회 연속해서 연산하라는 것입니다. 이것은 매우 강력하고 유연한 기술입니다. 16대신 다른 숫자를 넣음으로써, 연산의 횟수를 조정할 수 있습니다. Integer라는 문서를 살펴보십시오. 그 안에 Integer.do와 관련된 더 많은 정보가 있습니다.
(
// the arg direct will control the proportion of direct to processed signal
SynthDef("tutorial-DecayPink", { arg outBus = 0, effectBus, direct = 0.5;
var source;
// Decaying pulses of PinkNoise. We'll add reverb later.
source = Decay2.ar(Impulse.ar(1, 0.25), 0.01, 0.2, PinkNoise.ar);
// this will be our main output
Out.ar(outBus, source * direct);
// this will be our effects output
Out.ar(effectBus, source * (1 - direct));
}).send(s);
SynthDef("tutorial-DecaySin", { arg outBus = 0, effectBus, direct = 0.5;
var source;
// Decaying pulses of a modulating Sine wave. We'll add reverb later.
source = Decay2.ar(Impulse.ar(0.3, 0.25), 0.3, 1, SinOsc.ar(SinOsc.kr(0.2, 0, 110, 440)));
// this will be our main output
Out.ar(outBus, source * direct);
// this will be our effects output
Out.ar(effectBus, source * (1 - direct));
}).send(s);
SynthDef("tutorial-Reverb", { arg outBus = 0, inBus;
var input;
input = In.ar(inBus, 1);
// a low rent reverb
// aNumber.do will evaluate it's function argument a corresponding number of times
// {}.dup(n) will evaluate the function n times, and return an Array of the results
// The default for n is 2, so this makes a stereo reverb
16.do({ input = AllpassC.ar(input, 0.04, { Rand(0.001,0.04) }.dup, 3)});
Out.ar(outBus, input);
}).send(s);
b = Bus.audio(s,1); // this will be our effects bus
)
(
x = Synth.new("tutorial-Reverb", [\inBus, b]);
y = Synth.before(x, "tutorial-DecayPink", [\effectBus, b]);
z = Synth.before(x, "tutorial-DecaySin", [\effectBus, b, \outBus, 1]);
)
// Change the balance of wet to dry
y.set(\direct, 1); // only direct PinkNoise
z.set(\direct, 1); // only direct Sine wave
y.set(\direct, 0); // only reverberated PinkNoise
z.set(\direct, 0); // only reverberated Sine wave
x.free; y.free; z.free; b.free;
하나의 리버브 synth에 훨씬 더 많은 소스의 synth들을 아주 쉽게 연결할 수 있습니다. 만약 리버브를 소스 안에다가 만들어 넣는다면, 우리는 더 많이 복사하기 붙여넣기를 했어야 했겠지요. 하지만 private bus를 이용하면, 보다 효과적입니다.
More Fun with Control Busses
control rate bus를 이용하면, 몇 가지의 강력한 것들을 할 수 있습니다. 예를 들어, 실행 중인 synth의 어떠한 arg라도 매핑하여, control bus로부터 읽어들일 수 있습니다. 이것은 여러분이 In이라는 UGen을 사용하지 않아도 된다는 이야기입니다. 또한, 여러분은 control bus에 Bus.set이라는 method를 사용함으로써, 어떠한 상수도 쓸 수 있습니다. Bus.get이라는 method를 통해 어떠한 값이든 추출할 수도 있습니다.
(
// make two control rate busses and set their values to 880 and 884.
b = Bus.control(s, 1); b.set(880);
c = Bus.control(s, 1); c.set(884);
// and make a synth with two frequency arguments
x = SynthDef("tutorial-map", { arg freq1 = 440, freq2 = 440;
Out.ar(0, SinOsc.ar([freq1, freq2], 0, 0.1));
}).play(s);
)
// Now map freq1 and freq2 to read from the two busses
x.map(\freq1, b, \freq2, c);
// Now make a Synth to write to the one of the busses
y = {Out.kr(b, SinOsc.kr(1, 0, 50, 880))}.play(addAction: \addToHead);
// free y, and b holds its last value
y.free;
// use Bus-get to see what the value is. Watch the post window
b.get({ arg val; val.postln; f = val; });
// set the freq2, this 'unmaps' it from c
x.set(\freq2, f / 2);
// freq2 is no longer mapped, so setting c to a different value has no effect
c.set(200);
x.free; b.free; c.free;
audio rate bus들과는 달리 control rate bus들은 bus에 새로운 값이 쓰여지기 전까지 마지막의 값을 보전하고 있다는 것을 기억해두세요.
또한, Bus.get은 함수를 하나의 arg로 가진다는 점도 유의하세요. (여기서의 함수를 action function이라고 부릅니다.) 여기서 함수가 필요한 이유는, server가 답을 얻고 그 답을 돌려보내주기까지 아주 작지만 시간이 걸리기 때문입니다. arg로 사용되는, 값(, 만약 멀티채널 bus의 경우에는 값들의 배열)이 통과하는 이 함수는, 그 값이 돌아올 때만, 무엇인가를 할 수 있도록 만듭니다.
응답할 때까지 걸리는 이 작은 시간(보통 latency라고 불립니다.)에 대한 컨셉을 이해하는 것은 매우 중요합니다. SC에는 이런 식으로 기능하는, 몇 개의 다른 method들이 있습니다. 그리고 만약 여러분이 조심하지 않는다면, 문제를 야기할 수도 있습니다. 아래의 예제를 보면서, 이 컨셉을 머릿 속에 그려봅시다.
// make a Bus object and set its values
b = Bus.control(s, 1); b.set(880);
// execute this altogether
(
f = nil; // just to be sure
b.get({ arg val; f = val; });
f.postln;
)
// f equals nil, but try it again and it's as we expected!
f.postln;
왜 첫번째엔 f가 nil이었는데, 두 번째에선 그렇지 않을까요? 여러분의 코드를 실행하는 이 SC언어 어플리케이션의 일부분(인터프리터라고 불리는 녀석입니다)은, 여러분이 인터프리터에게 무엇인가를 보여달라고 얘기하는 순간, 여러분에게 가능한한 빠르게 그것을 보여주려고 합니다. 그래서 위의 예제 중 괄호로 묶인 코드의 블락이 ‘get’이라는 메세지를 server에 보내고, 응답을 받으면 함수가 실행될 수 있도록 스케쥴을 짠 뒤, 셋째줄의 f를 post하라는 명령에까지 이르지요. 그러나 아직 f에 대한 응답을 받지 않았기 때문에, f가 처음으로 post될 때까지도 f는 여전히 nil입니다.
server가 응답을 되돌려 보낼 때까지는 매우 작은 양의 시간만 필요하기 때문에, 이어서 마지막 줄의 코드를 실행할 때는, 우리가 기대한대로, 880이라는 값이 f에 설정되어 있게 됩니다. 앞의 예제에서는 별 문제가 없었습니다. 한 줄 씩 따로 실행하면 되니까요. 하지만 어떤 경우엔 하나의 블록을 지정해서 여러 코드를 한꺼번에 실행해야할 때도 있습니다. 그리고 이러한 때에 action function이라 불리는 이 기술은 매우 유용합니다.
Getting it all in the Right Order
위의 예제에서, 여러분은 Synth.after나 addAction: \addToHead 같은 것들이 무엇인지 궁금하셨을 겁니다. 한 싸이클(샘플들의 한 블록이 계산되는 시간)동안, server는 특별한 순서에 의해, 실행 중인 synth들의 리스트에 따라, 모든 것들을 계산합니다.
이 계산은 리스트에 있는 첫번째 synth부터 시작됩니다. 그리고 첫번째 synth의 첫번째 UGen에 있는 sample들의 블록부터 계산되지요. 그 뒤, 남아있는 UGen들 안의 샘플 블록이 각각 차례로 계산됩니다. (어떤 단계에서라도, 앞 단계에서 만들어진 UGen의 출력을 이번 단계의 입력으로 받을 수 있습니다.) 이러한 synth들의 출력은 하나의 bus나 여러 개의 bus들에 쓰여집니다. 그러면 server는 리스트에 있는 그 다음 synth로 옮기지요. 이러한 과정은 모든 실행 중인 synth들이 자신의 샘플 블록을 계산할 때까지 반복됩니다. 이제 server는 다음 싸이클로 과정을 진행해갈 수 있습니다.
여러분이 bus들을 이용해서 synth들을 서로 연결 할 때, 이해하셔야할 매우 중요한 것은 일반적인 규칙입니다. 그 규칙이란, server에 있는 리스트에서 bus에 신호를 쓰는 synth가 bus로부터 신호를 받아오는 synth들보다 먼저 오게 된다는 것입니다. 예를 들어, 위에서 본 오디오 bus 예제에서, 리버브 synth는 노이즈와 사인파 synth들보다 나중에 계산되어야 한다는 것이지요.
이것은 복잡한 주제입니다. 게다가 몇 개의 예외 규정들도 있습니다. 그러나 synth들을 상호 연결할 때에는 이것이 매우 중요하다는 것을 기억하셔야 합니다. Order-of-execution 문서가 이 주제와 관련해 아주 자세하게 설명하고 있습니다.
Synth.new는 synth가 어느 순서에 더해질지를 정하기 위해 두 개의 arg를 가집니다. 첫번째 arg는 타겟입니다. 두번째 arg는 addAction입니다. addAction은 타겟을 기준으로 해당 synth가 어디에 위치해야할 지를 알려줍니다.
x = Synth("default", [\freq, 300]);
// add a second synth immediately after x
y = Synth("default", [\freq, 450], x, \addAfter);
x.free; y.free;
타겟에는 다른 Synth가 올 수 있겠지요. (Synth 외에 다른 것도 올 수 있지만, 그것은 다음에 설명하겠습니다.) 두번째 arg인 addAction은 하나의 심볼(Symbol)입니다. Synth 문서를 보시면 사용할 수 있는 모든 addAction들의 리스트가 있습니다.
Synth.after와 같은 method들은 위와 같은 일을 수행하는 단순하고 편리한 방법입니다. 다른 점이 있다면, 타겟을 첫번째 arg로 취한다는 점입니다.
// These two lines of code are equivalent
y = Synth.new("default", [\freq, 450], x, \addAfter);
y = Synth.after(x, "default", [\freq, 450]);