Web Audio API를 사용한 게임 오디오 개발

소개

오디오는 멀티미디어 경험을 매력적으로 만드는 데 큰 부분을 차지합니다. 소리를 끈 상태로 영화를 본 적이 있다면 이를 아실 것입니다

게임도 예외는 아닙니다. 비디오 게임에서 가장 좋아하는 기억은 음악과 음향 효과였습니다. 좋아하는 곡을 플레이한 지 거의 20년이 지난 지금도 곤도 코지의 젤다 컴포지션맷 울멘의 분위기 있는 디아블로 사운드트랙을 머릿속에 떠올리지 못하는 경우가 많습니다. Warcraft의 즉시 인식 가능한 단위 클릭 응답과 Nintendo의 고전 샘플 등 사운드 효과에도 동일한 특징이 적용됩니다.

게임 오디오에는 몇 가지 흥미로운 도전과제가 있습니다. 설득력 있는 게임 음악을 만들기 위해 디자이너는 플레이어가 예측하기 어려운 게임 상태에 적응해야 합니다. 실제로 게임의 부분이 알 수 없는 시간 동안 지속될 수 있고 사운드가 환경과 상호작용하며 방 효과 및 상대적 사운드 위치와 같은 복잡한 방식으로 믹스될 수 있습니다. 마지막으로, 한 번에 많은 사운드가 재생될 수 있으며, 이러한 모든 사운드는 성능 저하 없이 좋은 소리를 내고 렌더링해야 합니다.

웹에서 즐기는 게임 오디오

간단한 게임의 경우 <audio> 태그를 사용하는 것으로 충분할 수도 있습니다. 하지만 대다수의 브라우저에서는 구현이 제대로 이루어지지 않아 오디오 결함과 긴 지연 시간이 발생합니다. 공급업체에서 각각의 구현을 개선하기 위해 열심히 노력하고 있으므로 이는 일시적인 문제가 될 수 있습니다. <audio> 태그의 상태를 엿볼 수 있도록 areweplayingyet.org에 유용한 테스트 모음이 있습니다.

그러나 <audio> 태그 사양을 자세히 살펴보면 미디어 재생용으로 설계되었기 때문에 당연히 수행할 수 없는 작업이 많다는 것을 알 수 있습니다. 일부 제한사항은 다음과 같습니다.

  • 소리 신호에 필터를 적용할 수 없음
  • 원시 PCM 데이터에 액세스할 방법이 없음
  • 소스 및 리스너의 위치와 방향 개념 없음
  • 세분화된 타이밍이 없습니다.

이 문서의 나머지 부분에서는 Web Audio API로 작성된 게임 오디오 맥락에서 이러한 주제 중 일부를 자세히 알아봅니다. 이 API에 대한 간략한 소개는 시작하기 튜토리얼을 참고하세요.

배경음악

게임에서 백그라운드 음악이 반복되는 경우가 많습니다.

루프가 짧고 예측 가능한 경우 매우 성가실 수 있습니다. 플레이어가 특정 영역이나 레벨에 머물러 있고 동일한 샘플이 백그라운드에서 계속 재생되는 경우 더 당황하지 않도록 트랙을 점진적으로 페이드 아웃하는 것이 좋습니다. 또 다른 전략은 게임의 컨텍스트에 따라 서서히 서로 크로스페이싱하는 다양한 강도의 조합을 사용하는 것입니다.

예를 들어 플레이어가 장대한 보스 전투가 벌어지는 구역에 있는 경우 분위기에서 예감, 강렬한 감정 범위까지 다양한 믹스를 사용할 수 있습니다. 음악 합성 소프트웨어를 사용하면 내보내기에 사용할 트랙 세트를 선택하여 곡을 기반으로 여러 믹스 (같은 길이)를 내보낼 수 있는 경우가 많습니다. 이렇게 하면 내부 일관성을 유지하고 한 트랙에서 다른 트랙으로 크로스 페이드할 때 전환이 부자연스럽지 않습니다.

차고 밴드

