오디오 태그: 웹 컴포넌트 + 웹 오디오 = ♥

이 글은 솔데다드 페나테스()와 안젤리나 파브로()의 Audio Tags: Web Components + Web Audio = ♥ 한국어 번역본입니다.

지난주 우리는 빠른 개발을 위해 필요한 웹 컴포넌트를 모아놓은 Brick 1.0을 공개했습니다. 브릭은 사용하기가 정말 쉽고 이미 만들어놓은 코드나 프레임워크에 UI 위젯을 통합할 수 있습니다.

이번 주에는 소리를 다루고 완벽한 악기를 구현할 수 있는 웹 오디오 블록으로 이루어진 웹 컴포넌트인 Audio 태그를 공개합니다. 오디오 블록은 재사용이 가능하며 개발자가 많은 분량의 상용구 코드를 사용하지 않고도 웹 오디오를 다룰 수 있습니다.

그럼 태그가 어떻게 동작하는지 살펴볼 수 있는 간단한 신시사이저를 만들어보겠습니다!

오디오 컨텍스트

먼저 필요한 것은 오디오 컨텍스트입니다. Canvas를 가지고 코드를 작성해봤다면 익숙하게 느낄겁니다.  컨텍스트는 도구 상자와 비슷합니다. 필요한 함수(도구)를 가지고 있고 어디에서든 사용할 수 있습니다. 모든 Audio 태그는 컨텍스트 안에 넣습니다.

Audio 태그를 사용할 때 오디오 컨텍스트는 아래와 같이 적용합니다.

<audio-context>
</audio-context>

정말 쉽죠!

오실레이터

한 개의 태그 선언만으로 오디오 컨텍스트를 만들 수 있다는 것은 정말 굉장한 일입니다. 하지만 아무런 소리도 만들어낼 수 없다면 의미가 없겠죠. 소리를 만들기 위해서는 약간의 작업이 필요합니다. 이번 단계에서는 오실레이터(oscillators)를 사용하는 간단한 작업부터 시작해보겠습니다. 이름에서 나타나는 것처럼 우리는 -1과 1 두 개의 값 사이를 왔다 갔다 하는 신호를 만들어 주기적인 파형을 만들 겁니다. 만들어진 것을 오디오 컨텍스트 안에 넣기만 하면 자동으로 컨텍스트의 출력 형식으로 컴퓨터 스피커에 전달됩니다.

<audio-context>
    <audio-oscillator>
    </audio-oscillator>
</audio-context>

Context with oscillator

(실행해보기).

현실 세계에서 오실레이터는 다양한 신호 형식을 만들 수 있습니다. 마찬가지로 웹 오디오도 사인파(sine), 사각파(square), 톱니파(sawtooth), 삼각파(triangle)와 같은 파형을 선택해 사용할 수 있습니다. 웹 컴포넌트는 DOM 엘리먼트의 첫 번째 클래스이기 때문에 아래와 같이 원하는 형식의 파형을 속성으로 지정해 설정할 수 있습니다.

<audio-oscillator type="square"></audio-oscillator>

콘솔을 열어 아래와 같이 입력하면 웹브라우저에서 바로 파형의 형식을 변경할 수 있습니다.

document.querySelector('audio-oscillator').type = 'square';

마찬가지로 frequency 속성을 지정하면 오실레이터의 주파수를 변경할 수 있습니다.

<audio-oscillator frequency="220"></audio-oscillator>

믹서

첫 번째 단계에서 실제 동작하는 오실레이터를 만들어보았습니다. 신시사이저 대부분은 한 번에 하나 이상의 오실레이터를 실행해 복잡하고 섬세한 소리를 만들어낼 수 있습니다. 이번에는 동시에 두 개 이상의 소리를 만들어서 하나의 출력으로 내보내는 작업을 해보겠습니다.

이런 작업을 오디오 믹싱이라고 하며 믹서(mixer)라는 것이 필요합니다.

