인트라넷에 올리려던 글.

1월 9th, 2012

1.
제게 하나님은, 저 스스로 결정할 수 있도록 지켜봐 주시는 분, 그 결정을 귀엽게 보아주시는 분, 쓰러질 걱정, 넘어질 걱정 하지 않도록 든든한 방패가 되어 주시는 분.입니다. 때때로 급박한 상황에서 친히 간섭하실 때도 있지만, 대부분의 경우는 관심과 사랑의 눈길로 지켜보아 주십니다.

보이지 않는 하나님에 대한 상은, 보이는 이웃들과의 관계를 통해 형성됩니다. 특히, 부모님을 비롯한 주위의 어른들의 모습에서 짐작하게 됩니다. 또한 사람에 대한 가치관은 신앙에 의해 영향을 받습니다. 저는 이렇게 형성된 하나님 상에 의해, 제 주변의 어른들 또한 그러한 태도를 지녀주셨으면 좋겠다고 바랍니다. 그리고 저 또한 그런 어른이 되고 싶습니다.

저는 인트라넷이 재학생들의 공간이라고 생각했습니다. 제가 휴학생일 때에도 인트라넷에서는 자주 글쓰지 않았습니다. 졸업생들이 글을 쓸 때에는, ‘졸업생’이라는 단서를 앞에 붙이기도 합니다. 그것은 졸업생들의 재학생들에 대한 배려라고 생각했습니다. 많은 졸업생들이 이곳의 글을 읽겠지만, 그저 지켜볼 뿐이라 생각했습니다.

저는 한동의 뭇 교수님들이 이곳 인트라넷에서 활동하는 학생들에 대하여, 그러한 태도를 가져주셨으면 좋겠다고 생각했습니다. 아이가 노는 것을 흐뭇하게 지켜보는 어른의 마음으로 그저 지켜보아 주시기를 바랐습니다. 저는 교수님들이 좀더 어른이었으면 좋겠다고 바라고 있었어요. 좀더 근엄하시길 바랐습니다. 학생들의 삶의 현장으로 들어와 참여하시 보다는 한 차원 위에서 내려보시기를 바랐습니다. 그리고 대부분의 교수님들은 i7에 글을 남기지 않으십니다.

물론 모든 교수님들 혹은 어른들이 저의 기준에 맞게 행동해야만 하는 것은 아닙니다. 그 분들께 제 생각을 관철시키려는 것도 아닙니다. 저의 주관을 표현했습니다. 그랬기에 저는 ‘바람’이라 표현했습니다. 강요하지 않았습니다. 마치 모두가 그렇게 생각하는 양 표현하지도 않았습니다.

2.
종종 여학생들에 대한 태도와 남학생들에 대한 태도가 사뭇 다른 몇몇 교수님들에 대한 이야기를 들을 때가 있습니다. 어떤 학생들은 그런 이야기를 하며 그 교수님을 비난하기도 합니다. 보통 심성 착한 한동의 대학생들은 그런 이야기들을 통해 교수님도 우리와 그리 다르지 않은, 약한 사람이구나 하며 교수님과의 심리적 거리를 줄여보려고도 합니다. 어느 쪽이 되었든 대부분의 경우 그것은 어디까지나 뒷담화입니다. 교수님에 대한 뒷담화도 있었습니다. 그리고 저는 그런, 교수님들에 대한 뒷담화가 그리 좋지 않았습니다.

차라리 교수님께, 이 곳에 들어와 글 쓰고 그러는 거 꼭 애들 노는 데 끼이려는 어른 답지 못한 어른의 행동 같으니, 글 쓰지 마십시오, 이런 말씀을 드리게 되었지요. 네, 절필 맞습니다. 글을 쓰지 말라는 것이니 절필이 아니라 무엇이겠습니까. 그렇다고 해서 교수님께 말씀을 드리는데, 꼭 그렇게 직설적인 표현이나 강한 어조의 단어를 써 가며 말할 필요는 없다고 생각했습니다. 제 깜냥으로는 예의를 갖춰가며 돌려서 말한 것이었는데, 교수님께는 그렇게 받아들여지지 않은 것 같습니다. 오히려 제가 바람이라고 말씀드린 것조차 강요로 받아들이신 것 같습니다.