그런 다음 웹 오디오 API를 사용하면 XHR을 통해 BufferLoader 클래스 등을 사용해 이러한 샘플을 모두 가져올 수 있습니다. 자세한 내용은 Web Audio API 소개 도움말을 참고하세요. 사운드를 로드하는 데는 시간이 걸리므로 게임에서 사용되는 애셋은 페이지 로드 시, 레벨이 시작될 때 또는 플레이어가 플레이하는 동안 점진적으로 로드해야 합니다.

다음으로 각 노드의 소스와 각 소스의 게인 노드를 만들고 그래프를 연결합니다.

이렇게 하면 루프에서 이러한 모든 소스를 동시에 재생할 수 있으며 길이가 모두 동일하기 때문에 Web Audio API가 정렬된 상태를 보장합니다. 캐릭터가 최종 보스 전투에서 가까워지거나 멀어지면 게임은 다음과 같은 획득량 알고리즘을 사용하여 체인에 있는 각 노드의 게인 값을 변경할 수 있습니다.

// Assume gains is an array of AudioGainNode, normVal is the intensity
// between 0 and 1.
var value = normVal - (gains.length - 1);
// First reset gains on all nodes.
for (var i = 0; i < gains.length; i++) {
    gains[i].gain.value = 0;
}
// Decide which two nodes we are currently between, and do an equal
// power crossfade between them.
var leftNode = Math.floor(value);
// Normalize the value between 0 and 1.
var x = value - leftNode;
var gain1 = Math.cos(x - 0.5*Math.PI);
var gain2 = Math.cos((1.0 - x) - 0.5*Math.PI);
// Set the two gains accordingly.
gains[leftNode].gain.value = gain1;
// Check to make sure that there's a right node.
if (leftNode < gains.length - 1) {
    // If there is, adjust its gain.
    gains[leftNode + 1].gain.value = gain2;
}

위의 접근 방식에서는 두 소스가 한 번에 재생되고 소개에 설명된 대로 동일한 전력 곡선을 사용하여 두 소스를 크로스페이드합니다.

오늘날 많은 게임 개발자들은 <audio> 태그를 배경 음악으로 사용합니다. 이는 스트리밍 콘텐츠에 매우 적합하기 때문입니다. 이제 <audio> 태그의 콘텐츠를 웹 오디오 컨텍스트로 가져올 수 있습니다.

이 기법은 <audio> 태그를 통해 콘텐츠가 모두 다운로드될 때까지 기다릴 필요 없이 즉시 배경 음악을 재생할 수 있는 스트리밍 콘텐츠와 함께 작동할 수 있으므로 유용할 수 있습니다. 스트림을 Web Audio API로 가져와 스트림을 조작하거나 분석할 수 있습니다. 다음 예에서는 <audio> 태그를 통해 재생되는 음악에 로우 패스 필터를 적용합니다.

var audioElement = document.querySelector('audio');
var mediaSourceNode = context.createMediaElementSource(audioElement);
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
mediaSourceNode.connect(filter);
filter.connect(context.destination);

<audio> 태그를 Web Audio API와 통합하는 방법에 관한 자세한 내용은 이 짧은 문서를 참고하세요.

음향 효과

게임은 사용자 입력이나 게임 상태 변경에 응답하여 음향 효과를 재생하는 경우가 많습니다. 그러나 배경 음악과 마찬가지로 음향 효과도 금방 성가실 수 있습니다. 이를 방지하기 위해 비슷하면서도 다른 사운드로 구성된 풀을 재생하는 것이 유용할 때가 많습니다. 이는 단위 클릭에 대한 반응으로 Warcraft 시리즈에서 볼 수 있듯이 발걸음 샘플의 경미한 변화부터 급격한 변화에 이르기까지 다양할 수 있습니다.

게임 음향 효과의 또 다른 주요 특징은 동시에 여러 개의 음향 효과가 있을 수 있다는 것입니다. 여러분이 여러 배우와 기관 총을 쏘는 총격전을 벌이고 있다고 상상해 보세요 각 기관총은 1초에 여러 번 발사되므로 수십 개의 음향 효과가 동시에 재생됩니다. 정확하게 시간이 지정된 여러 소스의 사운드를 동시에 재생하는 기능은 Web Audio API가 가장 빛을 발하는 기능입니다.

