우수사례 - JAM with Chrome

오디오 록을 탄생시킨 비결

Oskar Eriksson
Oskar Eriksson

소개

Chrome으로 음악 만들기는 Google에서 만든 웹 기반 음악 프로젝트입니다. JAM with Chrome을 사용하면 전 세계 사람들이 밴드를 결성하고 브라우저 내에서 실시간으로 연주할 수 있습니다. DinahMoe는 이 프로젝트에 참여하게 되어 기쁩니다. Google의 역할은 애플리케이션용 음악을 제작하고 음악 구성요소를 설계 및 개발하는 것이었습니다. 개발은 세 가지 주요 영역으로 구성되었습니다. 미디 재생, 소프트웨어 샘플러, 오디오 효과, 라우팅, 믹싱을 포함한 '음악 워크스테이션', 실시간으로 음악을 양방향으로 제어하는 음악 로직 엔진, 세션의 모든 플레이어가 정확히 동시에 음악을 들을 수 있도록 하는 동기화 구성요소(함께 연주하기 위한 기본 요건)입니다.

최대한 높은 수준의 진위성, 정확성, 오디오 품질을 달성하기 위해 Web Audio API를 사용하기로 했습니다. 이 사례에서는 발생한 몇 가지 문제와 이를 해결한 방법을 설명합니다. HTML5Rocks에는 Web Audio를 시작하는 데 도움이 되는 여러 가지 훌륭한 소개 기사가 이미 있으므로 바로 심층적인 내용으로 들어가겠습니다.

맞춤 오디오 효과 작성

Web Audio API에는 사양에 포함된 여러 가지 유용한 효과가 있지만 Chrome을 사용하는 JAM의 악기에는 더 정교한 효과가 필요했습니다. 예를 들어 웹 오디오에는 기본 지연 노드가 있지만 스테레오 지연, 핑퐁 지연, 슬랩백 지연 등 다양한 유형의 지연이 있습니다. 다행히 이러한 효과는 모두 네이티브 효과 노드와 약간의 상상력을 사용하여 웹 오디오에서 만들 수 있습니다.

최대한 투명한 방식으로 네이티브 노드와 자체 맞춤 효과를 사용할 수 있기를 원했기 때문에 이를 실행할 수 있는 래퍼 형식을 만들어야 한다고 판단했습니다. Web Audio의 네이티브 노드는 connect 메서드를 사용하여 노드를 서로 연결하므로 이 동작을 에뮬레이션해야 했습니다. 기본 개념은 다음과 같습니다.

var MyCustomNode = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    this.connect = function(target){
       output.connect(target);
    };
};

이 패턴에서는 네이티브 노드에 매우 가깝습니다. 이 기능이 어떻게 사용되는지 살펴보겠습니다.

//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
    customNode = new MyCustomNode(),
    anotherGain = audioContext.createGain();

//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
맞춤 노드 라우팅

맞춤 노드와 네이티브 노드의 유일한 차이점은 맞춤 노드 입력 속성에 연결해야 한다는 점입니다. 이를 우회하는 방법이 있을 수도 있지만, 이 방법이 목적 달성에 충분했습니다. 이 패턴은 네이티브 AudioNode의 연결 해제 메서드를 시뮬레이션하고 연결 시 사용자 정의 입력/출력을 수용하는 등 추가로 개발할 수 있습니다. 네이티브 노드의 기능을 알아보려면 사양을 확인하세요.

이제 맞춤 효과를 만드는 기본 패턴을 알게 되었으므로 다음 단계는 맞춤 노드에 맞춤 동작을 실제로 적용하는 것입니다. 슬랩백 지연 노드를 살펴보겠습니다.

진심이 담긴 슬랩백

슬랩백 딜레이(슬랩백 에코라고도 함)는 50년대 스타일의 보컬부터 서프 기타에 이르기까지 다양한 악기에 사용되는 클래식 효과입니다. 이 효과는 수신되는 사운드를 사용하여 약 75~250밀리초의 약간의 지연을 적용하여 사운드 사본을 재생합니다. 이렇게 하면 소리가 뒤로 줄어드는 듯한 느낌을 주게 됩니다. 다음과 같이 효과를 만들 수 있습니다.