3.
저는 제가 처음 글을 게시한 것에 대하여 자신하지 못하고 있습니다. 아마도 자신하지 말아야 하는 것이 옳지 않나 하는 생각도 듭니다. 제게 동조하는 사람이 더 많았더라면 저는 아마 자만하지 않았을까 의심도 해봅니다.

자신하지 못하는 이유 중 하나는 제 글이 고작 긁어 부스럼은 아니었나 하는 것입니다. 제 글에 댓글을 달아주신 학우들 중 많은 비율의 학우들은 교수님의 인트라넷 참여에 대해 긍정적으로 여기고 있었습니다. 그러나 제 글에 추천 버튼을 클릭하신 학우들도 있습니다. 인트라넷 안에서 학생들만의 공간이기를 바라는 사람들도 있었던 것이지요. 하지만 아무도 굳이 그 바람을 꺼내어 이야기하지는 않았습니다. 괜히 꺼내어 이야기해보았자 오해와 비난만 살 터이고, 누가 글을 쓰던 읽는 사람이 읽지 않으면 그만이니 굳이 이야기할 필요가 없다는 생각은 아니었을까요. 이야기하지 않아도 아무 문제가 없었지요. 크게 불편해하는 사람도 없었습니다.

다른 하나는 교수님들께서 이곳에 글을 쓰시지 않는다 하여 그것이 얼마나 유익할 것이며 학생 자치에 도움이 될 것인가 하는 것입니다. 제 의견에 동조한 몇몇 학우들이 있듯이, 제게 반대하는 더 많은 학우들이 있으니, 교수님께서 아니, 다른 교수님들께서도 교수님을 모범삼아 이곳에서 함께 ‘소통’하시기를 바라야 하는 것은 아닌가 하는 의문입니다. 또한 학생 자치라고 하는 것이 교수님들이나 학교 당국으로부터의 단절을 의미하는 것이 아닐진대, 굳이 이곳이 학생들만의 공간이어야 하는가 하는 의문이기도 합니다.

이러한 의심과 의문들은 아직 제 안에서 미처 정리되지 않았습니다.

4.
교수님, 결정되지 않은 일을 가지고 재단하였다 말씀하셨지요. 그러나 저는 제 글 어디에도 일단의 학생들이 그러한 결정을 했다는 것을 언급 아니, 암시조차 한 일이 없습니다. 다시 제 글을 읽으며 어쩌면 ‘암묵적 경계’라는 표현 때문에 그리 느끼신 것이 짐작해봅니다. 그러나 암묵적이라는 말은 아무도 그 일에 대하여 결정한 적이 없음을 의미하지 않는지요.
글을 그만 쓰시라는 요청이 부적절하다 말씀하신 것에 대해 좀더 생각해보았습니다. 아마도 글을 못 쓰게 한다면 더이상 대화도 설득도 불가하기 때문에, 그것은 소통의 철저한 단절이기 때문이 아닐까 짐작했습니다. 그러나 제가 쓴 글은 철저한 단절에의 바람을 표현한 것이 아니었습니다. 하나님이 우리를 지켜보시듯 보아달라고 말한 것이 어찌 극단적인 단절을 의미하는 것이겠습니까. 그러한 극단적인 단절을 바랐다면 오히려 이렇게 해명을 하고 설명을 하는 저 자신과 모순되는 것이 아니겠습니까.