<audio-context>
    <audio-mixer>
        <audio-oscillator frequency="220"></audio-oscillator>
        <audio-oscillator frequency="440"></audio-oscillator>
    </audio-mixer>
</audio-context>

믹서의 역할은 자신의 자식에서 만든 소리를 가져온 다음 이를 조합해서 자신의 출력으로 처리하고 컨텍스트의 출력으로 연결합니다. 기억해야 할 것은 우리가 다루는 것은 DOM 엘리먼트라는 것입니다. 여기서 ‘자식’이라는 표현은 믹서의 DOM 자식 엘리먼트를 의미합니다.

Mixer

(실행해보기).

체인(그리고 오실로스코프)

여러 개의 소리를 추가하고자 할 때 신시사이저에서 무슨일이 일어나고 있는지 확인할 수 있다면 매우 유용할 것입니다. 이럴 때 자식 요소의 출력과 다른 자식 요소의 입력 사이에 컴포넌트를 살짝 껴놓고 현재 시점에 어떤 소리가 재생되고 있는지 눈으로 볼 수 있다면 정말 좋겠죠.

믹서는 여러 소리의 출력을 합치는 것이기 때문에 이런 방식을 사용할 수 없습니다. 그래서 체인(chain)이라는 새로운 추상적인 구조가 필요합니다. 오디오 체인은 첫 번째 자식 요소의 출력을 두 번째 자식 요소의 입력으로 연결해주는 역할을 합니다. 그리고 두 번째 자식 요소의 출력은 다시 세 번째 요소의 입력으로 넘어갑니다. 이런 과정이 마지막 자식 요소를 만날 때까지 계속됩니다. 그리고 마지막 출력은 체인 자체의 출력으로 연결됩니다.

정리해보면 믹서는 여러 가지 요소를 병렬로 연결하고 체인은 여러 가지 요소를 직렬로 연결합니다.

체인을 사용해 오실레이터의 출력을 새로운 요소인 오실로스코프(oscilloscope)로 연결해보겠습니다. 오실로스코프는 어떤 것이 입력부로 연결되었는지를 보여주는 역할만 담당하고 신호는 전혀 변경되지 않은 상태로 다시 출력합니다. 오실레이터의 파형 형식을 사각파(square)로 바꾸어보면 오실로스코프에서 이를 어떻게 보여주는지 확인할 수 있습니다.

<audio-context>
    <audio-chain>
        <audio-oscillator frequency="220"></audio-oscillator>
        <audio-oscilloscope></audio-oscilloscope>
    </audio-chain>
</audio-context>

Chain

(실행해보기).

필터

신시사이저는 한 번에 실행할 수 있는 오실레이터의 개수에 제한을 두지 않습니다. 처음 생성된 소리는 후처리 과정을 거치면서 신시사이저는 독특한 자신만의 소리를 만듭니다.

후처리 효과를 추가하는 방식은 여러 가지가 있습니다. 그중에서 가장 일반적인 것은 필터(filter)라는 것입니다. 필터는 특정한 주파수를 강화하거나 삭제하는 작업을 할 수 있습니다. 예를 들어 오실레이터의 출력에 저역 통과 필터를 체인으로 연결할 수 있습니다. 그렇게 하면 특정 주파수보다 낮은 주파수만 통과합니다. 이런 과정은 일종의 완화(dampening) 효과입니다. 귀마개를 하고 있을 때 높은 주파수는 공기를 통해 귀에 전달됩니다. 반면에 낮은 주파수는 땅이나 사물을 통해 전달됩니다. 그래서 귀가 아닌 몸으로 느끼게 됩니다. 소리를 받아들이는 과정에서 귀로 듣든지 아니든 큰 상관은 없습니다.

<audio-context>
    <audio-chain>
        <audio-oscillator frequency="220"></audio-oscillator>
        <audio-filter type="lowpass"></audio-filter>
    </audio-chain>
</audio-context>

Filter

(실행해보기).

웹 오디오는 바이쿼드 폴 필터로 구현되어 있습니다. audio-oscillator 태그의 기본 기능 역시 마찬가지다. 필터에서 type 속성을 사용하면 필터의 동작 형식을 바꿀 수 있습니다.