var SlapbackDelayNode = function(){
    //create the nodes we'll use
    this.input = audioContext.createGain();
    var output = audioContext.createGain(),
        delay = audioContext.createDelay(),
        feedback = audioContext.createGain(),
        wetLevel = audioContext.createGain();

    //set some decent values
    delay.delayTime.value = 0.15; //150 ms delay
    feedback.gain.value = 0.25;
    wetLevel.gain.value = 0.25;

    //set up the routing
    this.input.connect(delay);
    this.input.connect(output);
    delay.connect(feedback);
    delay.connect(wetLevel);
    feedback.connect(delay);
    wetLevel.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};
슬랩백 노드의 내부 라우팅

이미 알고 계신 분들도 있겠지만 이 지연은 더 긴 지연 시간에도 사용할 수 있으므로 피드백이 있는 일반 모노 지연이 될 수 있습니다. 다음은 이 지연을 사용하여 어떤 소리가 나는지 보여주는 예입니다.

오디오 라우팅

전문 오디오 애플리케이션에서 다양한 악기와 음악 파트를 사용할 때는 효과적인 방식으로 사운드를 믹스하고 변조할 수 있는 유연한 라우팅 시스템이 필수적입니다. Chrome을 사용한 JAM에서는 실제 믹싱 보드에 있는 것과 유사한 오디오 버스 시스템을 개발했습니다. 이렇게 하면 리버브 효과가 필요한 모든 악기를 공통 버스 또는 채널에 연결한 다음 각 악기에 리버브를 추가하는 대신 해당 버스에 리버브를 추가할 수 있습니다. 이는 중요한 최적화이며 더 복잡한 애플리케이션을 시작하는 즉시 유사한 작업을 하는 것이 좋습니다.

AudioBus 라우팅

다행히 웹 오디오에서는 이 작업을 쉽게 수행할 수 있습니다. 기본적으로 효과에 정의한 스켈레톤을 동일한 방식으로 사용할 수 있습니다.

var AudioBus = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    //create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
    var delay = new SlapbackDelayNode(),
        convolver = new tuna.Convolver(),
        equalizer = new tuna.Equalizer();

    //route 'em
    //equalizer -> delay -> convolver
    this.input.connect(equalizer);
    equalizer.connect(delay.input);
    delay.connect(convolver);
    convolver.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};

다음과 같이 사용됩니다.

//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
    instrument1 = audioContext.createOscillator(),
    instrument2 = audioContext.createOscillator(),
    instrument3 = audioContext.createOscillator();

//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);

짜잔, 이제 각 악기에 효과를 적용한 것처럼 지연, 이퀄라이제이션, 리버브(성능 측면에서 다소 비싼 효과)를 절반의 비용으로 적용했습니다. 버스에 약간의 흥미를 더하려면 두 가지 새로운 게인 노드(preGain 및 postGain)를 추가하면 됩니다. 그러면 두 가지 방법으로 버스의 사운드를 끄거나 페이드할 수 있습니다. preGain은 효과 앞에, postGain은 체인의 끝에 배치됩니다. 그런 다음 preGain을 페이드하면 이득이 바닥을 쳤을 때도 효과가 계속 울려 퍼지지만 postGain을 페이드하면 모든 소리가 동시에 음소거됩니다.

이제 어디로 가야 하나요?

여기서 설명한 방법은 더 발전시킬 수 있으며 발전시켜야 합니다. 맞춤 노드의 입력 및 출력, 연결 메서드와 같은 항목은 프로토타입 기반 상속을 사용하여 구현할 수 있고 구현해야 합니다. 버스는 효과 목록을 전달받아 효과를 동적으로 만들 수 있어야 합니다.

Chrome용 JAM 출시를 기념하여 효과 프레임워크를 오픈소스로 제공하기로 결정했습니다. 이 간단한 소개가 마음에 드셨다면 한번 살펴보고 자유롭게 참여해 주세요. 맞춤 웹 오디오 항목의 형식을 표준화하는 것에 관한 논의는 여기에서 진행 중입니다. 시청자와 소통하세요