다음 예에서는 시간에 따라 시차를 두고 재생되는 사운드 소스를 여러 개 만들어 개별 글머리기호 샘플에서 기관총 라운드를 만듭니다.

var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
    var source = this.makeSource(this.buffers[M4A1]);
    source.noteOn(time + i - interval);
}

게임의 모든 기관기가 정확히 이런 소리를 내면 꽤 지루할 것입니다. 물론 타겟으로부터의 거리와 상대 위치 (나중에 자세히 설명)에 따라 사운드에 따라 달라지지만 이것만으로는 충분하지 않을 수도 있습니다. 다행히 Web Audio API를 사용하면 위의 예를 다음 두 가지 방법으로 쉽게 조정할 수 있습니다.

  1. 총알이 발사되는 시간 사이의 미묘한 변화
  2. 실제 세계의 무작위성을 더 잘 시뮬레이션하기 위해 각 샘플의 playRate (음높이도 변경)를 변경합니다.

이러한 기법의 실제 예를 더 보려면 당구대 데모를 살펴보세요. 무작위 샘플링을 사용하고 좀 더 흥미로운 공 충돌 소리를 낼 수 있도록 playbackRate를 변화시킵니다.

3D 위치 사운드

게임은 2D 또는 3D의 기하학적 속성이 있는 세계에서 설정되는 경우가 많습니다. 이 경우 스테레오로 배치된 오디오를 통해 환경의 몰입도를 크게 높일 수 있습니다. 다행히 Web Audio API에는 매우 간편하게 사용할 수 있는 하드웨어 가속 위치 오디오 기능이 내장되어 있습니다. 다음 예의 경우 적절한 스테레오 스피커 (헤드폰 권장)가 있는지 확인해야 합니다.

위의 예에서는 캔버스 중앙에 리스너 (사람 아이콘)가 있고 마우스가 소스의 위치 (스피커 아이콘)에 영향을 미칩니다. 위의 예는 AudioPannerNode를 사용하여 이러한 종류의 효과를 달성하는 간단한 예입니다. 위 샘플의 기본 개념은 다음과 같이 오디오 소스의 위치를 설정하여 마우스 움직임에 반응하는 것입니다.

PositionSample.prototype.changePosition = function(position) {
    // Position coordinates are in normalized canvas coordinates
    // with -0.5 < x, y < 0.5
    if (position) {
    if (!this.isPlaying) {
        this.play();
    }
    var mul = 2;
    var x = position.x / this.size.width;
    var y = -position.y / this.size.height;
    this.panner.setPosition(x - mul, y - mul, -0.5);
    } else {
    this.stop();
    }
};

웹 오디오의 공간화 처리에 관해 알아야 할 사항은 다음과 같습니다.

  • 리스너는 기본적으로 원점 (0, 0, 0)에 있습니다.
  • Web Audio Positional API는 단위가 없으므로 데모 사운드를 개선하기 위해 승수를 도입했습니다.
  • 웹 오디오는 y-is-up 데카르트 좌표 (대부분의 컴퓨터 그래픽 시스템과 반대)를 사용합니다. 이러한 이유로 위 스니펫에서 y축을 바꾸어 보겠습니다.

고급: 사운드콘

위치 모델은 매우 강력하고 발전적이며 주로 OpenAL에 기반합니다. 자세한 내용은 위에 링크된 사양의 섹션 3과 4를 참고하세요.

게재순위 모델

Web Audio API 컨텍스트에 연결된 AudioListener는 위치와 방향을 통해 공간에서 구성할 수 있습니다. 각 소스는 입력 오디오를 공간화하는 AudioPannerNode를 통해 전달될 수 있습니다. 화면 이동 노드에는 위치, 방향, 거리 및 방향 모델이 있습니다.

거리 모델은 소스에 대한 근접성에 따라 게인의 양을 지정하는 반면, 방향 모델은 리스너가 내부 원뿔 내부와 외부 원뿔 사이 또는 외부 원뿔 외부에 있는 경우 이득의 양을 결정하는 내부 및 외부 원뿔을 지정하여 구성할 수 있습니다.

var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;