교수님께서는 충돌이 존재하지 않았다 말씀하셨습니다. 네, 교수님께서는 늘 좋은 글들을 올리셨습니다. 단 한 번 많은 반대를 받으신 글이 있는데 그것은 “경고와 건의 하나씩”이라는 글입니다. 교수님께서는 매우 완곡한 표현을 사용하시며 부드럽게 표현하셨습니다. 부드러운 분위기를 만들기 위해 가벼운 농담도 곁들이셨지요. 심지어 ‘경고’라는 단어조차 반어적인 의미로 읽힐 수 있을만큼 조심스러운 글이었던 것으로 기억합니다. 그럼에도 그것은 경고였습니다. 비추천 하지 말라는 것이었지요. 무엇보다 그 ‘경고’는 교수님께서 스스로 교수라는 직위를 의식하고 쓰신 글입니다.
그 글에서 교수님은 chilling effect의 위험을 언급하셨습니다. 저의 글은 교수님을 비난한 글이 아니었습니다. 단순히 이곳이 학생들만의 공간이었으면 좋겠다는 의견을 근거도 없이 표출한 것도 아닙니다. 저 나름대로 왜 그런지 고민하며 고치고 고친 글이었습니다. 교수님께서는 진심이냐고 물으면서도 제 결론이 어떻게 형성되었는지 그 과정에는 전혀 관심이 없으신 듯 보였습니다. 조심스럽게 표현한 표현들을 격한 표현들로 바꾸어 받아들이셨습니다. 학생은 교수에 반대할 수 없는 것인가요. 교수님의 그러한 반응은 학생들에게 chilling effect가 되지 않겠습니까.

5.
i7회원들의 마음을 불편하게 한 점에 대하여서는 제 글을 읽고 불편해 하신 사과드립니다. 그 불편함이 설사 제 글을 오해했기 때문이다 하더라도, 제가 그 책임을 피할 수는 없다고 생각합니다. 미안합니다.

그러나 불편함에는 감수해야할 불편함과 지양하고 회피해야할 불편함이 있다고 생각합니다. 저는 지금보다 지난 학기 제 글에 비추천을 하던 회원들에 대해 비난했던 일이 더 부끄럽습니다. 당시 비추천을 받으며, 그에 대해 감정을 추스르지 못하고, 학번과 나이를 내세워 격하게 반응했던 것으로 기억합니다. 미안합니다. 그것은 지양하고 회피해야할 불편함이었습니다. 왜 비추천을 하는 지 좀더 깊이 생각해보지도, 그 회원들의 심정을 헤아려보려 하지도 않았습니다.
그러나 오그러네 님과 관련한 글에 대한 여러분의 불편함이 어떠한 불편함인지 조금더 생각해보아 주시기를 바랍니다. 불편함 감정에서 한 걸음 물러나 여러분이 왜 불편한지, 그 불편함이 어디서 연유하는지 되짚어보아 주시기를 바랍니다. 그리고 이야기해주셨으면 합니다.

Gap

1월 2nd, 2012

호용씨가 내게 자신이 디자인한 음악에 대한 영상 작업을 해달란지도, 벌써 수 개월.
이제서야 마무리를 하게 되었다.

처음에는 촬영을 한 소스 영상을 이용해 작업을 하려고 했으나,
구상이 잘 되지도, 촬영한 소스가 마음에 들지도 않아서,
Processing.org를 이용해 작업을 하였다.

첫번째 드레프트에서는 별 문제 없이 export가 되었는데,
지금 마무리한 영상에서는 랩탑의 성능 문제인지 MovieMaker 라이브러리로 export를 해도,
프레임이 누락되어서, 영상 결과물이 실제 영상보다 짧아지는 문제가 있었다.
결국 어쩔 수 없이, 소스를 공개하고, 호용씨에게도 소스를 보낸다.

// for sound analysis
import ddf.minim.*;
import ddf.minim.analysis.*;
// for export video
import processing.video.*;

/******************************************************************************************
 * Declarate global variables                                                             *
 ******************************************************************************************/
// video Object
MovieMaker mm;

// Minim Object
Minim minim;
AudioPlayer audioPlayer;
FFT fft;

// variables for environment
int w = 640;//1920;
int h = 360;//1080;
int fps = 30;
float tick = h / 360; // make movie to depend on the size of screen.