<audio-filter type="highpass"></audio-filter>

오실로스코프는 아래 예제처럼 필터 앞과 뒤에 여러 개 추가해 필터가 신호에 어떻게 영향을 미치는지 확인해볼 수 있습니다.

<audio-context>
    <audio-chain>
        <audio-oscillator frequency="220"></audio-oscillator>
        <audio-oscilloscope></audio-oscilloscope>
        <audio-filter type="lowpass"></audio-filter>
        <audio-oscilloscope></audio-oscilloscope>
    </audio-chain>
</audio-context>

Filter with two oscilloscopes

(Example).

마지막으로 간단한 신시사이저 만들기

이제 신시사이저를 만들 수 있는 적절한 컴포넌트를 확보했습니다. 두 개의 오실레이터(하나는 다른 하나보다 한 옥타브 높은)가 같이 동작하고 필터를 사용해 귀에 거슬리는 소리를 제거해 좀 더 깔끔한 소리를 만듭니다. 이렇게 하면 간단한 신시사이저를 구현할 수 있는 구조가 만들어졌습니다. <mini-synth> 는 지금까지 소개한 컴포넌트를 사용해 만들었습니다.

<audio-chain>
    <audio-mixer>
        <audio-oscillator></audio-oscillator>
        <audio-oscillator></audio-oscillator>
    </audio-mixer>
    <audio-filter type="lowpass"></audio-filter>
</audio-chain>

비교를 위해서 Web Audio API 오브젝트와 함수만 가지고 비슷한 기능을 만든다면 아래와 같습니다.

var mixerGain = context.createGain();

var osc1 = context.createOscillator();
var osc2 = context.createOscillator();
osc1.connect(mixerGain);
osc2.connect(mixerGain);

var filter = context.createBiquadFilter();
mixerGain.connect(filter);

// and the actual output is at *filter*

코드 자체는 복잡하지 않지만, 구문의 흐름이 시각적인 체계를 잘 보여주지는 못합니다. 코드의 흐름이 시각적인 단서를 제공하면 요소들 간의 관계를 쉽고 빠르게 이해할 수 있습니다.

<mini-synth> 컴포넌트가 신시사이저처럼 동작하게 하기 위해서는 약간의 자바스크립트를 추가해야 합니다. 이렇게 하면 한번에 오실레이터가 시작하고 끝낼 수 있습니다. AudioTag prototype는 공통으로 활용할 수 있는 기본 메소드를 제공합니다. 이 중에서 일부 기능을 오버로드(overload)해서 우리의 컴포넌트에 추가할 수 있습니다.

신시사이저에서 start, stop 메소드를 호출할 때 오실레이터가 소리 재생을 시작하거나 멈출 수 있도록   start, stop 메소드를 오버로드합니다. 현실 세계에 있는 신시사이저의 내부 기능을 추상화하는 방법이지만 이렇게 하면 일관성 있는 인터페이스를 제공할 수 있습니다.

start: function(when) {
    // We want to make sure we don't clip (i.e. go under -1 or over 1),
    // so we'll divide the gain by the number of oscillators in the synth
    var oscGain = this.oscillators.length > 0 ? 1.0 / this.oscillators.length : 1.0;
    this.oscillators.forEach(function(osc) {
        osc.gain = oscGain;
        osc.start(when);
    });
}

stop: function(when) {
    this.oscillators.forEach(function(osc) {
        osc.stop(when); 
    });
}

전체 구현 코드를 살펴보면 좀 더 명확해질 겁니다.

코드에서 when 이라는 파라미터에 대해 궁금하실 겁니다. 이것은 브라우저에게 실제 동작이 시작되었다는 것을 전달해줍니다. 그래서 정확한 시점에 다양한 이벤트를 처리할 수 있도록 합니다. 즉 “이 코드는 when 밀리세컨드에 시작하세요”라는 의미입니다. 전달하는 값에 0을 대입하면 “즉시 시작하세요”라는 의미가 됩니다. when 파라미터에 대해 좀 더 자세한 내용은 Web Audio 스펙을 참고하세요.