예시가 2D이지만 이 모델은 쉽게 3차원으로 일반화됩니다. 3D로 공간화된 사운드의 예는 이 위치 샘플을 참고하세요. 웹 오디오 사운드 모델에는 위치 지정 외에도 도플러 시프트의 속도도 선택적으로 포함됩니다. 이 예는 도플러 효과를 자세히 보여줍니다.

이 주제에 관한 자세한 내용은 [위치 오디오와 WebGL 믹싱][webgl]의 세부 가이드를 참조하세요.

방 효과 및 필터

실제로 소리가 인식되는 방식은 소리가 들리는 방에 따라 크게 달라집니다. 지하실에서는 똑같은 삐걱거리는 문 소리가 큰 개방형 홀에 비해 매우 다르게 들립니다. 프로덕션 가치가 높은 게임은 이러한 효과를 모방하는 것이 좋습니다. 각 환경에 별도의 샘플 세트를 만드는 것은 비용이 많이 들고 더 많은 애셋과 더 많은 게임 데이터로 이어질 수 있기 때문입니다.

대략적으로 말하자면 원시 소리와 실제 소리 사이의 차이를 나타내는 오디오 용어는 임펄스 응답입니다. 이러한 임펄스 응답은 공들여 녹음될 수 있으며, 실제로 편의를 위해 사전 녹음된 다수의 임펄스 응답 파일 (오디오로 저장됨)을 호스팅하는 사이트도 있습니다.

주어진 환경에서 임펄스 응답을 만드는 방법에 관한 자세한 내용은 웹 오디오 API 사양의 컨볼루션 부분에서 '녹음 설정' 섹션을 참고하세요.

무엇보다도 이러한 목적상 Web Audio API는 ConvolverNode를 사용하여 이러한 임펄스 응답을 소리에 적용할 수 있는 쉬운 방법을 제공합니다.

// Make a source node for the sample.
var source = context.createBufferSource();
source.buffer = this.buffer;
// Make a convolver node for the impulse response.
var convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);

Web Audio API 사양 페이지의 방 효과 데모와 훌륭한 재즈 표준의 드라이(원시) 및 습식 (컨볼버를 통해 처리)을 제어할 수 있는 이 예도 확인하세요.

마지막 카운트다운

게임을 빌드하고 위치 오디오를 구성했으며 이제 그래프에 많은 수의 AudioNode가 모두 동시에 재생됩니다. 좋습니다. 하지만 한 가지 더 고려해야 할 사항이 있습니다.

여러 사운드가 정규화 없이 서로 쌓여 있기 때문에 스피커 기능 임곗값을 초과하는 상황에 처할 수 있습니다. 캔버스 경계를 초과하는 이미지와 마찬가지로 파형이 최대 기준점을 초과하면 사운드가 클립되어 뚜렷한 왜곡이 발생할 수 있습니다. 파형은 다음과 같습니다.

클리핑

다음은 클립의 실제 예입니다. 파형이 좋지 않습니다.

클리핑

위와 같이 거슬리는 디스토션이나 반대로 청취자가 볼륨을 크게 하도록 만드는 지나치게 차분한 믹스를 듣는 것이 중요합니다. 이러한 상황에서는 정말로 문제를 해결해야 합니다.

클리핑 감지

기술적 관점에서 클리핑은 채널의 신호 값이 유효한 범위, 즉 -1과 1 사이의 값을 초과할 때 발생합니다. 이러한 동작이 감지되면 이 문제가 발생했다는 시각적 피드백을 제공하는 것이 좋습니다. 안정적으로 실행하려면 그래프에 JavaScriptAudioNode를 넣습니다. 오디오 그래프는 다음과 같이 설정됩니다.

// Assume entire sound output is being piped through the mix node.
var meter = context.createJavaScriptNode(2048, 1, 1);
meter.onaudioprocess = processAudio;
mix.connect(meter);
meter.connect(context.destination);

또한 다음 processAudio 핸들러에서 클리핑이 감지될 수 있습니다.

function processAudio(e) {
    var buffer = e.inputBuffer.getChannelData(0);

    var isClipping = false;
    // Iterate through buffer to check if any of the |values| exceeds 1.
    for (var i = 0; i < buffer.length; i++) {
    var absValue = Math.abs(buffer[i]);
    if (absValue >= 1) {
        isClipping = true;
        break;
    }
    }
}