// variables that are needed to initialized once
float centerX = w/2;  // for "translate" function in "draw" function
float centerY = h/2;  // for "translate" function in "draw" function
float angle = 0;      // for "rotate" function in "draw" function

/******************************************************************************************
 * setup function                                                                         *
 ******************************************************************************************/
void setup() {
  // video Object for export video
  mm = new MovieMaker(this, w, h, "gap.mov", fps, MovieMaker.ANIMATION, MovieMaker.LOSSLESS, fps);

  // setup environment
  size(w, h);
  smooth();
  frameRate(fps);

  /****************************************************************************************
   * sound analysis and play                                                              *
   ****************************************************************************************/
  minim = new Minim(this);
  audioPlayer = minim.loadFile("gap2.aiff", 512);
  audioPlayer.loop();      // this is for infinity loop.
  //audioPlayer.play();    // this is for play once.
  fft = new FFT(audioPlayer.bufferSize(), audioPlayer.sampleRate());
}

/******************************************************************************************
 * draw function                                                                          *
 ******************************************************************************************/
void draw() {
  /****************************************************************************************
   * shake the whole image                                                                *
   ****************************************************************************************/
  // shake the center with "translate" function
  centerX += random(-width/160, width/160);
  centerY += random(-height/160, height/160);
  centerX = constrain(centerX, width/2 - width/16, width/2+width/16);
  centerY = constrain(centerY, height/2 - height/16, height/2+height/16);
  translate(centerX, centerY);
  // shake the center with "rotate" function
  angle += random(-PI/60, PI/60);
  rotate(angle);

  // update background to erase old image
  background(0);

  /****************************************************************************************
   * generate image from analyzed data                                                    *
   ****************************************************************************************/
  // take the data of sound analysis
  fft.forward(audioPlayer.mix);
  for(int i = 0; i < fft.specSize(); i++){
    if(fft.getBand(i) > 0.7){  // filter noize
      // draw lines for making triangle
      noStroke();
      fill(255, 32);
      //fill(map(fft.getBand(i), 0, 100, 128, 255), 32);
      float sizeOfTriangle = tick*fft.getBand(i)/1.25;
      triangle(0, 0,
               -sizeOfTriangle, sizeOfTriangle,
               sizeOfTriangle, sizeOfTriangle);
      // draw lines by "beginShape" function
      stroke(255 - map(fft.getBand(i), 0, 100, 0, 64), 128);
      beginShape(LINE);
      float radius = tick*2*fft.getBand(i);
      int numberOfVertex = (int)map(radius, 0, tick, 11, 17);
      for(int j = 0; j < numberOfVertex; j++){
        radius += random(-sqrt(i)*tick, sqrt(i)*tick);
        vertex(radius*cos(TWO_PI*j/numberOfVertex), radius*sin(TWO_PI*j/numberOfVertex));
      }
      endShape();
    }
  }
  /****************************************************************************************
   * export the video by each frame                                                       *
   ****************************************************************************************/
  //mm.addFrame();
}

/******************************************************************************************
 * stop exporting video frames and save the video                                         *
 ******************************************************************************************/
void stop(){
  mm.finish();
  audioPlayer.close();
  minim.stop();
  super.stop();
}

p.s.1 블로그에서 사용하는 syntax highlighter에서 processing 언어를 지원하지 않아,
일단 processing의 부모격인 java 언어로 설정해 두었다.
비교적 잘 적용이 되는 것 같은데, documentation으로의 링크는 다를 가능성이 높다.

p.s.2 이런 거만 써놓으면 재미없어할 친구들을 위해, 호용씨의 허락도 받지 않고 첫번째 드래프트 영상을 걸어둔다.

SuperCollider Tutorial(Scott Wilson) in Korean 12

10월 18th, 2011

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

SuperCollider Tutorial(Scott Wilson) in Korean 11

10월 18th, 2011

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]);

SuperCollider Tutorial(Scott Wilson) in Korean 10