하나 더 필요한 것은 신시사이저에 재생할 음을 알려주는 메소드를 만들어야 합니다. 즉 어떤 주파수를 오실레이터에서 처리해야 하는지 말입니다. noteOn이라는 이름으로 메소드를 구현해보죠.

noteOn: function(noteNumber) {
    this.oscillators.forEach(function(osc, index) {
        // Each oscillator should play in a higher octave
        // Each octave is composed of 12 notes
        var oscNoteNumber = noteNumber + 12 * index;
        // We're using a library to convert note numbers to frequencies
        var frequency = MIDIUtils.noteNumberToFrequency(oscNoteNumber);
        osc.frequency = frequency; 
    });
}

MIDUtils를 사용할 필요는 없지만, 브라우저에서 여러 악기의 즉흥연주를 하고 싶거나 전통적인 미디 장비처럼 사용하고 싶다면 쓸모가 있을 겁니다. 표준 주파수를 사용하면서 두 개의 악기가 같은 음을 낼 수 있습니다. 정말 멋지죠.

그리고 신시사이저에서 음을 처리할 방법이 필요합니다. 화면상에 건반 컴포넌트를 구현하는 것이 좋을 것 같네요.

<audio-keyboard octaves="2"></audio-keyboard>

추가할 건반 컴포넌트는 2옥타브를 가집니다. 건반이 포커스를 가지게 되면(컴포넌트를 클릭했을 때) 컴퓨터 키보드에서 해당 키를 사용할 수 있습니다. 키를 입력하면 noteon  이벤트가 전달됩니다. 그리고 이 소리를 듣기 위해서 신시사이저로 전달할 수 있습니다. noteoff  이벤트도 마찬가지입니다.

keyboard.addEventListener('noteon', function(e) {

  var noteIndex = e.detail.index;
  // 48 is the base note here = C-3
  minisynth.noteOn(parseInt(noteIndex, 10) + 48);
  minisynth.start();

}, false);

keyboard.addEventListener('noteoff', function(e) {

  minisynth.noteOff();

}, false);

자 이제 데모를 확인해보시죠.

Minisynth

우리만의 신시사이저를 가지게 되었습니다. 이제는 어디 가서 뮤지션이라고 이야기해도 되겠죠. 하지만 진정한 뮤지션이라면 좀 더 멋져 보여야 합니다. 현실 세계의 뮤지션들은 자신만의 이름이 새겨진 기타와 멋진 케이스를 가지고 있습니다. 그렇다면 우리에게는… 아 CSS가 있죠! 데모에서 ‘Become a rockstar’ 버튼을 눌러보면 신시사이저가 뭔가 멋지게 바뀐 것을 확인할 수 있습니다. CSS가 선사해주는 마법에 감사해야겠네요.

Minisynth, rockstarified

무대 뒤 살펴보기

지금까지 우리는 정말 멋진 새로운 오디오 태그에 관해 이야기했고 브라우저에서 잘 동작하는 것도 확인했습니다. 물론 아직은 표준 요소는 아니지만 말이죠. 오디오 태그와 표준에 대해서는 우리도 정확하게 이야기해줄 수 없습니다. 하지만 이미 여러분은 여기까지 읽으면서 많은 것을 알게 되었고 이 비밀스러운 이야기를 좀 더 알고 싶을 것입니다.

예제에 사용한 소스 코드를 살펴보면 AudioTags.bundle.js (line 18) 이라는파일과  AudioTags.bundle.css (line 6) 파일을 포함하고 있는 것을 확인할 수 있습니다. CSS에서 특별히 처리하는 것은 없고 대부분의 마법은 자바스크립트에서 처리하고 있습니다. 자바스크립트 파일에는 두 개의 유틸리티 라이브러리가 담겨 있습니다. 각 라이브러리는 브라우저에서 커스텀 태그를 정의할 수 있는 기능을 제공하며 정의된 코드를 브라우저에서 새로운 태그처럼 사용할 수 있게 합니다.