일반적으로 성능상의 이유로 JavaScriptAudioNode를 과도하게 사용하지 않도록 주의하세요. 이 경우 측정의 대체 구현은 requestAnimationFrame에 의해 결정된 대로 렌더링 시 getByteFrequencyData의 오디오 그래프에서 RealtimeAnalyserNode를 폴링할 수 있습니다. 이 접근 방식은 더 효율적이지만, 렌더링이 초당 최대 60회 발생하는 반면 오디오 신호는 훨씬 더 빠르게 변경되므로 대부분의 신호 (클립 가능성이 있는 위치 포함)가 누락됩니다.

클립 감지는 매우 중요하므로 향후에 기본 제공되는 MeterNode Web Audio API 노드를 사용할 가능성이 높습니다.

클리핑 방지

마스터 AudioGainNode에서 게인을 조정하여 클리핑을 방지하는 수준으로 믹스를 낮출 수 있습니다. 그러나 실제로 게임에서 재생되는 사운드는 매우 다양한 요인에 따라 달라질 수 있으므로 모든 상태에 대한 클리핑을 방지하는 마스터 게인 값을 결정하기 어려울 수 있습니다. 일반적으로 최악의 경우를 대비하여 이득을 조정해야 하지만 이는 과학이라기보다는 예술에 가깝습니다.

설탕 약간 추가

압축기는 일반적으로 음악 및 게임 제작에서 신호를 완화하고 전체 신호의 급증을 제어하는 데 사용됩니다. 이 기능은 DynamicsCompressorNode를 통해 웹 오디오 환경에서 사용할 수 있습니다. 이 기능을 오디오 그래프에 삽입하면 더 크고 풍성하며 풍부한 사운드를 제공할 수 있으며 클리핑에도 도움이 됩니다. 사양을 직접 인용하면 이 노드는

다이내믹 압축은 일반적으로 이전에 논의한 것처럼 어떤 소리가 언제 재생될지 정확히 모르는 게임 환경에서 사용하는 것이 좋습니다. DinahMoe 실습의 Plink가 좋은 예입니다. 완전히 재생되는 사운드는 나와 다른 참여자에 따라 달라지기 때문입니다. 압축 프로그램은 이미 '제대로 된' 사운드를 위해 이미 튜닝되어 정교하게 마스터링된 트랙을 다루는 드문 경우를 제외하고 대부분의 경우에 유용합니다.

이 기능을 구현하려면 일반적으로 대상 앞의 마지막 노드로 DynamicsEnhanceorNode를 오디오 그래프에 포함하기만 하면 됩니다.

// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);

동적 압축에 관한 자세한 내용은 이 Wikipedia 문서를 참고하세요.

요약하면 클리핑을 주의 깊게 듣고 마스터 게인 노드를 삽입하여 이를 방지합니다. 그런 다음 동적 압축기 노드를 사용하여 전체 믹스를 조입니다. 오디오 그래프는 다음과 같을 수 있습니다.

최종 결과

결론

지금까지 웹 오디오 API를 사용한 게임 오디오 개발에서 가장 중요한 측면에 대해 알아보았습니다. 이러한 기술을 사용하면 브라우저에서 바로 진정으로 매력적인 오디오 환경을 구축할 수 있습니다. 종료하기 전에 브라우저별 팁을 하나 알려 드리겠습니다. 페이지 가시성 API를 사용하여 탭이 백그라운드로 이동하는 경우 알림음을 일시중지하세요. 그렇지 않으면 사용자에게 불편을 줄 수 있습니다.

웹 오디오에 관한 자세한 내용은 입문용 시작하기 도움말을 참고하고, 궁금한 점이 있으면 웹 오디오 FAQ에서 관련 답변이 있는지 확인하세요. 마지막으로, 추가 질문이 있으면 web-audio 태그를 사용하여 Stack Overflow에서 질문하세요.

종료하기 전에 현재 실제 게임에서 Web Audio API를 효과적으로 사용하는 방법을 몇 가지 알려 드리겠습니다.