10월 12th, 2011

SynthDefs and Synths

이제 우리는 기본적인 내용들을 거의 훑었습니다. 이제는 server abstraction에 대한 내용을 보기 시작할 것입니다. server abstraction은 server에 무엇인가를 나타내는(represent) 어플리케이션으로서의 다양한 class들입니다. server abstraction을 보기 위해서, 이 객체들이 server의 architecture의 일부분 아니라, 그 일부분의 client측 재현(representation)일 뿐이라는 것을 잘 이해해야합니다. server abstraction 객체는 편리합니다.

architecture의 일부분이냐, 그 재현이냐를 구분하는 것은 조금 어렵습니다. 그래서 일반적으로 여기서부터 저는 client측 class들에는 대문자로 시작하는 이름을, 그에 대응하는 server architecture의 부분에 대해서는 소문자로 시작하는 이름을 사용하겠습니다. 예를 들어 Synth와 synth로 말이죠.

여러분은 이미 한 종류의 server abstraction을 본 적이 있습니다. Server라는 class 자신이죠. 이 객체는 Server.local이나 Server.internal으로 쓰입니다. 그리고 이 둘은 Server라는 class의 instance들이죠. (양쪽 모두 interpreter 변수인 s에 저장될 수 있습니다.)

이제 나머지 것들에 대해서도 익숙해질 때입니다. 첫번째로 볼 것이 바로 SynthDef라는 class입니다. synth definition의 약자입니다.

Meet the SynthDef

이제껏 우리는 오디오 신호를 만들기 위해 함수를 사용해왔습니다. 함수를 이용하는 방법은 간단한 테스트나 유연성이 최대로 발휘되어야 할 때 매우 유용합니다. 함수는 실행될 때마다 새로이 연산되기 때문에 결과가 매우 변화무쌍합니다.

하지만 server는 함수나 객체지향이나 SC 언어를 전혀 이해하지 못합니다. server는 다만 synth definition이라 불리는 특별한 형태 안에서 오디오 신호가 어떻게 만들어질 지에 대한 정보만을 원합니다. synth definition은 UGen과 그 UGen들이 어떻게 상호연결되는지에 대한 데이터입니다. 이 데이터는 ‘바이트 코드’라 불리는, 특별히 최적화된 형태로 보내집니다. server는 이 ‘바이트 코드’를 매우 효과적으로 상대합니다.

server가 하나의 synth definition을 가지는 순간, 이 synth definition을 기반으로 다양한 synth들을 매우 효과적으로 사용할 수 있습니다. server에 있는 Synth는 기본적으로 소리를 만들거나, 가공하거나, 다른 synth들을 제어하기 위한 제어 신호를 만들어냅니다.

synth definition과 synth 사이의 이 관계는 class와 instance 간의 관계와 비슷합니다. 전자가 후자의 템플릿이 되는 것이죠. 하지만 server abstractoin이 객체 지향에 대해선 전혀 모른다는 것을 기억하세요.

운이 좋게도 SC에는 SynthDef과 같은 class들이 있습니다. SynthDef은 server가 필요로하는 ‘바이트 코드’들을 쉽게 생성할 수 있게 해주거나, 객체 지향적인 방법으로 synth definition을 제어할 수 있게 해줍니다.

여러분이 함수의 오디오 신호를 생성하는 method를 사용할 때마다 어떤 일이 일어나는 것일까요? 그에 대응하는 SynthDef의 instance가 ‘무대 뒤에서’ 만들어지고, 필요한 ‘바이트 코드’가 생성된 뒤 server로 보내집니다. 바로 server야말로 연주되기 위한 오디오 신호가 synth로 만들어지는 곳입니다. 그러므로 함수의 오디오 신호 method들은, 여러분이 이러한 ‘무대 뒤에서’ 벌어지는 일들에 대해 신경쓰지 않을 수 있도록 해주느 것이죠.

