Cohen-Sutherland Clipping Algorithm

7월 23rd, 2012

1. viewport?

두번째 주제는 clipping이었습니다. clipping은 viewport에 그려지지 않는 장면은 계산하지 않는다는 것인데요. viewport라고 하는 것은, 카메라의 뷰파인더 정도로 이해하면 될 것 같습니다. (구글링으로 viewport를 찾았더니, 최근에는 모바일 웹과 관련한 글들이 많이 올라오더군요.)

2. why clipping?

Computer Graphics는 꽤 많은 CPU와 메모리 자원을 필요로 합니다. 그런데 스크린에 보이지도 않은 장면의 구성을 위해 CPU가 항상 연산을 하고 메모리에 모두 저장시켜야 한다면 너무 많은 자원이 필요하게 되죠. 그래서 등장한 것이, clipping이에요. 자원을 아끼고, 연산속도를 향상시켜 보자는 거죠.

3. cohen-sutherland algorithm?

clipping에는 매우 오래된 알고리즘이 있어요. Ivan Sutherland와 Danny Cohen의 이름을 붙여 만든 이 알고리즘은 67년에 개발되었습니다. 길지 않은 Computer Science 역사를 생각해보면 매우 초기의 일이라는 걸 알 수 있죠. 이 알고리즘은 쉽고, 3차원으로의 확장도 쉬워서 많이 사용되고 있다고 합니다.
교수님께서는 논문이 있다고 말씀하셨지만, wikipedia에 워낙 잘 나와있어서(심지어 c 언어로 구현도 해놓았더라구요!) 논문까진 필요없지 않을까 싶네요. 영어가 부담스럽다면, 한글로 설명된 블로그 글도 있어요.

4. preparing for the altorithm

내용을 살펴보자면, 일단 2차원에 대해서만 생각을 해볼게요. 음의 무한대에서 양의 무한대까지 뻗어가는 2차원 평면이 있다고 가정합시다. 그리고 우리에게 주어진 viewport가 있어요. viewport는 직사각형이니, 4개의 parameter로 구현이 가능합니다. (minX, minY), (maxX, maxY)가 그것이죠. 그럼 이제 이 4개의 값에 의해 평면에는 4개의 직선이 그려집니다. 그리고 평면은 9개의 부분으로 나뉘구요. 말이 기니 어럽지요? 그림으로는 간단합니다.

아, 그림에는 이미 일련번호도 붙여두었군요. C로 구현되는 알고리즘을 위해 4비트의 정수로 각 위치를 표현하였습니다. x가 중앙일 때는 00, 왼쪽일 때는 01, 오른쪽일 때는 10이구요, y도 마찬가지로 중아일 때는 00, 아래일 때는, 01, 위일 때는 10입니다. 그래서 4비트의 정수로 각 위치가 표현됩니다. 0000이 바로 중앙인 viewport에 해당합니다. viewport 안에 들어오는 것만 연산을 하는 거죠.

5. posible cases

이 9개로 나누어진 표에서 하나의 선분이 위치할 수 있는 예는 5 가지 정도가 있습니다. 일단 그림부터 봅시다.

가장 좋은 것은 양 끝점이 viewport 내부에 위치하는 거죠. 그림에서 l1에 해당하는 겁니다. 그럼 그냥 그리면 됩니다. 쉬운 것부터 합시다. 두 번째로 좋은 건, 두 점 모두 viewport 바깥에 있는데, x의 위치나 y의 위치가 같은 경우죠. l2에요. 이런 경우는 절대로 viewport와 만날 일이 없어요. 이건 그냥 무시하면 되요.
세번째부터는 막 그리거나 막 무시할 수 없어요. 선분과 viewport 외곽선과의 교점을 찾아내어, viewport 내부의 부분만 그리는 스킬이 필요해지죠. 그래서 이 경우는, 선분이 가진 직선의 방정식을 구해야 해요. 중학교 때 ‘일차함수’라고 배웠던 것, 혹은 고등학교에서 ‘직선의 방정식’이라고 배웠던 바로 그것이죠. l3는 viewport 외곽선과의 교점이 없는 경우에요. 교점이 없으면 무시하면 됩니다. l4는 선분의 양 끝점이 viewport 바깥에 있으면서 교점이 2개 있는 경우죠. 이 경우는 두 교점 사이를 이어야 해요. l5는 선분의 한 끝점은 viewport 내부에, 다른 한 끝점은 외부에 있는 경우에요. 내부에서 시작해 교점까지를 이어야 합니다.

6. simple linear equation

기억 나세요, 직선의 방정식? 선분의 양 끝점을 각각 (x1, y1), (x2, y2)라고 합시다. minX(혹은 maxX)와의 교점에서 y값을 구한다면,

y = (y2 - y1) / (x2 - x1) * (minX - x1) + y1

입니다. 반대의 경우는,

x = (x2 - x1) / (y2 - y1) * (minY - y1) + x1

이겠지요.

6. processing code

위키에는 이미 c로 구현된 코드가 나와있으니 저는 processing으로 구현을 해볼게요. 사실 c로 구현된 코드를 테스트할 코드를 구현하는 게 귀찮을 것 같아요. 아직은 processing이 좀더 익숙하니까요. 구현이 끝나고 시간이 남으면 openFrameworks나 openGL 코드로도 구현을 해보겠습니다. 결과는 아래와 같습니다.

Image Processing vs. Computer Graphics

7월 23rd, 2012

오늘 이건 교수님과 컴퓨터 그래픽스를 위한 첫 만남을 가졌습니다.
이야기를 시작하기 위한 가벼운 주제는, Image Processing과 Computer Graphics의 차이였습니다. Image Processing은 Input Image가 있는 상황에서 그것을 분석(Analysis)하는 것을 의미합니다. Adobe社의 Photoshop이 이런 역할들을 하죠. Computer Graphics는 이에 반해 새로운 Image를 생성(Generate)하고 합성(Synthesis)하는 것이에요. Adobe社의 Illustrator가 이런 역할을 합니다.
막상 프로그래밍을 진행해 가면 이것이 엄격하게 구분되는 요소는 아니리라 생각됩니다. 그래도 일단 개념이니까 알고 넘어가야겠지요.

인트라넷에 올리려던 글.

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