좀 더 상세히 들여다보면 먼저  AudioContext-MonkeyPatch 라는 라이브러리를 찾을 수 있습니다. 이 라이브러리는 브라우저마다 다른 Web Audio API를 하나로 합쳐주는 기능을 제공해 일관성 있는 구문처리를 할 수 있습니다. 높은 이식성을 가지는 Web Audio 코드를 만드는 방법에 대해 더 궁금하다면 이 글을 참고하세요.

두 번째 라이브러리는 X-Tag 입니다.  이 라이브러리는 가장 안쪽의 핵심 영역을 건드립니다. X-Tag는 커스텀 요소 폴리필(polyfill. 오래된 브라우저에서도 최신 HTML5 API를 사용할 수 있게 해주는 기능)입니다. 커스텀 요소는 웹 컴포넌트 스펙 중에서 최근에 만들어진 것입니다. 이제 막 브라우저에서 구현되고 있는 중이죠. X-Tag는 모질라 브릭에서 사용하는 것과 같은 라이브러리입니다. 커스텀 요소를 어떻게 사용하는지에 대해서는 이 글을 참고하세요.

혹시 브릭과 오디오 태그를 같은 프로젝트에서 사용한다면 심각한 문제가 생길 수 있습니다. 브릭과 오디오 태그 모두 배포할 때 X-Tag를 포함하고 있어 충돌이 일어날 수 있습니다. 두 라이브러리 제작팀이 이 문제를 같이 논의하고 있지만, 오디오 태그가 이제 막 X-Tag 라이브러리를 사용하기 시작해서 아직 결론이 나지 않았습니다. 아마 브릭이나 오디오 태그 사용 시 X-Tag를 포함하지 않는 옵션을 제공할 가능성이 높을 듯 합니다.

마찬가지로 CascadiaJS 역시 같은 문제를 가지고 있습니다. 아래 비디오를 보면 관련된 문제를 좀 더 이해할 수 있을 겁니다.

오디오 태그의 미래는?

많은 분이 오디오 태그는 앞으로 어떻게 되는지 물어봅니다. 어떤 기능이 추가되나요? 일정이 어떻게 되죠? 계속 프로젝트를 진행할 건가요? 새로운 태그를 추가하려면 어떻게 하죠?

솔직히 말하면 어떤 계획도 없습니다. 그게 멋진 점이죠. 이제 막 출발한 프로젝트이며 좀 더 많은 것을 생각해야 합니다. 오디오 컴포넌트를 정의하는 개념에 대해 다양한 시도와 논의가 필요합니다. 오디오 태그 안내문에 소개된 기능 중 아직 동작하지 않는 것들도 많이 있고 떠오르는 생각들과 잠재적인 기능들도 있습니다. 오디오에 대한 경험이 골치 아픈 일이 되지 않도록 기능을 확장하고 문제점을 해결해나갈 겁니다. 그리고 웹 컴포넌트를 테스트하는 좋은 방법은 “빠르게 진화하는 캡슐화된 UI 위젯(encapsulated UI widgets on steroids)” 개념을 계속 적용하는 것일 겁니다.

프로젝트는 자발적으로 다양하게 진화하고 있습니다. 어떤 이들은 신호처리(signal processing)를 가르치는데 유용하게 사용하고 있으며 가속도계(accelerometer) 데이터와 연결해서 물리적으로 조작할 수 있는 신시사이저를 만들기도 합니다. 그리고 오디오 관련 기능을 조작해 WebRTC를 처리하는 커스텀 컴포넌트로 만들기도 합니다. 하고 싶은 것을 하면서 프로젝트에 참여할 수 있다는 거죠!

작성자: Joon-Ha Lee

左手はそえるだけ

Joon-Ha Lee가 작성한 문서들…


1개 댓글

  1. hadeath

    와…

    5월 9th, 2014 at 10:19 오전

댓글 쓰기