그렇다면 여러분은 어떻게 스스로 SynthDef을 만들 수 있을까요? new라는 method를 쓰면 됩니다. 우리에게 이미 친숙한 함수를 이용한 예제와 그와 동일하지만 SynthDef.을 이용한 예제를 비교해봅시다. 함수와 마찬가지로, SynthDef는 play라는 편리한 method를 가지고 있습니다. 그러므로 우리는 두 예제가 같다는 것을 쉽게 확인해볼 수 있습니다.

	//first the Function
	{ SinOsc.ar(440, 0, 0.2) }.play;

	// now here's an equivalent SynthDef
	SynthDef.new("tutorial-SinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.2)) }).play;

SynthDef.new는 몇 개의 arg를 취합니다. 첫번째 것은 이름입니다. 위에서와 같이 문자열(String)의 형태를 가집니다. 두번째 것은 사실 함수입니다. 이 함수는 UGen Graph Function이라고 불립니다. arg 안에 있는 다양한 UGen들이 어떻게 서로 연결되어 있는지를 server에게 알려줍니다.

SynthDefs vs. Functions

우리가 위에서 사용한 두번째 코드에서 UGen Graph Function은, 첫번째 코드에서 우리가 함수를 사용한 것과 비슷합니다. 그러나 매우 중요한 차이가 있습니다. Out이라는 이름의 또다른 UGen이 있다는 것이죠. Out은 ar 신호나 kr 신호를 server의 bus들 중 하나에 써냅니다. 이 bus들은 믹서의 채널이나 출력포트 등으로 비유할 수 있습니다. bus에 대해선 다음에 자세히 다룰 것입니다만, 지금으로서는 컴퓨터의 오디오 아웃을 연주하거나 마이크와 같은 소스로 부터 신호를 읽어내는데 사용된다는 것만 기억해두세요.

Out은 두 개의 arg들을 취합니다. 첫째 것은 신호가 나갈 bus의 번호표입니다. 이 번호표는 0에서 시작합니다. 0은 스테레오 세팅에서 보통 왼쪽 출력 채널에 해당하지요. 둘째 것은 UGen이나 UGen들의 배열입니다. 만약 여러분이 (예를 들어, 멀티 채널 출력과 같은) 배열을 사용한다면, 첫번째 채널이, 첫째 arg에 쓰인 bus로 출력되고, 다음 채널이 차례로 다음번 bus로 출력됩니다.

좀더 확실히 보여드리기 위해 스테레오 예제를 준비했습니다. 주파수가 440Hz인 SinOsc는 0번 bus(왼쪽 채널)에서 출력되구요, 주파수가 442Hz인 SinOsc는 1번 bus(오른쪽 채널)에서 출력됩니다.

	(
	SynthDef.new("tutorial-SinOsc-stereo", { var outArray;
		outArray = [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)];
		Out.ar(0, outArray)
	}).play;
	)

여러분이 Function-play를 사용할 때, 실제로는 Out UGen이 (여러분이 명시하지 않는다 해도) 생성됩니다. Out UGen의 bus 번호표에 대한 기본값은 0입니다.

Function-play와 SynthDef-play는 모두 Synth라는 형태의 객체를 반환합니다. Synth는 server에서 synth를 재현(represent)하지요. 만약 여러분이 이 객체를 하나의 변수에 저장한다면, 여러분은 이 객체의 행동을 다양한 방법으로 제어할 수 있습니다. 예를 들어 free라는 method는 server에 있는 synth로 하여금 연주를 정지하게 만들고, synth가 차지했던 메모리와 cpu의 공간을 비웁니다.

	x = { SinOsc.ar(660, 0, 0.2) }.play;
	y = SynthDef.new("tutorial-SinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.2)) }).play;
	x.free;	// free just x
	y.free;	// free just y

이것은 Cmd-.를 누르는 것보다 훨씬 유연하죠. Cmd-.는 모든 synth를 한꺼번에 free 시키는 것이니까요.

SynthDef는 바로 synth를 생성하지 않은 채로, server 어플리케이션에 ‘바이트 코드’를 보내는 두 개의 method를 가지고 있습니다. send와 load가 바로 그것입니다. 이 둘 사이의 차이점은 send가 네트워크를 통해 definition을 보내는 것이라면, load는 definition을 디스크에다 파일의 형태로 쓴다는 것이죠. 물론 load로 저장된 파일은 server가 불러올 수 있습니다. 이렇게 저장된 파일은 .scsyndef라는 확장자를 가지고, SC 디렉토리 안에 있는 synthdefs/ 디렉토리에 저장됩니다. 이 파일은 여러분이 직접 지우기 전에는 사라지지 않습니다. 또한 여러분이 server을 boot시킬 때마다 자동적으로 불러와집니다.

일반적으로는 send를 사용합니다. 매번 이 definition을 사용하지 않는 한 말이죠. 하지만 어떤 때엔 load를 사용할 필요도 있습니다. 예를 들어, 네트워크의 패킷 사이즈 한계에 비해 definition이 너무 복잡하고 그 용량이 크다면, load를 사용해야겠지요.

여러분은 같은 함수나 SynthDef을 가지고 아주아주 많은 Synth들을 만들어낼 수 있습니다. 하지만 SynthDef을 사용하면 확실한 장점이 있습니다. 그것은 바로 certain limitation입니다.

만약 server 어플리케이션에서 여러분이 definition을 하나 가지고 있다면, 여러분은 적은 cpu 사용량만으로도 많은 synth들을 만드실 수 있습니다. 어떻게 하냐구요? Synth의 new method를 사용하면 되지요. 이 method는 definition의 이름을 첫번째 arg으로 사용합니다.

	SynthDef.new("tutorial-PinkNoise", { Out.ar(0, PinkNoise.ar(0.3)) }).send(s);
	x = Synth.new("tutorial-PinkNoise");
	y = Synth.new("tutorial-PinkNoise");
	x.free; y.free;

같은 함수를 반복해서 호출하는 것보다 위와 같이 하는 것이 보다 효과적입니다. 매번, 함수를 연산하고, byte code로 컴파일하고, server로 보내는 이 모든 노력을 하지 않아도 되기 때문이지요. 많은 경우, 이렇게 cpu의 사용을 절약하는 것은 너무 작아서 차이를 느낄 수 없을 수 있습니다. 하지만 매우 큰 프로젝트를 수행할 때는 매우 중요해지겠지요.

SynthDef와 바로 작업하면, SynthDef 안에 있는 UGen Graph Function이 단 한 번 연산될 뿐입니다. (sever가 SC 언어에 대해서는 전혀 모른다는 걸 떠올려 보세요.) 그런데 이것은 어떻게든, 덜 유연하다는 의미가 되기도 합니다. 아래의 경우를 한 번 살펴보세요.

	// first with a Function. Note the random frequency each time 'play' is called.
	f = { SinOsc.ar(440 + 200.rand, 0, 0.2) };
	x = f.play;
	y = f.play;
	z = f.play;
	x.free; y.free; z.free;

	// Now with a SynthDef. No randomness!
	SynthDef("tutorial-NoRand", { Out.ar(0, SinOsc.ar(440 + 200.rand, 0, 0.2)) }).send(s);
	x = Synth("tutorial-NoRand");
	y = Synth("tutorial-NoRand");
	z = Synth("tutorial-NoRand");
	x.free; y.free; z.free;

여러분이 하나의 definition을 기반으로 new Synth를 만들 때마다, 랜덤값에도 불구하고 주파수는 일정합니다. 이건 함수(와 그 안의 200.rand)가 오직 한 번만 연산된다는 이야기입니다. 그 연산은 SynthDef가 만들어질 때 이루어집니다.

Creating Variety with SynthDefs

그러나 SynthDef을 이용해서도 다양한 결과를 도출할 수 있는 몇 가지 방법이 있습니다. 랜덤과 같은 어떤 것들은 다양한 UGen을 통해 구현되기도 합니다. Rand이 그 일례입니다. Rand는 최솟값과 최댓값 사이의 임의의 수를 synth가 만들어질 때 계산해냅니다.

	// With Rand, it works!
	SynthDef("tutorial-Rand", { Out.ar(0, SinOsc.ar(Rand(440, 660), 0, 0.2)) }).send(s);
	x = Synth("tutorial-Rand");
	y = Synth("tutorial-Rand");
	z = Synth("tutorial-Rand");
	x.free; y.free; z.free;

UGens라는 문서는 이러한 UGen들에 대한 목록을 보여주는 문서입니다.(원문에는 링크가 되어있습니다.)

변수를 만드는 가장 일반적인 방법은 UGen Graph Function에 arg들을 집어넣는 것입니다. 이렇게 하면 synth가 만들어질 때 다른 값을 지정할 수 있게 되죠. 이 값들은 두번째 arg인 배열 안으로 들어가 Synth-new에 이릅니다. 이 때 이 배열은 arg의 이름과 값을 쌍으로 가져야 합니다.

	(
	SynthDef("tutorial-args", { arg freq = 440, out = 0;
		Out.ar(out, SinOsc.ar(freq, 0, 0.2));
	}).send(s);
	)
	x = Synth("tutorial-args");							// no args, so default values
	y = Synth("tutorial-args", ["freq", 660]);			// change freq
	z = Synth("tutorial-args", ["freq", 880, "out", 1]);	// change freq and output channel
	x.free; y.free; z.free;

이 arg들과 UGen들의 조합을 보면, 하나의 definition으로부터도 다양한 결과가 도출될 수 있음을 알 수 있습니다. 하지만 프로그램의 유연성이 아주 강조되는 경우에는, 함수를 사용하거나 여러 개의 definition을 만드시면 됩니다.

More About Synth

Synth에는 synth가 생성된 다음에도 그에 대한 값들을 바꿀 수 있게 해주는 method들이 있습니다. 지금은 그 중에 하나만 살펴보지요. ‘set’은, Synth의 method로서의 set은 이름과 값, 이렇게 한 쌍의 arg를 갖습니다.

	Server.default = Server.internal;
	s = Server.default;
	s.boot;
	(
	SynthDef.new("tutorial-args", { arg freq = 440, out = 0;
		Out.ar(out, SinOsc.ar(freq, 0, 0.2));
	}).send(s);
	)
	s.scope; // scope so you can see the effect
	x = Synth.new("tutorial-args");
	x.set("freq", 660);
	x.set("freq", 880, "out", 1);
	x.free;

Some Notes on Symbols, Strings, SynthDef and Arg Names

SynthDef의 이름과 arg의 이름은 앞서 살펴본 바와 같이 모두 문자열(String)이 될 수 있습니다. 그러나 그 이름들은 심볼이 될 수도 있습니다. 심볼을 다른 종류의 문자들을 일컫는 말입니다. 심볼을 만드는 방법은 두 가지가 있습니다. 하나는 작은 따옴표를 이용하는 것(‘tutorial-SinOsc’)이고, 하나는 역슬러시(\)를 이용하는 것(\tutorial-SinOsc)입니다. 문자열과 같이 심볼은 알파벳과 숫자를 나열해 만듭니다. 문자열과 심볼의 차이점은, 심볼의 경우 심볼을 이루고 있는 문자의 구성이 같을 경우 두 개의 심볼은 같은 것으로 본다는 것입니다. 여러분은 ===를 이용해 테스트 해보실 수 있습니다. 다음 두 행을 실행시키시면서 post window를 잘 보세요.

	"a String" === "a String"; 	// this will post false
	\aSymbol === 'aSymbol';		// this will post true

일반적으로 server와 의사소통하는 method 안에서는 문자열과 심볼을 서로 바꾸어 사용할 수 있습니다. 하지만 일반적인 코드에서도 이것이 true일 필요는 없다는 것을 주의하세요.

	"this" === \this; 	// this will post false