Screen.orientation - Web API

Screen.orientation
==================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

Summary
-------

`orientation` 속성은 화면의 현재 방향을 알려 준다.

**참고:** 이 함수는 설치된 웹앱 또는 full-screen mode 의 웹페이지에서 동작한다.

Syntax
------

```js
var orientation = window.screen.orientation;
```

Return value
------------

리턴값은 화면의 방향을 표현하는 문자열이다. portrait-primary, portrait-secondary, landscape-primary, landscape-secondary 가 될 수 있다. (각 값들에 대해 좀 더 많은 정보를 구하려면 `lockOrientation` 를 보라.).

Example
-------

```js
var orientation = screen.mozOrientation;

if (orientation === "landscape-primary") {
  console.log("That looks good.");
} else if (orientation === "landscape-secondary") {
  console.log("Mmmh... the screen is upside down!");
} else if (
  orientation === "portrait-secondary" ||
  orientation === "portrait-secondary"
) {
  console.log("Mmmh... you should rotate your device");
}
```

명세서
---

| Specification |
| --- |
| Screen Orientation # dom-screen-orientation |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `window.screen.orientation`
* `window.screen.unlockOrientation()`
* `window.screen.onorientationchange`
* Managing screen orientation (en-US)


ClipboardEvent.clipboardData - Web API

ClipboardEvent.clipboardData
============================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

**`ClipboardEvent.clipboardData`** 속성은 다음과 같은 용도로 사용할 수 있는 `DataTransfer` 객체입니다.

* `cut`, `copy` 이벤트 처리기 내에서, 어떤 데이터를 클립보드에 넣어야 하는지 지정하기. 보통 `setData(format, data)` (en-US)를 호출해서 수행합니다.
* `paste` 이벤트 처리기 내에서 데이터를 가져오기. 보통 `getData(format)`을 호출해서 수행합니다.

`cut`, `copy`, `paste` 이벤트 문서에서 자세한 정보를 확인하세요.

구문
--

```js
data = ClipboardEvent.clipboardData;
```

명세
--

| Specification |
| --- |
| Clipboard API and events # clipboardevent-clipboarddata |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* 복사 관련 이벤트: `cut`, `copy`, `paste`
* 이 속성이 속한 `ClipboardEvent` 인터페이스.


BatteryManager.dischargingTime (배터리관리기 방전시간) - Web API

BatteryManager.dischargingTime (배터리관리기 방전시간)
============================================

몇 초 단위로, 배터리가 완전히 방전될 때까지 남은 시간량을 가리킵니다.

**참고:** 반환되는 시간이 정확하더라도, 개인정보보호 정책 사유로 브라우저들이 더 큰 간격(대개 15분)으로 시간량을 반올림합니다.

Syntax
------

```js
var time = battery.dischargingTime;
```

반환되는 중에, `time` 은 `BatteryManager` object(객체)인 `battery` 가 완전히 방전되고 시스템이 중단되는 몇 초 뒤에 남게 되는 시간입니다. 배터리가 현재 방전되기보다 충전되거나 시스템이 남은 방전 시간을 보고할 수 없다면, 이 값은 무한 값입니다.

예제
--

### HTML 콘텐츠

```html
<div id="dischargingTime">(discharging time unknown)</div>
```

### 자바언어 콘텐츠

```js
navigator.getBattery().then(function (battery) {
  var time = battery.dischargingTime;

  document.querySelector("#dischargingTime").textContent =
    battery.dischargingTime;
});
```

명세서
---

| Specification |
| --- |
| Battery Status API # dom-batterymanager-dischargingtime |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보세요
------

* `BatteryManager`
* `Navigator.getBattery`


BatteryManager.charging - Web API

BatteryManager.charging
=======================

현재 배터리가 충전중인지를 나타내는 Boolean 값 입니다.

문법
--

```js
var charging = battery.charging;
```

`charging` 변수는 배터리가 충전 중인지의 여부를 가져옵니다. 충전중일경우 `true`. 이외에는 `false` 를 가리킵니다.

예제
--

### HTML Content

```html
<div id="charging">(charging state unknown)</div>
```

### JavaScript Content

```js
navigator.getBattery().then(function (battery) {
  var charging = battery.charging;

  document.querySelector("#charging").textContent = charging;
});
```

명세
--

| Specification |
| --- |
| Battery Status API # dom-batterymanager-charging |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* `BatteryManager`
* `Navigator.getBattery`


BatteryManager.chargingTime (배터리관리기 충전시간) - Web API

BatteryManager.chargingTime (배터리관리기 충전시간)
=========================================

몇 초 단위로, 배터리가 완전히 충전될 때까지 남은 시간량을 가리킵니다.

**참고:** 반환되는 시간이 정확하더라도, 개인정보보호 정책 사유로 브라우저들이 더 큰 간격(대개 15분)으로 시간량을 반올림합니다.

```js
var time = battery.chargingTime;
```

반환되는 중에, 건전지가 완전히 충전될 때까지 또는 이미 완전히 충전되어 있다면 0이 될 때까지 `time` 은 몇 초 뒤에 남게 되는 시간입니다. 이는 `BatteryManager` object (객체),입니다. 배터리의 전류가 방전되어 있다면, 이 값은 `Infinity` (en-US), 즉 무한 값이 됩니다.

예제
--

### HTML 콘텐츠

```html
<div id="chargingTime">(charging time unknown)</div>
```

### 자바언어 콘텐츠

```js
navigator.getBattery().then(function (battery) {
  var time = battery.chargingTime;

  document.querySelector("#chargingTime").textContent = battery.chargingTime;
});
```

명세서
---

| Specification |
| --- |
| Battery Status API # dom-batterymanager-chargingtime |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보세요
------

* `BatteryManager`
* `Navigator.getBattery`


Web Audio 사용을 분석하기 위한 도구들 - Web API

Web Audio 사용을 분석하기 위한 도구들
=========================

Web Audio API 코드를 작성하는 중에, 여러분은 여러분이 만든 노드의 그래프를 분석하거나 그게 아니라면 여러분의 작업을 디버그할 도구가 필요하다고 생각했을 지도 모릅니다. 이 글은 여러분을 도울 이용 가능한 도구들에 대해 이야기합니다.

Chrome
------

Chrome Web Store에서 찾을 수 있는 유용한 web audio 검사기.

Edge
----

*Microsoft Edge를 사용하는 개발자들을 위한 정보를 추가해 주세요.*

Firefox
-------

Firefox는 네이티브 Web Audio 에디터 (en-US)를 제공합니다.

Safari
------

*Safari에서 작업하는 개발자들을 위한 정보를 추가해 주세요.*

같이 보기
-----

* Web Audio API
* Web Audio API 사용하기
* 모든 브라우저에서 작동하는 Web Audio API 코드 작성하기 (en-US)


Web audio 공간화 기초 - Web API

Web audio 공간화 기초
================

Web Audio API의 사운드 프로세싱 (그리고 다른 것들) 의 광범위한 다양성이 마치 충분하지 않다는 것처럼, Web Audio API는 또한 리스너가 음원 주위에서 움직일 때 소리의 차이를 모방할 수 있게 하는 기능을 포함하고 있습니다. 그 예시로는 3D 게임에서 음원 주위에서 움직일 때의 패닝 기능을 들 수 있습니다. 이 기능에 대한 공식적인 용어는 **spatialization**(공간화)이고, 이 글은 어떻게 그런 시스템을 구현하는지에 대한 기초를 다룰 것입니다.

공간화 기초
------

Web Audio에서, 복잡한 3D 공간화는 `PannerNode` (en-US)를 사용하여 생성되는데, 쉽게 말하자면 이는 기본적으로 오디오가 3D 공간에 나타날 수 있게 하는 멋진 수학적 마법입니다. 위로 날아가는, 뒤에서 슬금슬금 다가오는, 앞을 가로질러 가는 소리들을 생각해 보세요. 그런 종류의 것들을 다룹니다.

이 노드는 WebXR과 게이밍에 정말로 유용할 뿐만 아니라, 3D 공간에서 현실적인 오디오 효과를 낼 수 있게 하는 유일한 방법입니다. three.js와 A-frame같은 라이브러리들은 소리를 다룰 때 이 노드의 가능성을 활용합니다. 또한 주목할 가치가 있는 것은 **꼭** 3D 공간 내에서 사운드를 움직일 필요가 없다는 것입니다. 2D 평면 또한 사용할 수 있으므로, 만약 2D 게임을 계획하고 있다면 이것은 여전히 필요한 노드일 것입니다.

**참고:** 간단한 좌우 스테레오 패닝 이펙트를 생성하는 경우를 다루기 위해 설계된 `StereoPannerNode` (en-US)가 있습니다. 이 노드는 사용하기가 더욱 단순하긴 하지만, 명백히 다재다능하지는 않습니다. 만약 단지 간단한 스테레오 패닝 이펙트가 필요하다면, StereoPannerNode 예제 (소스 코드는 여기서 보실 수 있습니다) 에서 충분한 정보를 얻을 수 있을 것입니다.

3D 라디오 데모
---------

3D 공간화를 시연하기 위해 기초 Web Audio API 사용하기 가이드에서 만든 라디오 데모가 수정되었습니다. 3D 공간화 작동 예제를 구경해 보세요 (그리고 소스 코드 또한 보실 수 있습니다).

![회전된 라디오와 이것을 좌우, 안팎으로 이동하고 회전시키기 위한 조종 버튼을 가진 단순한 UI](/ko/docs/Web/API/Web_Audio_API/Web_audio_spatialization_basics/web-audio-spatialization.png)

이 라디오는 (브라우저 뷰포트의 가장자리에 의해 정의되는) 방 안에 위치하고 있고, 이 예제에서, 라디오는 여러 버튼을 통해 움직여지거나 회전될 수 있습니다. 라디오가 움직일 때, 라디오가 생성하는 사운드는 방의 좌우를 향해 움직여질 때 패닝되거나, 사용자로부터 떨어지거나 회전되어 스피커가 사용자를 등졌을 때 조용해지는 등 상황에 맞춰 변화합니다. 이러한 기능은 공간화를 모방하기 위해 그러한 움직임에 관한 `PannerNode` 객체 인스턴스의 여러 속성들을 설정함으로써 이루어질 수 있습니다.

**참고:** 위 예제가 제공하는 경험은 헤드폰을 사용하거나 컴퓨터에 연결할 서라운드 시스템 같은 것을 가지고 있다면 훨씬 낫게 다가올 것입니다.

오디오 리스너 생성하기
------------

그럼 시작해 봅시다! (`AudioContext`가 상속받는 인터페이스인) `BaseAudioContext`는 `AudioListener` (en-US) 객체를 반환하는 `listener` (en-US) 속성을 가지고 있습니다. 이것은 현장의 리스너를 나타내는데, 보통 사용자입니다. 여러분은 사용자가 공간 내에서 어디에 있는지, 그리고 어떤 방향을 향하고 있는지를 정의할 수 있습니다. 사용자는 고정되어 있습니다. 그리고 나서 `pannerNode`가 리스너의 위치에 관해서 자신의 사운드 위치를 계산할 수 있습니다.

컨텍스트와 리스너를 생성하고 리스너의 위치를 설정하여 방 안을 바라보고 있는 사람을 모방해 봅시다.

```js
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
const listener = audioCtx.listener;

const posX = window.innerWidth / 2;
const posY = window.innerHeight / 2;
const posZ = 300;

listener.positionX.value = posX;
listener.positionY.value = posY;
listener.positionZ.value = posZ - 5;
```

`positionX`를 통해 리스너를 좌우로 움직이거나, `positionY`를 통해 위아래로 움직이거나, `positionZ`를 통해 방의 안팎으로 움직일 수 있습니다. 위 코드에서 리스너의 위치는 뷰포트의 중앙 그리고 라디오의 살짝 앞에 있는 것으로 설정되었습니다. 또한, 리스너가 향하고 있는 방향도 설정할 수 있습니다. 이 속성들의 기본 값이면 충분할 것입니다.

```js
listener.forwardX.value = 0;
listener.forwardY.value = 0;
listener.forwardZ.value = -1;
listener.upX.value = 0;
listener.upY.value = 1;
listener.upZ.value = 0;
```

forward 속성은 리스너 앞 방향의 3D 좌표 위치를 나타냅니다 (예: 리스너가 마주하고 있는 방향), 한편 up 속성들은 리스너 머리 위의 3D 좌표 위치를 나타냅니다. 방향은 이 두 속성에 의해 잘 설정될 수 있습니다.

패너 노드 생성하기
----------

`PannerNode` (en-US)를 생성해 봅시다. 이 노드와 관련된 많은 속성들이 있습니다. 각 속성들을 살펴봅시다.

첫번째로 `panningModel` (en-US)을 알아봅시다. 이 속성은 3D 공간에서 오디오를 위치시키기 위해 사용되는 알고리즘입니다. 설정될 수 있는 값은 다음과 같습니다.

`equalpower` — 기본값이며, 패닝이 계산되는 일반적인 방법

`HRTF` — HRTF는 'Head-related transfer function' 의 약자이고 사운드가 어디 있는지를 계산할 때 사람의 머리를 고려하는 것으로 보입니다.

꽤 똑똑한 것이군요. `HRTF` 모델을 사용합시다!

```js
const pannerModel = "HRTF";
```

`coneInnerAngle` (en-US)과 `coneOuterAngle` (en-US) 속성은 어디서 볼륨이 발생되는지를 명시합니다. 둘 다 기본 값은 360도입니다. 이 예제의 라디오는 기본 값보다 작은 원뿔을 가지도록 정의될 것입니다. 내부 원뿔은 gain (볼륨) 이 항상 최대로 발생되는 곳이고 외부 원뿔은 gain이 줄어들기 시작하는 곳입니다. 이 gain은 `coneOuterGain` (en-US)의 값에 의해 감소됩니다. 이 매개변수들의 값을 저장할 상수를 생성합시다.

```js
const innerCone = 60;
const outerCone = 90;
const outerGain = 0.3;
```

다음 매개변수는 `distanceModel` (en-US)입니다. 이 매개변수는 오직 `linear`, `inverse`, 또는 `exponential`로만 설정될 수 있습니다. 이 값들은 각기 다른 알고리즘인데, 리스너로부터 멀어질 때 오디오 소스의 볼륨을 감소시키기 위해 사용됩니다. `linear`를 사용합시다. 간단하니까요.

```js
const distanceModel = "linear";
```

우리는 리스너와 음원 사이의 최대 거리 (`maxDistance` (en-US)) 를 설정할 수 있습니다. 만약 음원이 이 지점으로부터 더 멀리 이동하면 볼륨은 더 이상 감소되지 않을 것입니다. 최대 거리 설정은 유용할 수 있습니다. 거리감을 흉내내고자 하나 볼륨이 꺼져 버린다면 곤란할 수 있겠죠. 최대 거리는 기본값으로 10,000 (단위없는 상대 값) 입니다. 아래와 같이 설정할 수 있습니다.

```js
const maxDistance = 10000;
```

또한 참조 거리 (`refDistance` (en-US))가 있는데, 이는 거리 모델에 의해 사용됩니다. 이 또한 기본값 `1`로 설정할 수 있습니다.

```js
const refDistance = 1;
```

그리고 roll-off 인자 (`rolloffFactor` (en-US)) 가 있습니다. 이것은 패너가 리스너로부터 멀어질 때 얼마나 빨리 볼륨이 감소하는지를 정의합니다. 기본값은 1입니다. 이동을 과장시키는 효과를 주기 위해 이 값을 조금 더 크게 설정해 봅시다.

```js
const rollOff = 10;
```

이제 라디오의 위치와 방향 설정을 시작해 봅시다. 이는 리스너를 설정한 과정과 많이 비슷합니다. 인터페이스의 버튼들이 사용되었을 때 변경될 매개변수들은 아래와 같습니다.

```js
const positionX = posX;
const positionY = posY;
const positionZ = posZ;

const orientationX = 0.0;
const orientationY = 0.0;
const orientationZ = -1.0;
```

z 방향의 음수 값에 주목하세요. 음수 값은 라디오가 우리를 바라보게 만듭니다. 양수 값은 음원이 우리를 등지게 만들 것입니다.

생성자를 사용해 패너 노드를 생성하고 위에서 설정한 모든 매개변수들을 전달합시다.

```js
const panner = new PannerNode(audioCtx, {
  panningModel: pannerModel,
  distanceModel: distanceModel,
  positionX: positionX,
  positionY: positionY,
  positionZ: positionZ,
  orientationX: orientationX,
  orientationY: orientationY,
  orientationZ: orientationZ,
  refDistance: refDistance,
  maxDistance: maxDistance,
  rolloffFactor: rollOff,
  coneInnerAngle: innerCone,
  coneOuterAngle: outerCone,
  coneOuterGain: outerGain,
});
```

라디오 이동시키기
---------

이제 라디오를 '방' 주위에서 움직일 것입니다. 이를 위해 준비된 조종 버튼들이 있습니다. 라디오를 좌우로, 위아래로, 앞뒤로 움직일 수 있고, 회전시킬 수도 있습니다. 소리의 방향은 앞에 있는 라디오 스피커에서부터 오므로, 라디오를 회전시키면 소리의 방향을 변경할 수 있습니다. 예: 라디오가 180도 회전되어 우리를 등지고 있을 때 소리의 방향이 뒤를 향하도록 만들기.

인터페이스에 대한 것들을 설정해 봅시다. 첫째로, 우리가 움직이기를 원하는 요소들에 대한 참조를 얻습니다. 그리고 나서 실제로 움직임을 수행하기 위해 CSS transforms (en-US)을 설정했을 때 변경될 값들에 대한 참조를 저장합니다. 마지막으로, 경계를 설정해 라디오가 어떠한 방향으로든 너무 멀리 가지 않도록 할 것입니다.

```js
const moveControls = document
  .querySelector("#move-controls")
  .querySelectorAll("button");
const boombox = document.querySelector(".boombox-body");

// css transforms를 위한 값들
let transform = {
  xAxis: 0,
  yAxis: 0,
  zAxis: 0.8,
  rotateX: 0,
  rotateY: 0,
};

// 경계 설정
const topBound = -posY;
const bottomBound = posY;
const rightBound = posX;
const leftBound = -posX;
const innerBound = 0.1;
const outerBound = 1.5;
```

매개변수로 우리가 움직이기를 원하는 방향을 취하고, CSS transform을 수정할 뿐만 아니라 적절하게 소리를 변경하기 위해 패너 노드 속성들의 위치와 방향 값을 갱신하는 함수를 만들어 봅시다.

상하좌우 값들은 아주 간단하므로, 우선 이 값들을 살펴봅시다. 우리는 라디오를 이 축들을 따라 이동시키고 적절한 위치로 갱신할 것입니다.

```js
function moveBoombox(direction) {
  switch (direction) {
    case "left":
      if (transform.xAxis > leftBound) {
        transform.xAxis -= 5;
        panner.positionX.value -= 0.1;
      }
      break;
    case "up":
      if (transform.yAxis > topBound) {
        transform.yAxis -= 5;
        panner.positionY.value -= 0.3;
      }
      break;
    case "right":
      if (transform.xAxis < rightBound) {
        transform.xAxis += 5;
        panner.positionX.value += 0.1;
      }
      break;
    case "down":
      if (transform.yAxis < bottomBound) {
        transform.yAxis += 5;
        panner.positionY.value += 0.3;
      }
      break;
  }
}
```

안팎으로 이동하는 것에 대한 설정도 위와 비슷합니다.

```js
case 'back':
    if (transform.zAxis > innerBound) {
        transform.zAxis -= 0.01;
        panner.positionZ.value += 40;
    }
break;
case 'forward':
    if (transform.zAxis < outerBound) {
        transform.zAxis += 0.01;
        panner.positionZ.value -= 40;
    }
break;
```

그러나 회전 값들은 조금 더 복잡한데, 왜냐하면 소리를 **주위로** 이동시켜야 할 필요가 있기 때문입니다 (예: 만약 객체를 x축 주위로 회전시킨다면, y와 z좌표를 갱신해야 합니다). 두 축의 값을 갱신해야 할 뿐만 아니라, 이를 위해 수학 계산을 좀 할 필요도 있습니다. 회전은 원이고 이 원을 그리기 위해 `Math.sin`과 `Math.cos` (en-US)이 필요합니다.

라디오를 회전시켰을 때 새로운 좌표를 계산할 수 있도록 추후에 `Math.sin`과 `Math.cos`에서의 사용을 위해 라디안 범위 값으로 전환할 회전 비율을 설정해 봅시다.

```js
// 회전 상수 설정
const rotationRate = 60; // 더 큰 수일수록 소리 회전은 더 느려짐

const q = Math.PI / rotationRate; // 회전 증가 (라디안)
```

또한 이를 사용하여 회전된 각도를 계산할 수 있는데, 이는 CSS transform에 사용될 것입니다 (CSS transform을 위해 x축과 y축 모두가 필요하다는 것에 주목하세요).

```js
// css를 위해 각도 얻기
const degreesX = (q \* 180) / Math.PI;
const degreesY = (q \* 180) / Math.PI;
```

예제로써 좌회전을 살펴봅시다. y축을 주위로 이동하기 위해, 패너 좌표의 x방향과 z방향을 변경할 필요가 있습니다.

```js
case 'rotate-left':
  transform.rotateY -= degreesY;

  // '좌' 는 음수 각 증가를 가지는 y축에 대한 회전입니다
  z = panner.orientationZ.value\*Math.cos(q) - panner.orientationX.value\*Math.sin(q);
  x = panner.orientationZ.value\*Math.sin(q) + panner.orientationX.value\*Math.cos(q);
  y = panner.orientationY.value;

  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
```

이것은 **조금 혼란**스럽지만, 지금 하고 있는 것은 sin과 cos을 사용해 라디오의 회전에 필요한 좌표인 원운동을 계산하는 것입니다.

이 작업은 모든 축에 대해 이루어질 수 있습니다. 필요한 건 단지 갱신할 옳은 방향과 양수 또는 음수 증가 중 어느 쪽을 원하는지를 선택하는 것입니다.

```js
case 'rotate-right':
  transform.rotateY += degreesY;
  // '우' 는 양수 각 증가를 가지는 y축에 대한 회전입니다
  z = panner.orientationZ.value\*Math.cos(-q) - panner.orientationX.value\*Math.sin(-q);
  x = panner.orientationZ.value\*Math.sin(-q) + panner.orientationX.value\*Math.cos(-q);
  y = panner.orientationY.value;
  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
case 'rotate-up':
  transform.rotateX += degreesX;
  // '상' 은 음수 각 증가를 가지는 x축에 대한 회전입니다
  z = panner.orientationZ.value\*Math.cos(-q) - panner.orientationY.value\*Math.sin(-q);
  y = panner.orientationZ.value\*Math.sin(-q) + panner.orientationY.value\*Math.cos(-q);
  x = panner.orientationX.value;
  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
case 'rotate-down':
  transform.rotateX -= degreesX;
  // '하' 는 양수 각 증가를 가지는 x축에 대한 회전입니다
  z = panner.orientationZ.value\*Math.cos(q) - panner.orientationY.value\*Math.sin(q);
  y = panner.orientationZ.value\*Math.sin(q) + panner.orientationY.value\*Math.cos(q);
  x = panner.orientationX.value;
  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
```

마지막 하나! CSS를 갱신하고 마우스 이벤트의 마지막 움직임에 대한 참조를 유지할 필요가 있습니다. 아래가 완성된 `moveBoombox` 함수입니다.

```js
function moveBoombox(direction, prevMove) {
  switch (direction) {
    case "left":
      if (transform.xAxis > leftBound) {
        transform.xAxis -= 5;
        panner.positionX.value -= 0.1;
      }
      break;
    case "up":
      if (transform.yAxis > topBound) {
        transform.yAxis -= 5;
        panner.positionY.value -= 0.3;
      }
      break;
    case "right":
      if (transform.xAxis < rightBound) {
        transform.xAxis += 5;
        panner.positionX.value += 0.1;
      }
      break;
    case "down":
      if (transform.yAxis < bottomBound) {
        transform.yAxis += 5;
        panner.positionY.value += 0.3;
      }
      break;
    case "back":
      if (transform.zAxis > innerBound) {
        transform.zAxis -= 0.01;
        panner.positionZ.value += 40;
      }
      break;
    case "forward":
      if (transform.zAxis < outerBound) {
        transform.zAxis += 0.01;
        panner.positionZ.value -= 40;
      }
      break;
    case "rotate-left":
      transform.rotateY -= degreesY;

      // '좌' 는 음수 각 증가를 가지는 y축에 대한 회전입니다
      z =
        panner.orientationZ.value \* Math.cos(q) -
        panner.orientationX.value \* Math.sin(q);
      x =
        panner.orientationZ.value \* Math.sin(q) +
        panner.orientationX.value \* Math.cos(q);
      y = panner.orientationY.value;

      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
    case "rotate-right":
      transform.rotateY += degreesY;
      // '우' 는 양수 각 증가를 가지는 y축에 대한 회전입니다
      z =
        panner.orientationZ.value \* Math.cos(-q) -
        panner.orientationX.value \* Math.sin(-q);
      x =
        panner.orientationZ.value \* Math.sin(-q) +
        panner.orientationX.value \* Math.cos(-q);
      y = panner.orientationY.value;
      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
    case "rotate-up":
      transform.rotateX += degreesX;
      // '상' 은 음수 각 증가를 가지는 x축에 대한 회전입니다
      z =
        panner.orientationZ.value \* Math.cos(-q) -
        panner.orientationY.value \* Math.sin(-q);
      y =
        panner.orientationZ.value \* Math.sin(-q) +
        panner.orientationY.value \* Math.cos(-q);
      x = panner.orientationX.value;
      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
    case "rotate-down":
      transform.rotateX -= degreesX;
      // '하' 는 양수 각 증가를 가지는 x축에 대한 회전입니다
      z =
        panner.orientationZ.value \* Math.cos(q) -
        panner.orientationY.value \* Math.sin(q);
      y =
        panner.orientationZ.value \* Math.sin(q) +
        panner.orientationY.value \* Math.cos(q);
      x = panner.orientationX.value;
      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
  }

  boombox.style.transform =
    "translateX(" +
    transform.xAxis +
    "px) translateY(" +
    transform.yAxis +
    "px) scale(" +
    transform.zAxis +
    ") rotateY(" +
    transform.rotateY +
    "deg) rotateX(" +
    transform.rotateX +
    "deg)";

  const move = prevMove || {};
  move.frameId = requestAnimationFrame(() => moveBoombox(direction, move));
  return move;
}
```

조종 버튼 연결하기
----------

조종 버튼을 연결하는 것은 비교적 단순합니다. 이제 우리는 조종 장치에 대한 마우스 이벤트를 수신하고 이 함수를 실행시킬 수 있을 뿐만 아니라, 마우스가 떼졌을 때 멈출 수도 있습니다.

```js
// 각각의 조종 버튼에 대해, 라디오를 움직이고 위치 값을 변경합니다
moveControls.forEach(function (el) {
  let moving;
  el.addEventListener(
    "mousedown",
    function () {
      let direction = this.dataset.control;
      if (moving && moving.frameId) {
        window.cancelAnimationFrame(moving.frameId);
      }
      moving = moveBoombox(direction);
    },
    false,
  );

  window.addEventListener(
    "mouseup",
    function () {
      if (moving && moving.frameId) {
        window.cancelAnimationFrame(moving.frameId);
      }
    },
    false,
  );
});
```

그래프 연결하기
--------

HTML에는 패너 노드에 의해 영향을 받을 audio 요소가 있습니다.

```html
<audio src="myCoolTrack.mp3"></audio>
```

이 요소로부터 소스를 취해 `AudioContext.createMediaElementSource` (en-US)를 사용해 Web Audio API에 연결합시다.

```js
// audio 요소를 얻습니다
const audioElement = document.querySelector("audio");

// audio 요소를 오디오 컨텍스트에 전달합니다
const track = audioContext.createMediaElementSource(audioElement);
```

다음으로 오디오 그래프를 연결해야 합니다. 목적지 (이 경우 스피커) 에 연결되는 수정 노드 (패너) 에 입력 (이 곡) 을 연결합니다.

```js
track.connect(panner).connect(audioCtx.destination);
```

클릭되었을 때 현재 상태에 따라 오디오를 재생하거나 멈추는, 재생 버튼을 만듭시다.

```html
<button data-playing="false" role="switch">Play/Pause</button>
```

```js
// 재생 버튼을 선택합니다
const playButton = document.querySelector("button");

playButton.addEventListener(
  "click",
  function () {
    // 컨텍스트가 연기(suspended) 상태에 있는지 검사합니다 (자동 재생 정책)
    if (audioContext.state === "suspended") {
      audioContext.resume();
    }

    // 상태에 따라 곡을 재생하거나 정지합니다
    if (this.dataset.playing === "false") {
      audioElement.play();
      this.dataset.playing = "true";
    } else if (this.dataset.playing === "true") {
      audioElement.pause();
      this.dataset.playing = "false";
    }
  },
  false,
);
```

오디오의 재생/제어와 오디오 그래프에 대한 더욱 자세한 정보는 Web Audio API 사용하기에서 찾아볼 수 있습니다.

요약
--

이 글이 여러분께 어떻게 Web Audio 공간화가 작동하는지, 그리고 `PannerNode` (en-US)의 각 속성들이 무슨 일을 하는지 (속성들 중 상당수가 있습니다) 에 대한 이해를 주었기를 바랍니다. 값들은 때때로 조작하기 어려울 수 있고 사용하는 경우에 따라 그것들을 바르게 하는 데 시간이 필요할 수 있습니다.

**참고:** 각기 다른 브라우저 간에 오디오 공간화가 들리는 방식에 약간의 차이가 있습니다. 패너 노드는 아주 복잡한 수학 계산을 필요로 합니다. 여기 몇 개의 테스트가 있으니 여러분은 각기 다른 플랫폼들 사이에서 이 노드의 내부 작동 상태를 확인할 수 있습니다.

다시 말하지만, 여러분은 다음의 링크에서 최종 결과물과, 소스 코드를 확인할 수 있습니다. Codepen 데모 또한 있습니다.

만약 여러분이 3D 게임 그리고/또는 WebXR으로 작업한다면 그러한 기능을 만들기 위해 처음부터 직접 모든 것을 하려고 시도하기보다는 3D 라이브러리를 활용하는 것이 좋은 생각입니다. 우리는 이 글에서 여러분에게 어떻게 이것이 작동하는지에 대한 아이디어를 주기 위해 직접 만들었지만, 여러분에 앞서 남들이 해 놓은 작업을 이용함으로써 많은 시간을 절약할 수 있을 것입니다.


Web Audio API 잘 사용하기 - Web API

Web Audio API 잘 사용하기
====================

창의적인 코드를 작성할 때 엄격하게 옳거나 틀린 길은 없습니다. 여러분이 보안, 성능, 접근성을 고려하는 한, 여러분은 여러분만의 스타일에 적응할 수 있습니다. 이 문서에서, 우리는 몇 가지의 *좋은 습관들* — Web Audio API를 가지고 작업하는 데 있어서의 가이드라인, 팁, 그리고 트릭들 — 을 공유하고자 합니다.

사운드/파일 로딩하기
-----------

Web Audio API를 가지고 사운드를 로딩하는 4가지의 주요한 방법이 있고 어떤 것을 사용해야 하는지에 관해서 조금 헷갈릴 수 있습니다.

파일을 가지고 작업할 때, 파일을 `HTMLMediaElement` (예: `<audio>` (en-US) 또는 `<video>` 요소) 로 가져오거나, 파일을 가져와서 버퍼로 디코드하는 방법이 있습니다. 양 쪽 모두 작동하는 타당한 방법이지만, 전체 길이의 트랙을 가지고 작업할 때는 전자를 이용하는 방법이 더욱 일반적이고, 샘플같은 짧은 트랙을 가지고 작업할 때는 후자가 일반적입니다.

미디어 요소들은 박스로부터 스트리밍 지원을 받습니다. 오디오는 브라우저가 재생이 끝나기 이전에 파일의 나머지 부분을 로딩할 수 있다고 결정했을 때 재생을 시작합니다. 여러분은 Web Audio API 사용하기 자습서에서 Web Audio API를 가지고 이것을 어떻게 사용하는지에 대한 예제를 보실 수 있습니다.

그러나, 만약 버퍼 노드를 사용한다면 여러분은 더 많은 제어를 할 수 있습니다. 여러분은 파일을 요청하고 이것이 로딩되기를 기다려야 하지만 (이 글의 이 섹션은 이것을 하는 좋은 방법을 보여줍니다), 그 다음에 여러분은 데이터에 직접 접근할 수 있는데, 이는 더욱 정밀한 조작이 가능함을 의미합니다.

만약 여러분이 유저의 카메라나 마이크로부터의 오디오로 작업하는 방법을 찾고 있다면 여러분은 Media Stream API와 `MediaStreamAudioSourceNode` (en-US) 인터페이스를 통해 그것에 접근할 수 있습니다. 이것은 WebRTC와 녹음이나 오디오 분석을 원하는 상황에 알맞습니다.

마지막 방법은 여러분만의 사운드를 생성하는 것인데, 이는 `OscillatorNode` (en-US)나 버퍼를 만들고 데이터를 채워넣는 방법으로 이루어질 수 있습니다. 오실레이터와 버퍼로 사운드를 생성하기 위해 이 자습서에서 여러분만의 악기를 만드는 방법에 대해서 알아보세요.

크로스 브라우저 & 레거시 지원
-----------------

Web Audio API 명세는 끊임없이 진화하고 있으며 웹에 있는 대부분의 것들처럼, 브라우저들에서 일관되게 작동하는 데 있어 몇 가지 문제가 있습니다. 여기서 우리는 크로스 브라우저 문제들을 해결하기 위한 옵션들을 살펴보겠습니다.

`standardised-audio-context`(표준화된 오디오 컨텍스트) npm 패키지가 있는데, 이는 지속적으로 브라우저 간의 API 기능을 만들고, 문제들이 발생되면 해결합니다. 이것은 계속 개발되고 있으며 현재 명세에 뒤지지 않기 위해 노력하고 있습니다.

또한 라이브러리를 사용하는 옵션도 있는데, 여러분이 사용하는 경우에 의존하는 몇 가지가 있습니다. 좋은 올라운더로는, howler.js가 좋은 선택입니다. 이것은 크로스 브라우저를 지원하고, 유용한 기능들을 제공합니다. 비록 이것이 Web Audio API에 딸린 모든 필터들과 다른 이펙트들을 이용하지는 않지만, 여러분은 하기를 원하는 대부분의 것들을 할 수 있습니다.

만약 여러분이 사운드 생성 또는 더욱 악기 기반의 옵션을 찾고 있다면, tone.js가 훌륭한 라이브러리입니다. 이것은 고급 스케쥴링 기능, 신시사이저, 이펙트, 그리고 Web Audio API 위에서 개발된 직관적인 음악적 추상 기능들을 제공합니다.

BBC's Research & Development department로부터의 R-audio는, "Web Audio에의 더욱 직관적이고, 선언적인 인터페이스"를 제공하기를 목표로 하는 React 컴포넌트 라이브러리입니다. 만약 여러분이 JSX를 작성하는 데 익숙하다면 이것은 살펴볼만한 가치가 있을 것입니다.

자동 재생 정책
--------

브라우저들은 자동 재생 정책 기능을 구현하기 시작했는데, 이는 일반적으로 다음과 같이 요약될 수 있습니다.

>
> "Create or resume context from inside a user gesture".
>
>
> "유저 제스처 내부로부터 컨텍스트를 생성하거나 재개하십시오".
>
>
>

하지만 실제로 이것은 무엇을 의미하는 것일까요? 유저 제스처는 유저가 발생시킨 이벤트를 의미하는 것으로 이해될 수 있는데, 이는 보통 `click` 이벤트입니다. 브라우저 벤더들은 Web Audio 컨텍스트는 자동적으로 오디오를 재생하도록 허용되어져서는 안 된다고 결정했습니다; 대신 그것들은 유저에 의해 시작되어야만 합니다. 이것은 왜냐하면 자동 재생되는 오디오는 정말 짜증나고 거슬릴 수 있기 때문입니다. 하지만 우리는 어떻게 이것을 다룰까요?

여러분이 오디오 컨텍스트를 생성할 때 (offline 또는 online 둘 다) 이것은 `state`(상태)와 함께 생성되는데, 이것은 `suspended`, `running`, 또는 `closed`일 수 있습니다.

`AudioContext`를 가지고 작업할 때, 만약 여러분이 오디오 컨텍스트를 `click` 이벤트 내부에서 생성했다면 상태는 자동적으로 `running`으로 설정될 것입니다. 여기 `click` 이벤트 내부로부터 컨텍스트를 생성하는 간단한 예제가 있습니다:

```js
const button = document.querySelector("button");
button.addEventListener(
  "click",
  function () {
    const audioCtx = new AudioContext();
  },
  false,
);
```

그러나 만약, 여러분이 유저 제스처 바깥에서 컨텍스트를 생성했다면, 이것의 상태는 `suspended`로 설정될 것이고 유저 상호 작용 이후에 시작될 필요가 있을 것입니다. 우리는 같은 클릭 이벤트 예제를 여기서 사용하고, 컨텍스트의 상태를 시험하고, 만약 이것이 연기되었다면(suspended), `resume()` (en-US) 메서드를 사용하여 시작합니다.

```js
const audioCtx = new AudioContext();
const button = document.querySelector("button");

button.addEventListener(
  "click",
  function () {
    // 컨텍스트가 연기된 상태에 있는지 검사합니다 (자동 재생 정책)
    if (audioCtx.state === "suspended") {
      audioCtx.resume();
    }
  },
  false,
);
```

여러분은 대신 `OfflineAudioContext`을 가지고 작업할 수 있는데, 이 경우 여러분은 연기된 오디오 컨텍스트를 `startRendering()` (en-US) 메서드로 재개할 수 있습니다.

유저 컨트롤
------

만약 여러분의 웹사이트나 애플리케이션이 사운드롤 포함한다면, 여러분은 유저가 그것을 제어할 수 있게 허용해야만 합니다. 다시 말하지만 그렇지 않는다면, 이것은 짜증날 것입니다. 이것은 재생/정지 그리고 볼륨/음소거 제어로 달성될 수 있습니다. Web Audio API 사용하기 자습서는 어떻게 이것을 하는지에 대해 다룹니다.

만약 여러분이 오디오를 켜고 끄는 버튼을 가지고 있다면, ARIA `role="switch"` (en-US) 특성을 그것에 사용하는 것은 보조 기술에 그 버튼의 정확한 목적이 무엇인지에 대한 신호를 보내고, 그럼으로써 앱을 더욱 접근 가능하게 만들기 위한 좋은 옵션입니다. 여기 어떻게 이것을 사용하는지에 대한 데모가 있습니다.

여러분은 Web Audio API 내에서 많은 변화하는 값들을 가지고 작업하고 유저에게 이것들에 대한 제어를 제공하기를 원할 것이므로, `range input` (en-US)은 종종 사용할 제어의 좋은 선택입니다. 이것은 여러분이 최소와 최대값 뿐만 아니라 `step` (en-US) 특성으로 증가량 또한 설정할 수 있으므로 좋은 옵션입니다.

AudioParam 값 설정하기
-----------------

`AudioParam` 인터페이스 타입의 그 자신의 객체인, `AudioNode` 값을 조작하는 두 가지 방법이 있습니다. 첫째는 속성을 통해 직접 값을 설정하는 것입니다. 예를 들자면 만약 우리가 `GainNode` (en-US)의 `gain` 값을 변경하기를 원한다면 우리는 이와 같이 할 것입니다:

```js
gainNode.gain.value = 0.5;
```

이것은 볼륨을 절반으로 설정할 것입니다. 그러나, 만약 여러분이 이 값들을 설정하기 위해 `AudioParam`에 정의된 메서드들 중 어떤 것이라도 사용한다면, 그것들은 상기의 속성 설정에 대해 우선권을 얻을 것입니다. 예를 들어 만약, 여러분이 `gain` 값을 2초 동안 1까지 상승시키기를 원한다면, 여러분은 다음과 같이 할 수 있습니다:

```js
gainNode.gain.setValueAtTime(1, audioCtx.currentTime + 2);
```

이것은, 이전 예제가 코드의 뒤에 올 지라도, 이전 예제를 (당연히 그래야 하듯) 덮어쓸 것입니다.

이것을 명심하세요. 만약 여러분의 웹사이트나 애플리케이션이 타이밍과 스케쥴링을 요구한다면, 값을 설정하기 위해 `AudioParam` 메서드를 사용하는 것이 최선입니다. 만약 여러분이 이것이 필요없다고 확신하신다면, `value` 속성으로 설정하는 것도 괜찮습니다.


예제와 튜토리얼: 간단한 신시사이저 키보드 - Web API

예제와 튜토리얼: 간단한 신시사이저 키보드
=======================

이 문서는 마우스를 사용해 플레이할 수 있는 비디오 키보드의 데모와 코드를 보여줍니다. 이 키보드는 표준 파형들과 사용자 정의 파형 중에서 선택할 수 있는 기능을 제공하고, 키보드 아래에 있는 볼륨 슬라이더를 사용하여 메인 gain을 제어할 수 있습니다. 이 예제는 다음의 Web API 인터페이스를 사용합니다: `AudioContext`, `OscillatorNode` (en-US), `PeriodicWave`, 그리고 `GainNode` (en-US).

`OscillatorNode` (en-US)가 `AudioScheduledSourceNode`에 기반하기 때문에, 이것은 또한 얼마간 그것에 대한 예제이기도 합니다.

비디오 키보드
-------

### HTML

이 가상 키보드의 디스플레이에는 세 가지 주요한 컴포넌트가 있습니다. 첫번째는 뮤지컬 키보드 그 자체입니다. 우리는 이것을 중첩된 `<div>` (en-US) 요소의 쌍으로 그려 만약 모든 건반이 화면에 맞지 않으면 그것들이 줄바꿈되는 일 없이 키보드를 가로로 스크롤할 수 있게 되도록 만들 것입니다.

#### 키보드

첫째로, 키보드를 넣을 공간을 만듭니다. 우리는 프로그래밍적으로 키보드를 구성할 것인데, 왜냐하면 그렇게 하는 것은 우리에게 해당하는 음에 대한 적절한 데이터를 결정하면서 각각의 건반을 설정하는 유연성을 주기 때문입니다. 우리의 경우, 우리는 표로부터 각 음의 주파수를 얻지만, 이것은 또한 알고리즘적으로도 계산될 수 있습니다.

```html
<div class="container">
  <div class="keyboard"></div>
</div>
```

`"container"`라는 이름의 `<div>` (en-US)는 만약 이것이 이용 가능한 공간에 대해 너무 넓으면 가로로 스크롤될 수 있는 박스입니다. 건반들 자체는 `"keyboard"` 클래스의 블록 안으로 삽입될 것입니다.

#### 설정 바

키보드 아래에, 우리는 레이어를 설정하기 위한 조종 장치를 놓을 것입니다. 우선은, 우리는 두 조종 장치를 가지고 있습니다: 하나는 메인 볼륨을 설정하기 위한 것이고 나머지 하나는 노트를 생성할 때 어떤 주기적인 파형을 사용할 지 고르기 위한 것입니다.

##### 볼륨 컨트롤

첫째로 우리는 필요한 대로 스타일될 수 있도록, 설정 바를 포함하는 `<div>`를 생성합니다. 그리고 나서 바의 좌측에 나타날 박스를 생성하고 라벨과 `"range"` 유형의 `<input>` (en-US) 요소를 배치합니다. range 요소는 보통 슬라이더로 표현됩니다; 각 위치마다 0.01만큼 움직이며 0.0과 1.0 사이의 모든 값을 허용하게 설정합니다.

```html
<div class="settingsBar">
  <div class="left">
    <span>Volume: </span>
    <input
 type="range"
 min="0.0"
 max="1.0"
 step="0.01"
 value="0.5"
 list="volumes"
 name="volume" />
    <datalist id="volumes">
      <option value="0.0" label="Mute"></option>
      <option value="1.0" label="100%"></option>
    </datalist>
  </div>
</div>
```

우리는 기본값을 0.5로 명시하고, ID가 맞는 옵션 목록을 찾기 위해 `name` 특성을 사용하여 range에 연결된 `<datalist>` 요소를 제공합니다; 이 경우, 데이터셋은 `"volume"`이라는 이름입니다. 이는 우리로 하여금 브라우저가 옵션적으로 어떤 방식으로 디스플레이하기를 선택할지도 모르는 특별한 문자열과 일반적인 값의 집합을 제공하게 합니다; 우리는 값 0.0 ("무음")과 1.0 ("100%")에 대해 이름을 제공합니다.

##### 파형 선택기

세팅 바의 우측에, 우리는 라벨과 이용 가능한 파형에 부합하는 옵션을 가지고 있는 `"waveform"`라는 이름의 `<select>` (en-US) 요소를 배치합니다.

```html
  <div class="right">
    <span>Current waveform: </span>
    <select name="waveform">
      <option value="sine">Sine</option>
      <option value="square" selected>Square</option>
      <option value="sawtooth">Sawtooth</option>
      <option value="triangle">Triangle</option>
      <option value="custom">Custom</option>
    </select>
  </div>
</div>
```

```
.container {
  overflow-x: scroll;
  overflow-y: hidden;
  width: 660px;
  height: 110px;
  white-space: nowrap;
  margin: 10px;
}

.keyboard {
  width: auto;
  padding: 0;
  margin: 0;
}

.key {
  cursor: pointer;
  font:
    16px "Open Sans",
    "Lucida Grande",
    "Arial",
    sans-serif;
  border: 1px solid black;
  border-radius: 5px;
  width: 20px;
  height: 80px;
  text-align: center;
  box-shadow: 2px 2px darkgray;
  display: inline-block;
  position: relative;
  margin-right: 3px;
  user-select: none;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}

.key div {
  position: absolute;
  bottom: 0;
  text-align: center;
  width: 100%;
  pointer-events: none;
}

.key div sub {
  font-size: 10px;
  pointer-events: none;
}

.key:hover {
  background-color: #eef;
}

.key:active {
  background-color: #000;
  color: #fff;
}

.octave {
  display: inline-block;
  padding: 0 6px 0 0;
}

.settingsBar {
  padding-top: 8px;
  font:
    14px "Open Sans",
    "Lucida Grande",
    "Arial",
    sans-serif;
  position: relative;
  vertical-align: middle;
  width: 100%;
  height: 30px;
}

.left {
  width: 50%;
  position: absolute;
  left: 0;
  display: table-cell;
  vertical-align: middle;
}

.left span,
.left input {
  vertical-align: middle;
}

.right {
  width: 50%;
  position: absolute;
  right: 0;
  display: table-cell;
  vertical-align: middle;
}

.right span {
  vertical-align: middle;
}

.right input {
  vertical-align: baseline;
}
```

### JavaScript

JavaScript 코드는 몇 개의 변수를 초기화함으로써 시작합니다.

```js
let audioContext = new (window.AudioContext || window.webkitAudioContext)();
let oscList = [];
let mainGainNode = null;
```

1. `audioContext`는 전역 `AudioContext` 객체를 (또는 필요하다면 `webkitAudioContext`를) 참조하기 위해 설정되었습니다.
2. `oscList`는 현재 재생되고 있는 모든 oscillator를 포함할 준비가 되기 위해 설정되었습니다. 이것은 빈 상태로 시작하는데, 왜냐하면 아직 어떤 것도 재생되고 있지 않기 때문입니다.
3. `mainGainNode`은 null로 설정되었습니다; 설정 과정 중에, 이것은 재생되는 모든 oscillator가 연결되고 슬라이더를 사용해 전체 볼륨이 제어되도록 하는 `GainNode` (en-US)를 포함하도록 설정될 것입니다.

```js
let keyboard = document.querySelector(".keyboard");
let wavePicker = document.querySelector("select[name='waveform']");
let volumeControl = document.querySelector("input[name='volume']");
```

접근이 필요한 요소들에의 참조는 위와 같이 얻어집니다:

* `keyboard`는 건반이 배치될 컨테이너입니다.
* `wavePicker`는 음에 대해 사용할 파형을 선택하는 데 사용되는 `<select>` (en-US) 요소입니다.
* `volumeControl`는 메인 오디오 볼륨을 제어하기 위해 사용되는 (`"range"` 유형의) `<input>` (en-US) 요소입니다.

```js
let noteFreq = null;
let customWaveform = null;
let sineTerms = null;
let cosineTerms = null;
```

마지막으로, 파형을 생성할 때 사용될 전역 변수들이 생성됩니다:

* `noteFreq`는 배열들의 배열입니다; 각 배열은 하나의 옥타브를 나타내는데, 그 옥타브에 있는 각 음에 대한 항목을 포함합니다. 각각에 대한 값은 음의 음색을 나타내는 헤르츠로 표현되는 주파수입니다.
* `customWaveform`는 사용자가 "Custom"을 파형 선택기에서 선택했을 때 사용할 파형을 기술하는 `PeriodicWave`로 설정될 것입니다.
* `sineTerms` 와 `cosineTerms`는 파형을 생성하기 위한 데이터를 저장하기 위해 사용될 것입니다; 각각은 사용자가 "Custom"을 선택했을 때 생성되는 배열을 포함할 것입니다.
### 음 테이블 생성하기

`createNoteTable()` 함수는 각 옥타브를 나타내는 객체의 배열을 포함하는 `noteFreq` 배열을 만듭니다. 차례로 각 옥타브는 그 옥타브에 있는 각 음에 대한 하나의 지정된 속성을 가집니다; 그 속성의 이름은 음의 이름입니다 (예를 들자면 C-sharp는 "C#"로 표현됩니다), 그리고 값은 헤르츠로 표현되는 그 음의 주파수입니다.

```js
function createNoteTable() {
  let noteFreq = [];
  for (let i=0; i< 9; i++) {
    noteFreq[i] = [];
  }

  noteFreq[0]["A"] = 27.500000000000000;
  noteFreq[0]["A#"] = 29.135235094880619;
  noteFreq[0]["B"] = 30.867706328507756;

  noteFreq[1]["C"] = 32.703195662574829;
  noteFreq[1]["C#"] = 34.647828872109012;
  noteFreq[1]["D"] = 36.708095989675945;
  noteFreq[1]["D#"] = 38.890872965260113;
  noteFreq[1]["E"] = 41.203444614108741;
  noteFreq[1]["F"] = 43.653528929125485;
  noteFreq[1]["F#"] = 46.249302838954299;
  noteFreq[1]["G"] = 48.999429497718661;
  noteFreq[1]["G#"] = 51.913087197493142;
  noteFreq[1]["A"] = 55.000000000000000;
  noteFreq[1]["A#"] = 58.270470189761239;
  noteFreq[1]["B"] = 61.735412657015513;
```

... 간결성을 위해 몇몇 옥타브는 생략되었습니다 ...
```

noteFreq[2]["C"] = 65.406391325149658;
noteFreq[2]["C#"] = 69.295657744218024;
noteFreq[2]["D"] = 73.41619197935189;
noteFreq[2]["D#"] = 77.781745930520227;
noteFreq[2]["E"] = 82.406889228217482;
noteFreq[2]["F"] = 87.307057858250971;
noteFreq[2]["F#"] = 92.498605677908599;
noteFreq[2]["G"] = 97.998858995437323;
noteFreq[2]["G#"] = 103.826174394986284;
noteFreq[2]["A"] = 110.0;
noteFreq[2]["A#"] = 116.540940379522479;
noteFreq[2]["B"] = 123.470825314031027;

noteFreq[3]["C"] = 130.812782650299317;
noteFreq[3]["C#"] = 138.591315488436048;
noteFreq[3]["D"] = 146.83238395870378;
noteFreq[3]["D#"] = 155.563491861040455;
noteFreq[3]["E"] = 164.813778456434964;
noteFreq[3]["F"] = 174.614115716501942;
noteFreq[3]["F#"] = 184.997211355817199;
noteFreq[3]["G"] = 195.997717990874647;
noteFreq[3]["G#"] = 207.652348789972569;
noteFreq[3]["A"] = 220.0;
noteFreq[3]["A#"] = 233.081880759044958;
noteFreq[3]["B"] = 246.941650628062055;

noteFreq[4]["C"] = 261.625565300598634;
noteFreq[4]["C#"] = 277.182630976872096;
noteFreq[4]["D"] = 293.66476791740756;
noteFreq[4]["D#"] = 311.12698372208091;
noteFreq[4]["E"] = 329.627556912869929;
noteFreq[4]["F"] = 349.228231433003884;
noteFreq[4]["F#"] = 369.994422711634398;
noteFreq[4]["G"] = 391.995435981749294;
noteFreq[4]["G#"] = 415.304697579945138;
noteFreq[4]["A"] = 440.0;
noteFreq[4]["A#"] = 466.163761518089916;
noteFreq[4]["B"] = 493.883301256124111;

noteFreq[5]["C"] = 523.251130601197269;
noteFreq[5]["C#"] = 554.365261953744192;
noteFreq[5]["D"] = 587.32953583481512;
noteFreq[5]["D#"] = 622.253967444161821;
noteFreq[5]["E"] = 659.255113825739859;
noteFreq[5]["F"] = 698.456462866007768;
noteFreq[5]["F#"] = 739.988845423268797;
noteFreq[5]["G"] = 783.990871963498588;
noteFreq[5]["G#"] = 830.609395159890277;
noteFreq[5]["A"] = 880.0;
noteFreq[5]["A#"] = 932.327523036179832;
noteFreq[5]["B"] = 987.766602512248223;

noteFreq[6]["C"] = 1046.502261202394538;
noteFreq[6]["C#"] = 1108.730523907488384;
noteFreq[6]["D"] = 1174.659071669630241;
noteFreq[6]["D#"] = 1244.507934888323642;
noteFreq[6]["E"] = 1318.510227651479718;
noteFreq[6]["F"] = 1396.912925732015537;
noteFreq[6]["F#"] = 1479.977690846537595;
noteFreq[6]["G"] = 1567.981743926997176;
noteFreq[6]["G#"] = 1661.218790319780554;
noteFreq[6]["A"] = 1760.0;
noteFreq[6]["A#"] = 1864.655046072359665;
noteFreq[6]["B"] = 1975.533205024496447;
```

```js
  noteFreq[7]["C"] = 2093.004522404789077;
  noteFreq[7]["C#"] = 2217.461047814976769;
  noteFreq[7]["D"] = 2349.318143339260482;
  noteFreq[7]["D#"] = 2489.015869776647285;
  noteFreq[7]["E"] = 2637.020455302959437;
  noteFreq[7]["F"] = 2793.825851464031075;
  noteFreq[7]["F#"] = 2959.955381693075191;
  noteFreq[7]["G"] = 3135.963487853994352;
  noteFreq[7]["G#"] = 3322.437580639561108;
  noteFreq[7]["A"] = 3520.000000000000000;
  noteFreq[7]["A#"] = 3729.310092144719331;
  noteFreq[7]["B"] = 3951.066410048992894;

  noteFreq[8]["C"] = 4186.009044809578154;
  return noteFreq;
}
```

결과는 `noteFreq` 배열인데, 이는 각 옥타브에 대한 객체를 가지고 있습니다. 각 옥타브 객체는 속성 이름이 음의 이름이고 (예를 들자면 C-sharp는 "C#"로 표현됩니다) 속성의 값은 헤르츠로 표현되는 음의 주파수인 지정된 속성들을 가지고 있습니다. 부분적으로는, 결과 객체는 다음과 같이 보입니다:

|  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- |
| 옥타브 | 음 |  |  |  |  |
| 0 | "A" ⇒ 27.5 | "A#" ⇒ 29.14 | "B" ⇒ 30.87 |  |  |  |  |  |  |  |  |  |
| 1 | "C" ⇒ 32.70 | "C#" ⇒ 34.65 | "D" ⇒ 36.71 | "D#" ⇒ 38.89 | "E" ⇒ 41.20 | "F" ⇒ 43.65 | "F#" ⇒ 46.25 | "G" ⇒ 49 | "G#" ⇒ 51.9 | "A" ⇒ 55 | "A#" ⇒ 58.27 | "B" ⇒ 61.74 |
| 2 | . . . |

준비된 이 표를 가지고, 우리는 특정한 옥타브에 있는 주어진 음에 대한 주파수를 꽤 쉽게 찾을 수 있습니다. 만약 우리가 옥타브 1의 G# 음의 주파수를 원한다면, 우리는 `noteFreq[1]["G#"]`을 사용하여 결과로 51.9의 값을 얻습니다.

**참고:** 위의 예시 표의 값들은 소숫점 둘째 자리까지 반올림되었습니다.

### 키보드 만들기

`setup()` 함수의 역할은 키보드를 만들고 앱이 음악을 재생하도록 준비하는 것입니다.

```js
function setup() {
  noteFreq = createNoteTable();

  volumeControl.addEventListener("change", changeVolume, false);

  mainGainNode = audioContext.createGain();
  mainGainNode.connect(audioContext.destination);
  mainGainNode.gain.value = volumeControl.value;

  // Create the keys; skip any that are sharp or flat; for
  // our purposes we don't need them. Each octave is inserted
  // into a <div> of class "octave".

  noteFreq.forEach(function (keys, idx) {
    let keyList = Object.entries(keys);
    let octaveElem = document.createElement("div");
    octaveElem.className = "octave";

    keyList.forEach(function (key) {
      if (key[0].length == 1) {
        octaveElem.appendChild(createKey(key[0], idx, key[1]));
      }
    });

    keyboard.appendChild(octaveElem);
  });

  document
    .querySelector("div[data-note='B'][data-octave='5']")
    .scrollIntoView(false);

  sineTerms = new Float32Array([0, 0, 1, 0, 1]);
  cosineTerms = new Float32Array(sineTerms.length);
  customWaveform = audioContext.createPeriodicWave(cosineTerms, sineTerms);

  for (i = 0; i < 9; i++) {
    oscList[i] = {};
  }
}

setup();
```

1. 음의 이름과 옥타브를 주파수에 대응(map)시키는 표는 `createNoteTable()`를 호출함으로써 생성됩니다.
2. 메인 gain 제어에서 `change (en-US)` 이벤트를 다루기 위해 `addEventListener()`를 호출함으로써 이벤트 핸들러가 생성되었습니다. 이것은 메인 gain 노드의 음량을 제어의 새 값으로 업데이트 합니다.
3. 다음으로, 음 주파수 표에 있는 각 옥타브에 대해 순회합니다. 각 옥타브에 대해, 우리는 그 옥타브에 있는 음들의 목록을 얻기 위해 `Object.entries()`를 사용합니다.
4. 그 옥타브의 음들을 포함하는 `<div>` (en-US)를 생성하고 (이렇게 함으로써 우리는 옥타브들 사이에 약간의 공간을 가질 수 있습니다), 이것의 클래스명을 "octave"로 설정합니다.
5. 옥타브에 있는 각 건반에 대해, 우리는 음의 이름이 한 문자보다 많은지 검사합니다. 우리는 이것을 생략하는데, 왜냐하면 우리는 이 예제에서 샤프(#) 음들을 무시하기 때문입니다. 만약 음의 이름이 단지 한 문자라면, 우리는 `createKey()`를 호출하는데, 이는 음의 문자열, 옥타브, 그리고 주파수를 명시합니다. 이 반환된 요소는 단계 4에서 생성된 옥타브 요소에 추가됩니다.
6. 각 옥타브 요소가 생성되었을 때, 키보드에 추가됩니다.
7. 키보드가 생성되고 나면, 옥타브 5의 "B" 음이 보이도록 스크롤합니다; 이것은 주변 건반들을 따라 중앙 '다' 음이 보이도록 하는 효과를 가지고 있습니다.
8. 그리고 나서 새로운 사용자 정의 파형이 `AudioContext.createPeriodicWave()` (en-US)를 사용하여 생성됩니다. 이 파형은 언제든지 사용자가 파형 선택기에서 "Custom"을 선택했을 때 사용될 것입니다.
9. 마지막으로, oscillator 목록이 어떤 oscillator가 어떤 건반과 연관되어 있는지를 식별하는 정보를 받기 위한 준비가 되었다는 것을 보장하도록 초기화됩니다.

#### 건반 생성하기

`createKey()` 함수는 가상 키보드에 표시하기를 원하는 각각의 건반에 대해 한 번 호출됩니다. 이것은 건반과 건반의 라벨으로 구성되는 요소를 생성하고, 추후의 사용을 위해 그 요소에 데이터 특성을 추가하고, 그리고 우리가 관심을 가지고 있는 이벤트에 대한 이벤트 핸들러를 부여합니다.

```js
function createKey(note, octave, freq) {
  let keyElement = document.createElement("div");
  let labelElement = document.createElement("div");

  keyElement.className = "key";
  keyElement.dataset["octave"] = octave;
  keyElement.dataset["note"] = note;
  keyElement.dataset["frequency"] = freq;

  labelElement.innerHTML = note + "<sub>" + octave + "</sub>";
  keyElement.appendChild(labelElement);

  keyElement.addEventListener("mousedown", notePressed, false);
  keyElement.addEventListener("mouseup", noteReleased, false);
  keyElement.addEventListener("mouseover", notePressed, false);
  keyElement.addEventListener("mouseleave", noteReleased, false);

  return keyElement;
}
```

건반과 건반의 라벨을 표현할 요소를 생성한 이후, 건반의 클래스를 (외양을 설정하는) "key"로 설정함으로써 건반의 요소를 설정합니다. 그리고 나서 건반의 옥타브(`data-octave` 특성), 재생할 음을 표현하는 문자열(`data-note` 특성), 헤르츠로 표현되는 주파수(`data-frequency` 특성)를 포함하는 `data-*` 특성을 추가합니다. 이것은 우리로 하여금 이벤트를 다룰 때 필요한 경우 쉽게 이 정보를 가져올 수 있도록 할 것입니다.

### 음악 만들기

#### 음 재생하기

`playTone()` 함수의 역할은 주어진 주파수의 음을 재생하는 것입니다. 이것은 적절한 음을 재생하는 키보드 건반의 이벤트 핸들러에 의해 사용될 것입니다.

```js
function playTone(freq) {
  let osc = audioContext.createOscillator();
  osc.connect(mainGainNode);

  let type = wavePicker.options[wavePicker.selectedIndex].value;

  if (type == "custom") {
    osc.setPeriodicWave(customWaveform);
  } else {
    osc.type = type;
  }

  osc.frequency.value = freq;
  osc.start();

  return osc;
}
```

`playTone()`은 `AudioContext.createOscillator()` (en-US) 메서드를 호출하여 새로운 `OscillatorNode` (en-US)를 생성함으로써 시작합니다. 그리고 나서 우리는 이것을 메인 gain 노드에 새로운 oscillator의 `OscillatorNode.connect()` 메서드를 호출함으로써 연결하는데, 이는 oscillator에게 이것의 결과를 어디로 보낼지 알려줍니다. 이렇게 함으로써, 메인 gain 노드의 gain을 변경하는 것은 생성되는 모든 음의 볼륨에 영향을 미칠 것입니다.

그리고 나서 우리는 사용할 파형의 유형을 세팅 바의 파형 선택기의 값을 검사함으로써 얻습니다. 만약 사용자가 이것을 `"custom"`으로 설정했다면, 우리는 사용자 정의 파형을 사용할 oscillator를 설정하기 위하여 `OscillatorNode.setPeriodicWave()` (en-US)를 호출합니다. 이를 하는 것은 자동적으로 oscillator의 `type` (en-US)을 `custom`으로 설정합니다. 만약 파형 선택기에서 다른 파형이 선택되었다면, 우리는 oscillator의 유형을 선택기의 값으로 설정합니다; 그 값은 `sine`, `square`, `triangle`, 그리고 `sawtooth` 중 하나일 것입니다.

oscillator의 주파수는 `Oscillator.frequency` `AudioParam` 객체의 값을 설정함으로써 `freq` 파라미터에 명시된 값으로 설정됩니다. 그리고서, 마침내, oscillator는 상속된 `AudioScheduledSourceNode.start()` (en-US) 메서드를 호출하여 소리를 생성하도록 시작됩니다.

#### 음 재생하기

`mousedown (en-US)` 이나 `mouseover` 이벤트가 건반에서 발생했을 때, 우리는 대응하는 음을 재생하기를 원합니다. `notePressed()` 함수는 이 이벤트들에 대한 이벤트 핸들러로 사용됩니다.

```js
function notePressed(event) {
  if (event.buttons & 1) {
    let dataset = event.target.dataset;

    if (!dataset["pressed"]) {
      let octave = +dataset["octave"];
      oscList[octave][dataset["note"]] = playTone(dataset["frequency"]);
      dataset["pressed"] = "yes";
    }
  }
}
```

두 가지 이유로, 우리는 주요 마우스 버튼이 눌러졌는지를 확인함으로써 시작합니다. 첫째로, 우리는 오직 주요 마우스 버튼이 노트 재생을 할 수 있게 허용하기를 원합니다. 둘째로, 그리고 더욱 중요하게, 우리는 유저가 음에서 음으로 드래그하는 경우에 대해 `mouseover (en-US)`를 다루기 위해 이것을 사용하고, 우리는 오직 마우스가 요소에 들어왔을 때 눌러졌다면 노트를 재생하기를 원합니다.

만약 마우스 버튼이 실제로 눌러졌다면, 우리는 눌러진 건반의 `dataset` 특성을 얻습니다; 이는 요소의 사용자 정의 데이터 특성에 접근하는 것을 쉽게 해 줍니다. 우리는 `data-pressed` 특성을 찾습니다; 만약 (음이 이미 재생되고 있지 않다는 것을 나타내는) 그것이 없다면, 요소의 `data-frequency` 특성 값을 전달하며, 우리는 음을 재생하기 위해 `playTone()`을 호출합니다. 반환된 oscillator는 `oscList`에 미래의 참조를 위해 저장되고, `data-pressed`는 음이 재생되고 있다는 것을 나타내기 위해 `yes`로 설정되어 다음 번에 이것이 호출되었을 때 이것을 다시 시작하지 않습니다.

#### 음 멈추기

`noteReleased()` 함수는 사용자가 마우스 버튼을 떼거나 마우스를 현재 재생되고 있는 건반 밖으로 이동시켰을 때 호출되는 이벤트 핸들러입니다.

```js
function noteReleased(event) {
  let dataset = event.target.dataset;

  if (dataset && dataset["pressed"]) {
    let octave = +dataset["octave"];
    oscList[octave][dataset["note"]].stop();
    delete oscList[octave][dataset["note"]];
    delete dataset["pressed"];
  }
}
```

`noteReleased()`는 사용자 정의 `data-octave`와 `data-note` 특성을 건반의 oscillator를 찾아보기 위해 사용하고, 그리고 나서 음 재생을 멈추기 위해 oscillator의 상속된 `stop()` (en-US) 메서드를 호출합니다. 마지막으로, 음이 현재 재생되고 있지 않다는 것을 나타내기 위해, 음에 대한 `oscList` 항목은 지워지고 `data-pressed` 특성은 (`event.target`에 의해 식별된) 건반 요소로부터 제거됩니다.

#### 메인 볼륨 변경하기

세팅 바의 볼륨 슬라이더는 메인 gain 노드의 gain 값을 변경하기 위한 간단한 인터페이스를 제공하는데, 이로써 재생되는 모든 음의 세기를 변경합니다. `changeVolume()` 메서드는 슬라이더의 `change (en-US)` 이벤트에 대한 핸들러입니다.

```js
function changeVolume(event) {
  mainGainNode.gain.value = volumeControl.value;
}
```

이것은 메인 gain 노드의 `gain` `AudioParam`의 값을 슬라이더의 새로운 값으로 설정합니다.

### 결과

이를 모두 합하면, 결과는 간단하지만 작동하는 마우스로 이용 가능한 뮤지컬 키보드입니다.

같이 보기
-----

* Web Audio API
* `OscillatorNode` (en-US)
* `GainNode` (en-US)
* `AudioContext`


Web Audio API로 시각화하기 - Web API

Web Audio API로 시각화하기
====================

Web Audio API의 가장 흥미로운 기능 중 하나는 주파수, 파형, 그리고 다른 데이터들을 오디오 소스로부터 추출할 수 있는 능력인데, 이는 그리고 나서 시각화하는 데 사용될 수 있습니다. 이 글은 어떻게 시각화를 하는지 설명하고, 기초적인 사용 방법을 두 개 제공합니다.

**참고:** 모든 코드의 작동 예제를 Voice-change-O-matic 데모에서 찾을 수 있습니다.

기본 개념
-----

오디오 소스에서 데이터를 추출하려면, 여러분은 `AnalyserNode`가 필요한데, 이는 `BaseAudioContext.createAnalyser` (en-US)를 사용하여 생성됩니다. 예를 들자면:

```js
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioCtx.createAnalyser();
```

그리고 나서 이 노드는 소스와 목적지 사이의 어떤 지점에서 여러분의 오디오 소스에 연결되는데, 예를 들자면:

```js
source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(audioCtx.destination);
```

**참고:** 입력이 소스에 직접적으로든 혹은 다른 노드를 경유하든 연결되어 있는 한, 여러분은 analyser의 출력을 다른 노드에 이것이 작동하도록 연결할 필요가 없습니다.

analyser 노드는 그리고 나서, 여러분이 `AnalyserNode.fftSize` 속성 값에 명시한 것에 따라, 고속 푸리에 변환 (fft) 을 사용하여 특정한 주파수 도메인에 있는 오디오 데이터를 캡쳐합니다. (만약 값이 명시되어 있지 않으면, 기본값은 2048입니다.)

**참고:** 여러분은 또한 `AnalyserNode.minDecibels`과 `AnalyserNode.maxDecibels`을 사용하여 fft 데이터 스케일링 범위에 대해 최소와 최대 power 값을 명시할 수 있고, `AnalyserNode.smoothingTimeConstant`를 사용하여 각각 다른 데이터 averaging 상수를 명시할 수 있습니다. 이것들을 어떻게 사용하는지에 대해서는 각 페이지를 읽어 보세요.

데이터를 캡쳐하기 위해서는, 여러분은 주파수 데이터를 캡쳐하기 위해 `AnalyserNode.getFloatFrequencyData()`와 `AnalyserNode.getByteFrequencyData()` 메서드를 사용할 필요가 있고, 파형 데이터를 캡쳐하기 위해서는 `AnalyserNode.getByteTimeDomainData()`와 `AnalyserNode.getFloatTimeDomainData()`를 사용할 필요가 있습니다.

이 메서드들은 데이터를 명시된 배열에 복사하므로, 여러분은 메서드를 호출하기 전에 데이터를 받기 위한 새로운 배열을 만들 필요가 있습니다. 첫번째 메서드는 32비트 부동 숫자를 만들고, 두번째와 세번째는 8비트 unsigned 정수를 만들기 때문에, 표준 JavaScript 배열은 이를 수행할 수 없습니다 — 여러분이 어떤 데이터를 다루느냐에 따라, `Float32Array` (en-US)나 `Uint8Array` (en-US)배열을 사용할 필요가 있습니다.

그래서 예를 들자면, 우리가 2048의 fft 사이즈를 다루고 있다고 해 봅시다. 우리는 fft의 절반인 `AnalyserNode.frequencyBinCount` 값을 반환하고, 그리고 나서 Uint8Array의 길이 인자로서 frequencyBinCount와 함께 Uint8Array()을 호출합니다 — 이것은 얼마나 많은 데이터 포인트를 우리가 그 fft 사이즈에 대해 수집할 것인지를 나타냅니다.

```js
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
```

실제로 데이터를 얻고 배열에 복사하기 위해서, 우리는 그리고 나서 우리가 원하는 데이터 수집 메서드를 그것의 인자로서 전달된 배열과 함께 호출합니다. 예를 들자면:

```js
analyser.getByteTimeDomainData(dataArray);
```

우리는 이제 시간으로 그 순간에 대해 배열 속에 캡쳐된 오디오 데이터를 가지고 있고, 우리가 원하는 대로 시각화를 진행할 수 있는데, 예를 들자면 이것을 HTML5 `<canvas>`에 그려볼 수 있습니다.

몇몇 구체적인 예제를 살펴봅시다.

파형/오실로스코프 생성하기
--------------

오실로스코프 시각화를 만들기 위해서는 (Voice-change-O-matic 내의 원본 코드에 대해 Soledad Penadés께 인사를 보냅니다), 우리는 우선 버퍼를 설정하기 위해 이전 섹션에서 설명된 표준 패턴을 따라야 합니다.

```js
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
```

다음으로, 우리는 새로운 시각화 디스플레이를 준비하기 위해 캔버스 위에 이전에 그려진 것을 지웁니다.

```js
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
```

우리는 이제 `draw()` 함수를 정의합니다:

```js
function draw() {
```

여기서, 우리는 드로잉 함수가 한 번 시작되면 계속 반복되도록 `requestAnimationFrame()`을 사용합니다.

```js
var drawVisual = requestAnimationFrame(draw);
```

다음으로, 우리는 시간 도메인 데이터를 배열에 복사합니다.

```js
analyser.getByteTimeDomainData(dataArray);
```

다음으로, 시작하기 위해 캔버스를 단색으로 채웁니다.

```js
canvasCtx.fillStyle = "rgb(200, 200, 200)";
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
```

우리가 그릴 파동에 대해 선의 굵기와 색상을 설정하고, 경로를 그리기 시작합니다.

```js
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "rgb(0, 0, 0)";
canvasCtx.beginPath();
```

캔버스 너비를 (앞서 정의된 바와 같이 FrequencyBinCount와 동일한) 배열 길이로 나눔으로써 그려질 선의 각 부분의 너비를 결정하고, 선의 각 부분을 그리기 위해 이동할 위치를 정의하기 위해 x 변수를 정의합니다.

```js
var sliceWidth = (WIDTH \* 1.0) / bufferLength;
var x = 0;
```

이제 배열로부터의 데이터 포인트 값에 기반한 특정한 높이에서의 버퍼의 각 부분에 대해 파동의 작은 부분의 위치를 정의하고, 다음 파동 부분이 그려질 지점까지 선을 이동하는 반복문을 실행합니다.

```js
for (var i = 0; i < bufferLength; i++) {
  var v = dataArray[i] / 128.0;
  var y = (v \* HEIGHT) / 2;

  if (i === 0) {
    canvasCtx.moveTo(x, y);
  } else {
    canvasCtx.lineTo(x, y);
  }

  x += sliceWidth;
}
```

마지막으로, 우리는 캔버스의 우측 중앙에서 선을 끝내고, 우리가 정의한 획을 그립니다:

```js
      canvasCtx.lineTo(canvas.width, canvas.height/2);
      canvasCtx.stroke();
    };
```

코드의 이 섹션의 마지막에서, 우리는 전체 과정을 시작하기 위해 `draw()` 함수를 호출합니다:

```js
draw();
```

이것은 초당 수 차례 갱신되는 멋진 파형 디스플레이를 보여줍니다:

![오디오 신호의 파형을 보여주는 검은색 오실로스코프 선](/ko/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API/wave.png)

주파수 막대 그래프 생성하기
---------------

제작할 또 다른 멋진 작은 사운드 시각화는 Winamp 스타일의 주파수 막대 그래프 중 하나입니다. 우리는 Voice-change-O-matic에서 이용 가능한 하나를 가지고 있습니다; 어떻게 이것이 완성되었는지를 살펴봅시다.

우선, 우리는 analyser와 데이터 배열을 다시 준비하고, `clearRect()`로 현재 캔버스 디스플레이를 지웁니다. 이전과 다른 유일한 점은 fft 사이즈를 더욱 작게 설정했다는 것입니다. 이것은 그래프에서 각각의 바가 얇은 가닥보다는 실제로 막대기처럼 보이기에 충분히 크도록 하기 위함입니다.

```js
analyser.fftSize = 256;
var bufferLength = analyser.frequencyBinCount;
console.log(bufferLength);
var dataArray = new Uint8Array(bufferLength);

canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
```

다음으로, 우리는 표시된 데이터가 계속 업데이트되도록 `requestAnimationFrame()`으로 반복을 다시 설정하고, 각 애니메이션 프레임의 디스플레이를 지우며, `draw()` 함수를 시작시킵니다.

```js
    function draw() {
      drawVisual = requestAnimationFrame(draw);

      analyser.getByteFrequencyData(dataArray);

      canvasCtx.fillStyle = 'rgb(0, 0, 0)';
      canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
```

이제 우리는 `barWidth`를 막대기의 수 (버퍼 길이)로 나눠진 캔버스의 폭과 동일하게 설정합니다. 그러나, 우리는 또한 그 폭에 2.5를 곱하는데, 왜냐하면 우리가 매일 듣는 소리들의 대부분은 특정한 낮은 주파수 범위에 있으므로 대부분의 주파수들은 어떤 오디오도 가지고 있지 않은 채로 되돌아오기 때문입니다. 우리는 수많은 비어있는 막대기들을 표시하기 원하지 않으므로, 우리는 뚜렷한 높이에 자주 표시될 막대기들을 옮겨 그것들이 캔버스 디스플레이를 채우도록 합니다.

우리는 또한 `barHeight` 변수와, 현재 막대기를 그리기 위해 스크린이 얼마나 멀리 가로질러 떨어져 있는지를 기록하기 위해 `x` 변수를 설정합니다.

```js
var barWidth = (WIDTH / bufferLength) \* 2.5;
var barHeight;
var x = 0;
```

이전과 같이, 우리는 이제 for 반복문을 시작해 `dataArray`의 각 값을 순회합니다. 각 값에 대해, 우리는 `barHeight`를 배열 값과 같게 만들고, 채울 색깔을 `barHeight`에 기반해 설정하고 (높을수록 밝습니다), 막대기를 캔버스를 `x` 픽셀 가로지른 곳에 그리는데, 이는 폭이 `barWidth`이고 높이는 `barHeight/2`입니다. (우리는 결국 막대기들이 캔버스에 더 잘 맞도록 각 막대기들을 반절로 자르기로 결정했습니다)

설명을 필요로 하는 하나의 값은 우리가 각 막대기를 그리는 수직 오프셋 위치인 `HEIGHT-barHeight/2`입니다. 제가 이것을 한 이유는, 만약 우리가 수직 위치를 0으로 설정했다면 각 막대기가 캔버스의 위에서부터 아래로 튀어나왔을 텐데, 제가 원한 것은 이게 아니라 막대기들이 아래에서부터 위로 튀어나오기를 원했기 때문입니다. 그러므로, 우리는 대신 매번 수직 위치를 캔버스의 높이 마이너스 `barHeight/2`를 하여, 각 막대기가 캔버스의 아래 도중에서부터, 아래에서 캔버스의 밑을 향해 그려질 것입니다.

```js
      for(var i = 0; i < bufferLength; i++) {
        barHeight = dataArray[i]/2;

        canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
        canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight);

        x += barWidth + 1;
      }
    };
```

다시, 코드의 마지막에서 우리는 전체 과정을 움직이게 만들기 위해 draw() 함수를 호출합니다.

```js
draw();
```

이 코드는 다음과 같은 결과를 보여줍니다:

![오디오 신호의 각각 다른 주파수들의 강도를 보여주는 막대 그래프의 일련의 붉은 색 막대들](/ko/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API/bar-graph.png)

**참고:** 이 글에서 언급된 예제들은 `AnalyserNode.getByteFrequencyData()` 와 `AnalyserNode.getByteTimeDomainData()`의 사용을 보여주었습니다. `AnalyserNode.getFloatFrequencyData()` 와 `AnalyserNode.getFloatTimeDomainData()`를 보여주는 작동 예제에 대해서는, Voice-change-O-matic-float-data 데모 (소스 코드 도 확인할 수 있습니다) 를 참고하세요 — 이것은 unsigned 바이트 데이터가 아니라, Float 데이터를 사용한다는 것을 제외하면, 이것은 정확히 원래 Voice-change-O-matic와 똑같습니다.


고급 기법: 오디오 생성 및 시퀸싱 - Web API

고급 기법: 오디오 생성 및 시퀸싱
===================

이 자습서에서, 우리는 사운드 생성과 수정 뿐만 아니라, 타이밍과 스케쥴링도 다룰 것입니다. 우리는 샘플 로딩, 엔벨로프, 필터, 웨이브 테이블, 주파수 조정을 소개할 것입니다. 만약 여러분이 이 용어들에 익숙하고 Web Audio API 내에서의 이것들의 응용에 대한 입문을 찾고 있다면, 맞는 장소에 오셨습니다.

데모
--

우리는 아주 간단한 스텝 시퀸서를 살펴볼 것입니다:

![재생과 BPM 주 제어, 각각을 제어할 수 있는 4가지의 다른 소리를 기능으로 가지는 사운드 시퀸서 애플리케이션.](/ko/docs/Web/API/Web_Audio_API/Advanced_techniques/sequencer.png)

실제로는 이것은 라이브러리로 하는 것이 더 쉽습니다 — Web Audio API는 기반이 되도록 개발되었습니다. 만약 여러분이 무언가 더욱 복잡한 것의 개발에 착수하려고 한다면, tone.js가 시작하기 좋은 지점일 것입니다. 그러나, 학습 예제로서, 우리는 이런 데모를 기본 원칙들으로부터 어떻게 개발하는지를 보이고 싶습니다.

**참고:** 여러분은 step-sequencer 소스 코드를 GitHub에서 찾을 수 있습니다; step-sequencer 작동 예제도 확인해 보세요.

인터페이스는 시퀸서를 재생/정지할 수 있게 하는 주 제어 버튼, 그리고 "음악"의 속도를 올리거나 내릴 수 있게 하는 BPM (beats per minute) 조절 슬라이더로 이루어져 있습니다.

재생될 수 있는 네 가지의 다른 음, 또는 소리가 있습니다. 각 소리는 네 개의 버튼을 가지고 있는데, 이는 음악의 한 마디에 있는 네 개의 비트를 나타냅니다. 이것들이 활성화되었을 때 노트는 소리를 낼 것입니다. 악기가 재생될 때, 이것은 이 비트들의 세트를 가로질러 이동하고 마디를 반복할 것입니다.

각 소리들은 또한 해당 소리를 수정할 수 있는 슬라이더를 가지고 있는데, 이는 이펙트나 우리가 이 소리들을 만들기 위해 사용한 각 기법의 특정한 파라미터를 조작할 수 있게 해 줍니다. 우리가 사용하는 이 기법들은:

| 소리의 이름 | 기법 | 연관된 Web Audio API 기능 |
| --- | --- | --- |
| "스윕" | 오실레이터, 주기파 | `OscillatorNode` (en-US), `PeriodicWave` |
| "펄스" | 다수의 오실레이터 | `OscillatorNode` (en-US) |
| "노이즈" | 무작위 노이즈 버퍼, 바이쿼드 필터 | `AudioBuffer`, `AudioBufferSourceNode`, `BiquadFilterNode` |
| "다이얼 음" | 재생할 사운드 샘플 로딩하기 | `BaseAudioContext/decodeAudioData` (en-US), `AudioBufferSourceNode` |

**참고:** 이 악기는 좋은 소리를 내기 위해 만들어지지 않았습니다. 이것은 데모 코드를 제공하기 위해 만들어졌고 이런 악기의 **아주** 단순화된 버전을 나타냅니다. 이 소리들은 다이얼 접속 모뎀에 기반합니다. 만약 여러분이 이것이 어떤 소리인지 모른다면 여기서 들어볼 수 있습니다.

오디오 컨텍스트 생성하기
-------------

지금쯤은 이미 익숙할지도 모르지만, 각 Web Audio API 앱은 오디오 컨텍스트로 시작합니다:

```js
// 크로스 브라우저 호환성을 위해
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
```

"스윕" — 오실레이터, 주기파, 엔벨로프
-----------------------

여러분이 접속할 때 처음 듣는 노이즈인 우리가 "스윕" 소리라고 부를 것에 대해서, 우리는 소리를 생성하기 위해 오실레이터를 생성할 것입니다.

`OscillatorNode` (en-US)에는 바로 사용할 수 있는 기본 파형 — 사인파, 사각파, 삼각파, 톱니파 — 이 딸려 있습니다. 그러나, 기본으로 딸린 표준 파동을 사용하는 대신, 우리는 `PeriodicWave` 인터페이스와 웨이브 테이블에 설정된 값들을 이용해 우리만의 소리를 만들 것입니다. 우리는 오실레이터로 이 사용자 정의 파동을 만들기 위해 `BaseAudioContext.createPeriodicWave` 메서드를 사용할 수 있습니다.

### 주기파

우선, 우리는 주기파를 생성합니다. 이렇게 하기 위해서는, 우리는 `BaseAudioContext.createPeriodicWave()` 메서드에 real과 imaginary 값을 전달할 필요가 있습니다:

```js
const wave = audioCtx.createPeriodicWave(wavetable.real, wavetable.imag);
```

**참고:** 우리의 예제에서 웨이브 테이블은 별도의 JavaScript 파일 (`wavetable.js`) 에 저장되어 있는데, 왜냐하면 **아주** 많은 값들이 있기 때문입니다. 이것은 Google Chrome Labs으로부터의 Web Audio API 예제들에서 찾을 수 있는 웨이브 테이블 레포지토리에서 가져온 것입니다.

### 오실레이터

이제 우리는 `OscillatorNode` (en-US)를 생성하고 우리가 생성한 것에 이것의 파동을 설정합니다.

```js
function playSweep(time) {
  const osc = audioCtx.createOscillator();
  osc.setPeriodicWave(wave);
  osc.frequency.value = 440;
  osc.connect(audioCtx.destination);
  osc.start(time);
  osc.stop(time + 1);
}
```

여기서 우리는 시간 매개변수를 함수에 전달하는데, 이는 우리가 나중에 스윕을 스케쥴하기 위해서 사용할 것입니다.

### 진폭 제어하기

이것은 훌륭하지만, 만약 우리가 이것에 적용되는 진폭 엔벨로프를 가지고 있다면 멋지지 않을까요? Web Audio API로 엔벨로프를 생성하기 위해 필요한 메서드들에 익숙해지도록 간단한 것을 생성해 봅시다.

우리의 엔벨로프가 attack과 release를 가지고 있다고 해 봅시다. 우리는 사용자가 이것들을 인터페이스의 range 입력 (en-US)를 사용하여 제어할 수 있도록 할 수 있습니다.

```html
<label for="attack">Attack</label>
<input
 name="attack"
 id="attack"
 type="range"
 min="0"
 max="1"
 value="0.2"
 step="0.1" />

<label for="release">Release</label>
<input
 name="release"
 id="release"
 type="range"
 min="0"
 max="1"
 value="0.5"
 step="0.1" />
```

이제 우리는 JavaScript에서 몇 가지 변수들을 생성하고 입력 값들이 업데이트되었을 때 바뀌도록 할 수 있습니다:

```js
let attackTime = 0.2;
const attackControl = document.querySelector("#attack");
attackControl.addEventListener(
  "input",
  function () {
    attackTime = Number(this.value);
  },
  false,
);

let releaseTime = 0.5;
const releaseControl = document.querySelector("#release");
releaseControl.addEventListener(
  "input",
  function () {
    releaseTime = Number(this.value);
  },
  false,
);
```

### 최종 playSweep() 함수

이제 우리는 우리의 `playSweep()` 함수를 확장할 수 있습니다. 우리는 `GainNode` (en-US)를 추가하고 실제로 진폭 변화를 소리에 적용하기 위하여 우리의 오디오 그래프를 통해 이 노드를 연결할 필요가 있습니다. gain 노드는 하나의 속성 `gain` 을 가지고 있는데, 이는 `AudioParam` 유형입니다.

이것은 정말로 유용합니다 — 이제 우리는 gain 값에 audio param 메서드의 힘을 사용할 수 있습니다. 우리는 특정한 시간에 값을 설정할 수 있거나, `AudioParam.linearRampToValueAtTime`과 같은 메서드로 시간에 **따라** 이것을 변화시킬 수 있습니다.

우리의 attack과 release에 대해, 우리는 위에서 언급된 `linearRampToValueAtTime` 메서드를 사용할 것입니다. 이것은 두 개의 매개변수를 취합니다 — 변화시키고 있는 파라미터에 설정하기 원하는 값 (이 경우 gain) 그리고 언제 이것을 하고 싶은지. 우리의 경우 **언제**는 우리의 입력에 의해 제어됩니다. 그래서 아래의 예제에서 gain은 attack 범위 입력이 설정된 시간에 따라 선형 비율로 1까지 증가됩니다. 비슷하게 release의 경우, release 입력이 설정된 시간에 걸쳐 선형 비율로 gain은 0으로 설정됩니다.

```js
let sweepLength = 2;
function playSweep(time) {
  let osc = audioCtx.createOscillator();
  osc.setPeriodicWave(wave);
  osc.frequency.value = 440;

  let sweepEnv = audioCtx.createGain();
  sweepEnv.gain.cancelScheduledValues(time);
  sweepEnv.gain.setValueAtTime(0, time);
  // attack을 설정합니다
  sweepEnv.gain.linearRampToValueAtTime(1, time + attackTime);
  // release를 설정합니다
  sweepEnv.gain.linearRampToValueAtTime(0, time + sweepLength - releaseTime);

  osc.connect(sweepEnv).connect(audioCtx.destination);
  osc.start(time);
  osc.stop(time + sweepLength);
}
```

"펄스" — 낮은 주파수 오실레이터 조정
----------------------

훌륭합니다, 이제 우리는 스윕을 가지고 있습니다! 계속해서 이 멋진 펄스 사운드에도 관심을 기울여 봅시다. 우리는 이것을 두 번째 오실레이터에 의해 조정되는 기본적인 오실레이터로 달성할 수 있습니다.

### 첫 오실레이터

우리가 맞춤형 파동을 설정하기 위해 웨이브 테이블을 사용하지 않을 것이라는 점을 제외하고, 우리는 스윕 사운드에 한 것과 같은 방식으로 첫번째 `OscillatorNode` (en-US)를 설정합니다 — 우리는 단지 기본 `sine`파를 사용할 것입니다:

```js
const osc = audioCtx.createOscillator();
osc.type = "sine";
osc.frequency.value = 880;
```

우리가 두 번째 저주파 오실레이터로 진동시킬 것은 gain 값이므로, 이제 우리는 `GainNode` (en-US)를 만들 것입니다.

```js
const amp = audioCtx.createGain();
amp.gain.setValueAtTime(1, audioCtx.currentTime);
```

### 두 번째 저주파 오실레이터 생성하기

첫 번째 사인파의 진폭을 변경하기 위해 우리는 이제 두 번째 — `square` — 파 (또는 펄스) 오실레이터를 생성할 것입니다:

```js
const lfo = audioCtx.createOscillator();
lfo.type = "square";
lfo.frequency.value = 30;
```

### 그래프 연결하기

여기서 중요한 점은 그래프를 옳게 연결하는 것이고, 더불어 두 개의 오실레이터를 모두 시작시키는 것입니다:

```js
lfo.connect(amp.gain);
osc.connect(amp).connect(audioCtx.destination);
lfo.start();
osc.start(time);
osc.stop(time + pulseTime);
```

**참고:** 우리는 또한 우리가 생성하는 이 오실레이터 중 어느 것에 대해서도 기본 파동 유형을 사용할 필요가 없습니다 — 우리는 웨이브 테이블과 우리가 전에 한 것처럼 주기파를 사용할 수 있습니다. 최소한의 노드들로도 다수의 가능성이 있습니다.

### 펄스 슬라이더

UI 제어에 대해, 오실레이터의 두 주파수들이 range 입력을 통해 제어될 수 있도록 모두 노출합시다. 하나는 음색을 변경할 것이고 나머지 하나는 어떻게 펄스가 첫번째 파동을 조정하는지를 변경할 것입니다.

```html
<label for="hz">Hz</label>
<input
 name="hz"
 id="hz"
 type="range"
 min="660"
 max="1320"
 value="880"
 step="1" />
<label for="lfo">LFO</label>
<input name="lfo" id="lfo" type="range" min="20" max="40" value="30" step="1" />
```

이전처럼, 우리는 range 입력 값이 사용자에 의해 변화되었을 때 파라미터에 변화를 줄 것입니다.

```js
let pulseHz = 880;
const hzControl = document.querySelector("#hz");
hzControl.addEventListener(
  "input",
  function () {
    pulseHz = Number(this.value);
  },
  false,
);

let lfoHz = 30;
const lfoControl = document.querySelector("#lfo");
lfoControl.addEventListener(
  "input",
  function () {
    lfoHz = Number(this.value);
  },
  false,
);
```

### 최종 playPulse() 함수

여기 전체 `playPulse()` 함수가 있습니다:

```js
let pulseTime = 1;
function playPulse(time) {
  let osc = audioCtx.createOscillator();
  osc.type = "sine";
  osc.frequency.value = pulseHz;

  let amp = audioCtx.createGain();
  amp.gain.value = 1;

  let lfo = audioCtx.createOscillator();
  lfo.type = "square";
  lfo.frequency.value = lfoHz;

  lfo.connect(amp.gain);
  osc.connect(amp).connect(audioCtx.destination);
  lfo.start();
  osc.start(time);
  osc.stop(time + pulseTime);
}
```

"노이즈" — 바이쿼드 필터와 함께하는 무작위 노이즈 버퍼
--------------------------------

이제 우리는 노이즈를 만들 필요가 있습니다! 모든 모뎀은 노이즈를 가지고 있습니다. 노이즈는 오디오 데이터에 관한 한 단지 무작위 숫자들이므로, 코드로 생성하기 비교적 간단한 것입니다.

### 오디오 버퍼 생성하기

우리는 이 값들을 넣을 빈 컨테이너를 만들 필요가 있지만, 이것은 Web Audio API가 이해하는 것이어야 합니다. 여기서 `AudioBuffer` 객체가 등장합니다. 여러분은 파일을 가지고 와서 버퍼 안에 디코드하거나 (자습서에서 나중에 시작할 것입니다), 또는 여러분은 빈 버퍼를 만들고 여러분만의 데이터로 채울 수 있습니다.

노이즈에 대해, 후자를 선택합시다. 우리는 버퍼를 만들기 위해 버퍼의 크기를 계산할 필요가 있습니다. 우리는 이것을 위해 `BaseAudioContext.sampleRate` (en-US) 속성을 사용할 수 있습니다:

```js
const bufferSize = audioCtx.sampleRate \* noiseLength;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
```

이제 우리는 이것을 -1과 1 사이의 랜덤 값으로 채울 수 있습니다.

```js
let data = buffer.getChannelData(0); // 데이터 얻기

// 버퍼를 노이즈로 채우기
for (let i = 0; i < bufferSize; i++) {
  data[i] = Math.random() \* 2 - 1;
}
```

**참고:** 왜 -1에서 1인가요? 파일이나 스피커에 사운드를 출력할 때 우리는 0db 풀 스케일을 표현하기 위해 하나의 숫자를 가질 필요가 있습니다 — 고정점 미디어나 DAC의 숫자로 나타낸 한도입니다. 부동점 오디오에서, 1은 신호에 대한 수학적 연산을 위한 "풀 스케일"을 매핑하는 편리한 숫자입니다. 그래서 오실레이터, 노이즈 생성기, 그리고 다른 음원은 보통 -1에서 1 범위에서 쌍극(bipolar) 신호를 출력합니다. 브라우저는 이 범위 바깥의 값들을 고정시킬 것입니다.

### 버퍼 소스 생성하기

이제 우리는 오디오 버퍼를 가지고 있고 이것을 데이터로 채웠으니, 우리는 소스로서 버퍼를 사용할 수 있는 그래프에 추가할 노드가 필요합니다. 우리는 이것을 위해 `AudioBufferSourceNode`를 생성하고, 우리가 생성한 데이터를 전달할 것입니다:

```js
let noise = audioCtx.createBufferSource();
noise.buffer = buffer;
```

만약 우리가 이것을 오디오 그래프를 통해 연결시키고 재생한다면 —

```js
noise.connect(audioCtx.destination);
noise.start();
```

여러분은 이것이 꽤 쉭쉭하는 소리가 나거나 금속성의 소리가 나는 것을 인지하였을 것입니다. 우리는 백색소음을 만들었고, 백색소음이라면 그래야만 합니다. 값은 -1과 1 사이에서 실행되는데, 이는 우리가 모든 주파수의 피크를 가지고 있고, 결국 실제로 꽤 찢어지는 듯한 소리가 난다는 것을 의미합니다. 우리는 이 함수가 0.5에서 -0.5에서 실행되거나 피크를 내려 불편함을 줄이는 유사한 작업을 하도록 수정**할 수 있는데**, 그렇게 하면 재미가 없죠? 우리가 생성한 노이즈가 필터를 통과하도록 전송합시다.

### 믹스에 바이쿼드 필터 추가하기

우리는 분홍색 또는 갈색 소음의 범위에 있는 무언가를 원합니다. 우리는 저 높은 주파수들과 아마 몇몇 낮은 것들을 잘라내기를 원합니다. 이 일을 위해 밴드패스 바이쿼드 필터를 도입합시다.

**참고:** Web Audio API는 두 종류의 필터 노드를 가지고 있습니다: `BiquadFilterNode`와 `IIRFilterNode` (en-US)입니다. 대부분의 경우 바이쿼드 필터는 충분히 좋습니다 — 이것은 로우패스, 하이패스, 밴드패스와 같은 다른 종류의 필터들을 가지고 있습니다. 그러나 만약 여러분이 좀 더 맞춤형의 무언가를 찾고 있다면, IIR 필터가 좋은 옵션일 수 있습니다 — 더 많은 정보가 필요하시다면 IIR 필터 사용하기를 참고해 보세요.

이것을 연결하는 것은 우리가 전에 본 것과 같습니다. 우리는 `BiquadFilterNode`를 생성하고, 원하는 속성들을 설정하고 그래프를 통해 연결시킵니다. 바이쿼드 필터의 다른 유형들은 다른 속성들을 가지고 있습니다 — 예를 들자면 밴드패스 유형에 주파수를 설정하는 것은 중앙 주파수를 조정하는데, 로우패스에서 이것은 위쪽 주파수를 설정할 것입니다.

```js
let bandpass = audioCtx.createBiquadFilter();
bandpass.type = "bandpass";
bandpass.frequency.value = 1000;

// 그래프 연결하기
noise.connect(bandpass).connect(audioCtx.destination);
```

### 노이즈 슬라이더

UI에서 우리는 사용자가 range 입력과 지난 섹션에서처럼의 이벤트 처리기를 통해 노이즈 지속과 우리가 밴드하기를 원하는 주파수를 조정할 수 있도록 이것들을 노출시킬 것입니다.

```html
<label for="duration">Duration</label>
<input
 name="duration"
 id="duration"
 type="range"
 min="0"
 max="2"
 value="1"
 step="0.1" />

<label for="band">Band</label>
<input
 name="band"
 id="band"
 type="range"
 min="400"
 max="1200"
 value="1000"
 step="5" />
```

```js
let noiseDuration = 1;
const durControl = document.querySelector("#duration");
durControl.addEventListener(
  "input",
  function () {
    noiseDuration = Number(this.value);
  },
  false,
);

let bandHz = 1000;
const bandControl = document.querySelector("#band");
bandControl.addEventListener(
  "input",
  function () {
    bandHz = Number(this.value);
  },
  false,
);
```

### 최종 playNoise() 함수

여기 전체 `playNoise()` 함수가 있습니다:

```js
function playNoise(time) {
  const bufferSize = audioCtx.sampleRate \* noiseDuration; // 노트의 시간을 설정합니다
  const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); // 빈 버퍼를 생성합니다
  let data = buffer.getChannelData(0); // get data

  // 버퍼를 노이즈로 채웁니다
  for (let i = 0; i < bufferSize; i++) {
    data[i] = Math.random() \* 2 - 1;
  }

  // 생성된 데이터에 대해 버퍼 소스 생성하기
  let noise = audioCtx.createBufferSource();
  noise.buffer = buffer;

  let bandpass = audioCtx.createBiquadFilter();
  bandpass.type = "bandpass";
  bandpass.frequency.value = bandHz;

  // 그래프 연결하기
  noise.connect(bandpass).connect(audioCtx.destination);
  noise.start(time);
}
```

"다이얼 음" — 사운드 샘플 로딩하기
---------------------

이미 살펴본 메서드들을 사용해 두 개의 오실레이터를 함께 재생함으로써 휴대폰 다이얼 (DTMF) 소리를 모방하는 것은 충분히 간단하지만, 이 섹션에서, 우리는 무엇이 관여되어 있는지를 살펴볼 수 있도록 대신 샘플 파일을 로드할 것입니다.

### 샘플 로딩하기

우리가 파일을 사용하기 전에 파일이 로드되었고 버퍼 안으로 디코드되었음을 확실히 하기를 원하므로, 이를 하기 위해서 `async` 함수를 생성합시다.

```js
async function getFile(audioContext, filepath) {
  const response = await fetch(filepath);
  const arrayBuffer = await response.arrayBuffer();
  const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
  return audioBuffer;
}
```

그리고 나서 이 함수를 호출할 때 우리는 `await` 연산자를 사용할 수 있는데, 이는 이것이 실행을 마쳤을 때 오직 우리가 차후의 코드를 실행할 수 있다는 것을 보장합니다.

샘플을 설정하기 위해 또 다른 `async` 함수를 생성합시다 — 이 파일이 로드되고 버퍼된 후의 동작을 수행하기 위해 우리는 두 개의 async 함수를 멋진 프로미스 패턴으로 결합할 수 있습니다:

```js
async function setupSample() {
  const filePath = "dtmf.mp3";
  const sample = await getFile(audioCtx, filePath);
  return sample;
}
```

**참고:** 파일의 배열을 취해서 하나 이상의 샘플의 로드를 반복하기 위해 여러분은 쉽게 위의 함수를 수정할 수 있습니다. 이것은 더욱 복잡한 악기나, 게이밍에 대해 매우 유용할 것입니다.

이제 우리는 `setupSample()`을 다음과 같이 사용할 수 있습니다:

```js
setupSample().then((sample) => {
  // 샘플은 우리의 버퍼된 파일입니다
  // ...
});
```

샘플이 재생할 준비가 되었을 때, 프로그램을 UI를 설정하므로 이것은 실행될 준비가 됩니다.

### 샘플 재생하기

우리가 다른 소리들에 대해 한 것과 비슷한 방법으로 `playSample()` 함수를 생성해 봅시다. 이번엔 이것은 `AudioBufferSourceNode`를 생성하고, 우리가 가져온 버퍼 데이터를 넣고 내부에서 디코드하고, 재생할 것입니다:

```js
function playSample(audioContext, audioBuffer, time) {
  const sampleSource = audioContext.createBufferSource();
  sampleSource.buffer = audioBuffer;
  sampleSource.connect(audioContext.destination);
  sampleSource.start(time);
  return sampleSource;
}
```

**참고:** 우리는 `AudioBufferSourceNode`의 `stop()`을 호출할 수 있지만, 이것은 샘플이 재생을 마쳤을 때 자동적으로 발생할 것입니다.

### 다이얼 음 슬라이더

`AudioBufferSourceNode`는 `playbackRate` (en-US) 속성을 가지고 있습니다. 우리가 샘플의 속도를 올리고 내릴 수 있도록, 이것을 UI에 드러내 봅시다. 우리는 이전에 한 것과 같은 종류의 방법으로 이것을 할 것입니다:

```html
<label for="rate">Rate</label>
<input
 name="rate"
 id="rate"
 type="range"
 min="0.1"
 max="2"
 value="1"
 step="0.1" />
```

```js
let playbackRate = 1;
const rateControl = document.querySelector("#rate");
rateControl.addEventListener(
  "input",
  function () {
    playbackRate = Number(this.value);
  },
  false,
);
```

### 최종 playSample() 함수

그리고 나서 우리는 `playbackRate` 속성을 `playSample()` 함수에 업데이트하기 위한 라인을 추가할 것입니다. 최종 버전은 다음과 같습니다:

```js
function playSample(audioContext, audioBuffer, time) {
  const sampleSource = audioContext.createBufferSource();
  sampleSource.buffer = audioBuffer;
  sampleSource.playbackRate.value = playbackRate;
  sampleSource.connect(audioContext.destination);
  sampleSource.start(time);
  return sampleSource;
}
```

**참고:** 이 사운드 파일의 출처는 soundbible.com입니다.

시간에 맞춰 오디오 재생하기
---------------

디지털 오디오 애플리케이션의 일반적인 문제는 사운드를 비트가 일정하게 유지되고, 시간에서 벗어나지 않도록, 제 시간에 재생하는 것입니다.

우리는 `for` 반복문 내에서 소리가 재생되도록 스케쥴할 수 있지만, 이것의 가장 큰 문제는 이것이 재생되는 도중에 업데이트되고, 우리는 이미 이것을 하기 위한 UI를 구현했다는 것입니다. 또한, 악기 전체의 BPM 슬라이더를 고려하는 것은 정말 좋을 것입니다. 비트에 맞춰 소리를 재생시키는 가장 좋은 방법은 우리가 노드가 언제 재생될지 내다보고 그것들을 큐에 넣는 스케쥴링 시스템을 만드는 것입니다. 우리는 currentTime 속성으로 정밀한 시간에서 이것들을 시작시킬 수 있고 또한 어떠한 변화도 고려할 수 있습니다.

**참고:** 이것은 Chris Wilson의 A Tale Of Two Clocks 글의 가장 기본적인 것만 남긴 버전인데, 저 글은 이 방법을 더욱 자세하게 다룹니다. 이것을 모두 여기서 반복할 필요는 없지만, 저 글을 읽고 이 방법을 사용하는 것을 대단히 추천합니다. 여기서의 대부분의 코드는 그의 메트로놈 예제에서 가져온 것인데, 이 예제는 그가 저 글에서 언급했습니다.

또한 — 추측하셨다시피 — 유저가 또 다른 range 입력을 통해 제어할 수 있는 기본 BPM (beats per minute)을 설정하면서 시작해 봅시다.

```js
let tempo = 60.0;
const bpmControl = document.querySelector("#bpm");
bpmControl.addEventListener(
  "input",
  function () {
    tempo = Number(this.value);
  },
  false,
);
```

그리고 나서 우리는 얼마나 미리 우리가 내다보기를 원하는지, 그리고 얼마나 미리 우리가 스케쥴하기를 원하는지를 정의하는 변수를 생성할 것입니다:

```js
const lookahead = 25.0; // 얼마나 자주 스케쥴링 함수를 호출할 것인지 (밀리세컨드로)
const scheduleAheadTime = 0.1; // 얼마나 미리 오디오를 스케쥴할지 (초)
```

한 박마다 노트를 앞으로 이동시키고, 박자가 4번째 (마지막)에 도달했을 때 첫번째로 반복해 돌아오는 함수를 생성해 봅시다:

```js
let currentNote = 0;
let nextNoteTime = 0.0; // 다음 노트가 예정되어 있을 때

function nextNote() {
  const secondsPerBeat = 60.0 / tempo;

  nextNoteTime += secondsPerBeat; // 박자 시간을 유지하기 위해 비트 길이를 추가

  // 박자 숫자를 진행시키고, 0으로 만듭니다
  currentNote++;
  if (currentNote === 4) {
    currentNote = 0;
  }
}
```

우리는 재생될 노트에 대한 참조 큐와, 이전에 우리가 만든 함수들을 사용해 그것들을 재생할 기능을 만들기를 원합니다:

```js
const notesInQueue = [];

function scheduleNote(beatNumber, time) {
  // 우리가 재생 중이 아닐지라도, 노트를 큐에 푸시합니다.
  notesInQueue.push({ note: beatNumber, time: time });

  if (
    pads[0]
      .querySelectorAll("button")
      [beatNumber].getAttribute("aria-checked") === "true"
  ) {
    playSweep(time);
  }
  if (
    pads[1]
      .querySelectorAll("button")
      [beatNumber].getAttribute("aria-checked") === "true"
  ) {
    playPulse(time);
  }
  if (
    pads[2]
      .querySelectorAll("button")
      [beatNumber].getAttribute("aria-checked") === "true"
  ) {
    playNoise(time);
  }
  if (
    pads[3]
      .querySelectorAll("button")
      [beatNumber].getAttribute("aria-checked") === "true"
  ) {
    playSample(audioCtx, dtmf, time);
  }
}
```

여기서 우리는 현재 시간을 보고 다음 노트에 대한 시간과 비교합니다; 두 개가 일치할 때 이것은 이전의 두 함수를 호출할 것입니다.

`AudioContext` 객체 인스턴스는 `currentTime` (en-US) 속성을 가지고 있는데, 이는 우리가 처음 컨텍스트를 생성한 이후의 초를 얻을 수 있게 합니다. 이것은 우리가 우리의 스텝 시퀸서 내에서 타이밍을 위해 사용해야만 하는 것입니다 — 약 소수 15자리까지의 정확한 float 값을 반환하는 이것은 극도로 정확합니다.

```js
function scheduler() {
  // 다음 구간 전에 재생할 필요가 있을 노트가 있는 동안, 그것들을 스케쥴하고 포인터를 진행시킵니다.
  while (nextNoteTime < audioCtx.currentTime + scheduleAheadTime) {
    scheduleNote(currentNote, nextNoteTime);
    nextNote();
  }
  timerID = window.setTimeout(scheduler, lookahead);
}
```

언제 비트가 진행하는지를 볼 수 있도록 우리는 또한 UI를 업데이트할 그리기 함수가 필요합니다.

```js
let lastNoteDrawn = 3;

function draw() {
  let drawNote = lastNoteDrawn;
  let currentTime = audioCtx.currentTime;

  while (notesInQueue.length && notesInQueue[0].time < currentTime) {
    drawNote = notesInQueue[0].note;
    notesInQueue.splice(0, 1); // 노트를 큐에서 제거합니다
  }

  // 우리는 오직 노트가 이동되었을 경우 그릴 필요가 있습니다.
  if (lastNoteDrawn != drawNote) {
    pads.forEach(function (el, i) {
      el.children[lastNoteDrawn].style.borderColor = "hsla(0, 0%, 10%, 1)";
      el.children[drawNote].style.borderColor = "hsla(49, 99%, 50%, 1)";
    });

    lastNoteDrawn = drawNote;
  }
  // 다시 그리기 위해 설정됨
  requestAnimationFrame(draw);
}
```

종합하기
----

이제 남은 것은 우리가 악기를 **재생**할 수 있기 전에 샘플이 로드된 것을 확실히 하는 것입니다. 우리는 파일이 가져와지고 디코드되었을 때 사라지는 로딩 스크린을 추가할 것이고, 그리고 나서 우리는 재생 버튼 클릭 이벤트를 사용해 스케쥴러가 시작되도록 할 수 있습니다.

```js
// 로드된 샘플이 재생을 허용했을 때
let loadingEl = document.querySelector(".loading");
const playButton = document.querySelector("[data-playing]");
let isPlaying = false;
setupSample().then((sample) => {
  loadingEl.style.display = "none"; // 로딩 스크린 제거

  dtmf = sample; // 우리의 playSample 함수에서 사용될 것임

  playButton.addEventListener("click", function () {
    isPlaying = !isPlaying;

    if (isPlaying) {
      // 재생 시작

      // 컨텍스트가 연기(suspended) 상태인지 확인 (자동 재생 정책)
      if (audioCtx.state === "suspended") {
        audioCtx.resume();
      }

      currentNote = 0;
      nextNoteTime = audioCtx.currentTime;
      scheduler(); // 스케쥴링 시작
      requestAnimationFrame(draw); // 드로잉 루프 시작.
      this.dataset.playing = "true";
    } else {
      window.clearTimeout(timerID);
      this.dataset.playing = "false";
    }
  });
});
```

요약
--

우리는 이제 브라우저 안에 악기를 가지고 있습니다! 계속 재생하고 실험해 보세요 — 여러분은 무언가 더욱 복잡한 것을 만들기 위해 이 모든 기법을 확장할 수 있습니다.


IIR 필터 사용하기 - Web API

IIR 필터 사용하기
===========

Web Audio API의 **`IIRFilterNode`** 인터페이스는 일반적인 무한 임펄스 응답 (IIR) 필터를 구현하는 `AudioNode` 프로세서입니다; 이 유형의 필터는 음색 제어 장치와 그래픽 이퀄라이저를 구현하는 데 사용될 수 있으며, 필터 응답 파라미터들은 필요하면 조율될 수 있도록 명시될 수 있습니다. 이 글은 어떻게 이것을 구현하는지, 그리고 간단한 예제로 어떻게 이것을 사용하는지를 살펴봅니다.

데모
--

이 가이드를 위한 간단한 예제는 오디오 재생을 시작하거나 멈추는 재생/정지 버튼과, 사운드의 음색을 변경하는 IIR 필터를 켜고 끄는 토글 버튼을 제공합니다. 이것은 또한 오디오의 주파수 응답이 그려진 캔버스를 제공하므로, 여러분은 IIR 필터가 어떤 이펙트를 가지고 있는지를 볼 수 있습니다.

![재생 버튼, 필터를 켜고 끄기 위한 토글 버튼, 필터가 적용된 이후에 반환된 필터 주파수들을 보여주는 선 그래프 기능을 가진 데모](/ko/docs/Web/API/Web_Audio_API/Using_IIR_filters/iir-filter-demo.png)

여러분은 Codepen에서 전체 데모를 확인할 수 있습니다. GitHub에서 소스 코드도 볼 수 있습니다. 이것은 각각 다른 로우패스 주파수들에 대한 몇 가지 다른 계수 값들을 포함합니다 — 여러분은 각각 다른 이용 가능한 이펙트들을 확인해보기 위해 `filterNumber` 상수의 값을 0에서 3사이의 값으로 변경할 수 있습니다.

브라우저 지원
-------

비록 IIR 필터가 Biquad 필터같은, 더욱 오랫동안에 걸친 Web Audio API 기능들 중 몇 가지보다 더욱 최근에 구현되긴 했지만, IIR 필터 (en-US)는 현대 브라우저들에서 잘 지원됩니다.

IIRFilterNode
-------------

Web Audio API는 이제 `IIRFilterNode` (en-US) 인터페이스를 가지고 있습니다. 하지만 이것은 무엇이고 어떻게 이것이 우리가 이미 가지고 있는 `BiquadFilterNode`와 다를까요?

IIR 필터는 **무한 임펄스 응답 필터**입니다. 이것은 오디오와 디지털 신호 처리에서 사용되는 두 가지 주요한 유형의 필터 중 하나입니다. 다른 유형은 FIR — **유한 임펄스 응답 필터**입니다. 여기 IIR 필터와 FIR 필터에 대한 정말 좋은 개요가 있습니다.

biquad 필터는 실제로는 무한 임펄스 응답 필터의 *특정한 유형*입니다. 이것은 흔히 쓰이는 유형이고 우리는 이미 Web Audio API에 노드로 이것을 가지고 있습니다. 만약 여러분이 이 노드를 선택한다면 힘든 일들은 이미 여러분을 위해 완료되어 있습니다. 예를 들자면, 만약 여러분이 사운드에서 낮은 주파수들을 여과하기를 원한다면, 여러분은 type (en-US)을 `highpass`로 설정하고 어떤 주파수를 여과할지 (또는 잘라낼지) 설정할 수 있습니다. 여기 biquad 필터가 어떻게 작동하는지에 대한 더 많은 정보가 있습니다.

여러분이 `IIRFilterNode` (en-US)를 `BiquadFilterNode` 대신에 사용할 때 여러분은 미리 프로그램된 유형을 단지 선택하는 것 보다는 필터를 직접 만드는 것입니다. 그래서 여러분은 하이패스 필터, 또는 로우패스 필터, 또는 더욱 맞춤형의 필터를 만들 수 있습니다. 그리고 이것이 IIR 필터가 유용해지는 지점입니다 — 만약 이미 이용 가능한 세팅중 아무 것도 여러분이 원하는 것에 대해 적합하지 못하다면 여러분은 여러분만의 필터를 만들 수 있습니다. 이것 뿐만이 아니라, 만약 여러분의 오디오 그래프가 내부에 하이패스와 밴드패스 필터를 필요로 한다면, 여러분은 이것을 위해 필요할 두 개의 biquad 필터 노드 대신에 단지 하나의 IIR 필터 노드를 사용할 수 있습니다.

IIR 필터 노드와 함께 필터가 필요로 하는 어떤 `feedforward`와 `feedback` 값을 설정할지는 여러분에게 달렸습니다 — 이것은 필터의 특징을 결정합니다. 불리한 면은 이것은 몇 가지 복잡한 수학을 수반한다는 것입니다.

만약 여러분이 더 배우기를 원한다면 여기 몇 가지 IIR 필터 뒤의 수학들에 대한 정보가 있습니다. 이는 신호 처리 이론의 영역에 들어갑니다 — 보고 이것이 여러분을 위한 것이 아닌 것처럼 느껴지더라도 걱정하지는 마세요.

만약 여러분이 IIR 필터 노드를 가지고 놀기를 원하고 그러기 위해 몇몇 값들이 필요하다면, 여기 이미 계산된 값들의 표가 있습니다; 링크된 PDF의 페이지 4 & 5에서 a*n*값들은 `feedForward`값을 나타내고 b*n*값들은 `feedback`을 나타냅니다. 만약 여러분이 다른 필터들과 어떻게 그것들이 디지털적으로 구현되는지에 대해 더 읽기를 원한다면 musicdsp.org는 또한 훌륭한 자원입니다.

이것 모두를 염두에 둔 채로, Web Audio API로 IIR 필터를 만드는 코드를 살펴봅시다.

IIR 필터 계수 설정하기
--------------

IIR 필터를 만들 때, 우리는 `feedforward`와 `feedback` 계수를 옵션으로 (계수들은 우리가 값들을 표현하는 방법입니다) 전달합니다. 이 파라미터 둘 다는 배열이고, 둘 중 어느 것도 20개의 원소보다 클 수 없습니다.

계수를 설정할 때, `feedforward` 값들은 모두 0으로 설정될 수 없는데, 그렇지 않으면 아무 것도 필터로 전달되지 않을 것입니다. 이것과 같은 것은 가능합니다:

```js
let feedForward = [0.00020298, 0.0004059599, 0.00020298];
```

`feedback` 값들은 0으로 시작할 수 없는데, 그렇지 않으면 첫번째 전달에서 아무 것도 되돌아오지 않을 것입니다:

```js
let feedBackward = [1.0126964558, -1.9991880801, 0.9873035442];
```

**참고:** 이 값들은 Web Audio API 명세의 필터 특징들에서 명시된 로우패스 필터에 기반하여 계산된 값들입니다. 이 필터 노드가 더욱 많은 인기를 얻고 있으므로 우리는 더 많은 계수 값들을 모을 수 있을 것입니다.

오디오 그래프에서 IIR 필터 사용하기
---------------------

컨텍스트와 필터 노드를 만들어 봅시다:

```js
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();

const iirFilter = audioCtx.createIIRFilter(feedForward, feedBack);
```

우리는 재생할 음원이 필요합니다. 우리는 사용자 정의 함수인 `playSoundNode()`를 사용하여 이것을 설정하는데, 이 함수는 존재하는 `AudioBuffer`로부터 버퍼 소스를 만들고 (en-US), 이것을 기본 목적지에 부착하고, 재생을 시작시키고, 이것을 반환합니다.

```js
function playSourceNode(audioContext, audioBuffer) {
  const soundSource = audioContext.createBufferSource();
  soundSource.buffer = audioBuffer;
  soundSource.connect(audioContext.destination);
  soundSource.start();
  return soundSource;
}
```

이 함수는 재생 버튼이 눌러졌을 때 호출됩니다. 재생 버튼 HTML은 다음같이 생겼습니다:

```html
<button
 class="button-play"
 role="switch"
 data-playing="false"
 aria-pressed="false">
  Play
</button>
```

그리고 `click` 이벤트 리스너는 다음과 같이 시작합니다:

```js
playButton.addEventListener('click', function() {
    if (this.dataset.playing === 'false') {
        srcNode = playSourceNode(audioCtx, sample);
        ...
}, false);
```

IIR 필터를 켜고 끄는 토글 버튼은 비슷한 방식으로 설정됩니다. 우선, HTML은:

```html
<button
 class="button-filter"
 role="switch"
 data-filteron="false"
 aria-pressed="false"
 aria-describedby="label"
 disabled></button>
```

필터 버튼의 `click` 핸들러는 그리고 나서 `IIRFilter`를 그래프에 연결하는데, 이는 소스와 목적지 사이입니다:

```js
filterButton.addEventListener('click', function() {
    if (this.dataset.filteron === 'false') {
        srcNode.disconnect(audioCtx.destination);
        srcNode.connect(iirfilter).connect(audioCtx.destination);
        ...
}, false);
```

### 주파수 응답

우리는 `IIRFilterNode` (en-US) 인스턴스에서 이용 가능한 단 하나의 메서드 `getFrequencyResponse()`만을 가지고 있는데, 이는 필터 내로 전달되고 있는 오디오의 주파수에 무슨 일이 일어나는지 볼 수 있게 해 줍니다.

우리가 만든 필터의 주파수 플롯을 이 메서드로부터 얻은 데이터로 그려 봅시다.

우리는 세 개의 배열을 만들 필요가 있습니다. 규모 응답과 상(phase) 응답을 받기를 원하는 주파수 값의 배열 하나, 그리고 데이터를 받기 위한 두 개의 빈 배열입니다. 이 세 개 모두 `float32array` 유형이여야만 하고 같은 크기여야 합니다.

```js
// 주파수 응답에 대한 배열들
const totalArrayItems = 30;
let myFrequencyArray = new Float32Array(totalArrayItems);
let magResponseOutput = new Float32Array(totalArrayItems);
let phaseResponseOutput = new Float32Array(totalArrayItems);
```

첫번째 배열을 우리가 원하는 반환될 데이터인 주파수 값으로 채웁시다.

```js
myFrequencyArray = myFrequencyArray.map(function (item, index) {
  return Math.pow(1.4, index);
});
```

우리는 선형적인 접근을 택할 수도 있지만, 주파수를 가지고 작업할 때는 로그 접근을 취하는 것이 훨씬 나으므로, 배열 원소에서 더 커진 주파수 값들로 배열을 채웁시다.

이제 응답 데이터를 얻읍시다:

```js
iirFilter.getFrequencyResponse(
  myFrequencyArray,
  magResponseOutput,
  phaseResponseOutput,
);
```

우리는 이 데이터를 필터 주파수 플롯을 그리기 위해 사용할 수 있습니다. 우리는 2d 캔버스 컨텍스트에서 그렇게 할 것입니다.

```js
// 캔버스 요소를 만들고 dom에 추가합니다
const canvasContainer = document.querySelector(".filter-graph");
const canvasEl = document.createElement("canvas");
canvasContainer.appendChild(canvasEl);

// 2d 컨텍스트를 설정하고 크기를 설정합니다
const canvasCtx = canvasEl.getContext("2d");
const width = canvasContainer.offsetWidth;
const height = canvasContainer.offsetHeight;
canvasEl.width = width;
canvasEl.height = height;

// 배경을 채울 색상을 설정합니다
canvasCtx.fillStyle = "white";
canvasCtx.fillRect(0, 0, width, height);

// 크기에 맞춰 여백을 설정합니다
const spacing = width / 16;
const fontSize = Math.floor(spacing / 1.5);

// 축을 그립니다
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "grey";

canvasCtx.beginPath();
canvasCtx.moveTo(spacing, spacing);
canvasCtx.lineTo(spacing, height - spacing);
canvasCtx.lineTo(width - spacing, height - spacing);
canvasCtx.stroke();

// 축은 gain(y축)과 주파수(x축)입니다 -> 라벨을 만듭니다
canvasCtx.font = fontSize + "px sans-serif";
canvasCtx.fillStyle = "grey";
canvasCtx.fillText("1", spacing - fontSize, spacing + fontSize);
canvasCtx.fillText("g", spacing - fontSize, (height - spacing + fontSize) / 2);
canvasCtx.fillText("0", spacing - fontSize, height - spacing + fontSize);
canvasCtx.fillText("Hz", width / 2, height - spacing + fontSize);
canvasCtx.fillText("20k", width - spacing, height - spacing + fontSize);

// 규모 응답 데이터를 순회하고 필터를 그립니다

canvasCtx.beginPath();

for (let i = 0; i < magResponseOutput.length; i++) {
  if (i === 0) {
    canvasCtx.moveTo(spacing, height - magResponseOutput[i] \* 100 - spacing);
  } else {
    canvasCtx.lineTo(
      (width / totalArrayItems) \* i,
      height - magResponseOutput[i] \* 100 - spacing,
    );
  }
}

canvasCtx.stroke();
```

요약
--

이것이 IIR 필터 데모의 전부입니다. 이것은 어떻게 기본을 사용하는지를 보여주고, 왜 이것이 유용한지와 어떻게 이것이 작동하는지를 이해하는 것을 도와주었을 것입니다.


ConstantSourceNode로 다수의 파라미터 제어하기 - Web API

ConstantSourceNode로 다수의 파라미터 제어하기
=================================

이 글은 다수의 파라미터가 같은 값을 공유하도록 파라미터들을 함께 연결하기 위해 어떻게 `ConstantSourceNode` (en-US)를 사용하는지를 설명합니다. 이 값은 `ConstantSourceNode.offset` (en-US) 파라미터의 값을 설정함으로써 변경될 수 있습니다.

간혹 다수의 오디오 파라미터들이 같은 값을 공유할 수 있도록, 심지어 값이 어떤 방법으로 변경되는 동안일지라도, 연결시켜두고 싶을 때가 있습니다. 예를 들자면, 한 세트의 오실레이터들이 있고, 이 두 개가 하나의 설정 가능한 볼륨을 공유할 필요가 있거나, 특정한 입력에 적용되나 모든 입력에는 적용되지 않는 필터가 있을 수도 있습니다. 반복문을 사용하여 각각의 영향받는 `AudioParam`의 값을 한번에 하나씩 변경할 수도 있겠지만, 여기에는 두 가지 문제점이 있습니다. 첫째는, 곧 보게 되겠지만, 작성할 필요가 없는 추가적인 코드의 작성입니다. 그리고 둘째는, 그 반복은 스레드 (메인 스레드일 가능성이 높음) 의 소중한 CPU 시간을 사용합니다. 이런 작업을 이에 최적화되어 있으며 여러분의 코드보다 더욱 적합한 우선도에서 실행할 수 있는 오디오 렌더링 스레드에 넘길 수 있는 방법이 있습니다.

그 해결책이란 간단한데, 바로 첫눈에는 그다지 유용해 보이지 않는 오디오 노드 유형 `ConstantSourceNode` (en-US)을 사용하는 것입니다.

기법
--

이 방법은 듣기엔 어려워 보이지만 사실은 굉장히 쉽습니다. `ConstantSourceNode` (en-US)를 생성하고 항상 일치하는 값을 갖도록 연결된 모든 `AudioParam`에 이 노드를 연결합니다. `ConstantSourceNode`의 `offset` (en-US) 값은 모든 출력으로 바로 전달되므로, 이 노드는 그 값을 각각의 연결된 파라미터에 보내는 스플리터 역할을 합니다.

아래의 그림은 이 과정이 어떻게 작동하는지 보여줍니다. 입력값 `N`은 `ConstantSourceNode.offset` (en-US) 속성의 값으로 설정됩니다. `ConstantSourceNode`는 필요한 만큼 많은 출력을 가질 수 있습니다. 아래 그림의 경우, `ConstantSourceNode`를 세 개의 노드에 연결했습니다. 두 개의 `GainNode` (en-US)와 `StereoPannerNode` (en-US)입니다. 그래서 `N`은 명시된 파라미터의 값이 됩니다 (`GainNode` (en-US)의 `gain` (en-US), `StereoPannerNode` (en-US)의 pan).

![어떻게 ConstantSourceNode가 입력 매개변수를 다수의 노드에 공유하기 위해 사용될 수 있는지를 보여주는 SVG 그림.](/ko/docs/Web/API/Web_Audio_API/Controlling_multiple_parameters_with_ConstantSourceNode/customsourcenode-as-splitter.svg)

그 결과로써, `N` (입력 `AudioParam`의 값) 이 바뀔 때마다, 두 개의 `GainNode`의 `gain` 속성의 값 그리고 `StereoPannerNode`의 `pan` 속성의 값은 모두 `N`으로 또한 설정됩니다.

예제
--

실제 사용 예제를 살펴 봅시다. 이 간단한 예제에서는 세 개의 `OscillatorNode` (en-US)를 만들었습니다. 두 개는 공유된 입력 슬라이더를 사용해 제어되는 조정 가능한 gain을 가지고 있습니다. 나머지 오실레이터 하나는 고정된 볼륨을 가지고 있습니다.

### HTML

이 예제의 HTML 코드는 주로 오실레이터의 음색을 켜고 끄기 위한 버튼과 세 개 중 두 개의 오실레이터의 볼륨을 조절하기 위한 `range` type의 `<input>` (en-US) 요소입니다.

```html
<div class="controls">
  <div class="left">
    <div id="playButton" class="button">▶</div>
  </div>
  <div class="right">
    <span>Volume: </span>
    <input
 type="range"
 min="0.0"
 max="1.0"
 step="0.01"
 value="0.8"
 name="volume"
 id="volumeControl" />
  </div>
</div>

<p>
  음을 재생하고 정지하기 위해 위의 버튼을, 그리고 화음 E와 G의 볼륨을 변경하기
  위해 볼륨 슬라이더를 사용하세요.
</p>
```

```
.controls {
  width: 400px;
  position: relative;
  vertical-align: middle;
  height: 44px;
}

.button {
  font-size: 32px;
  cursor: pointer;
  user-select: none;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
}

.right {
  width: 50%;
  font:
    14px "Open Sans",
    "Lucida Grande",
    "Arial",
    sans-serif;
  position: absolute;
  right: 0;
  display: table-cell;
  vertical-align: middle;
}

.right span {
  vertical-align: middle;
}

.right input {
  vertical-align: baseline;
}

.left {
  width: 50%;
  position: absolute;
  left: 0;
  display: table-cell;
  vertical-align: middle;
}

.left span,
.left input {
  vertical-align: middle;
}
```

### JavaScript

이제 JavaScript 코드를 부분별로 살펴 봅시다.

#### 설정하기

전역 변수 초기화부터 시작해 봅시다.

```js
let context = null;

let playButton = null;
let volumeControl = null;

let oscNode1 = null;
let oscNode2 = null;
let oscNode3 = null;
let constantNode = null;
let gainNode1 = null;
let gainNode2 = null;
let gainNode3 = null;

let playing = false;
```

이 변수들은 다음과 같습니다.

`context`

모든 오디오 노드들이 있는 `AudioContext`.

`playButton`과 `volumeControl`

재생 버튼과 볼륨 제어 요소에 대한 참조.

`oscNode1`, `oscNode2`, `oscNode3`

화음을 생성하기 위해 쓰이는 세 개의 `OscillatorNode` (en-US).

`gainNode1`, `gainNode2`, `gainNode3`

세 개의 각 오실레이터에 대해 볼륨 레벨을 제공하는 세 개의 `GainNode` (en-US) 인스턴스. `gainNode2`와 `gainNode3`은 `ConstantSourceNode` (en-US)를 사용해 같은 조정 가능한 값을 가지기 위해 함께 연결될 것입니다.

`constantNode`

`gainNode2`와 `gainNode3`의 값을 함게 제어하기 위해 쓰이는 `ConstantSourceNode` (en-US).

`playing`

현재 음을 재생하고 있는지 아닌지를 추적하기 위해 사용할 `Boolean`.

이제 `setup()` 함수를 살펴봅시다. 이 함수는 window의 `load` 이벤트에 대한 이벤트 처리기(handler)입니다. 이것은 DOM이 준비되기 위한 모든 초기화 작업을 다룹니다.

```js
function setup() {
  context = new (window.AudioContext || window.webkitAudioContext)();

  playButton = document.querySelector("#playButton");
  volumeControl = document.querySelector("#volumeControl");

  playButton.addEventListener("click", togglePlay, false);
  volumeControl.addEventListener("input", changeVolume, false);

  gainNode1 = context.createGain();
  gainNode1.gain.value = 0.5;

  gainNode2 = context.createGain();
  gainNode3 = context.createGain();
  gainNode2.gain.value = gainNode1.gain.value;
  gainNode3.gain.value = gainNode1.gain.value;
  volumeControl.value = gainNode1.gain.value;

  constantNode = context.createConstantSource();
  constantNode.connect(gainNode2.gain);
  constantNode.connect(gainNode3.gain);
  constantNode.start();

  gainNode1.connect(context.destination);
  gainNode2.connect(context.destination);
  gainNode3.connect(context.destination);
}

window.addEventListener("load", setup, false);
```

먼저, window의 `AudioContext`에 대한 접근을 얻고, 이 참조를 `context` 변수에 저장합니다. 그리고 나서 `playButton`에 재생 버튼에 대한 참조와 `volumeControl`에 사용자가 연결된 오실레이터 쌍의 gain을 조정하기 위해 사용할 슬라이더에 대한 참조를 설정하며 제어 위젯에 대한 참조를 얻습니다.

그리고 나서 재생 버튼의 `click` 이벤트와 볼륨 슬라이더의 `input (en-US)` 이벤트에 이벤트 처리기를 부착합니다 (`togglePlay()` 메서드에 대해 알아보려면 오실레이터 켜고 끄기를, 아주 짧은 `changeVolume()` 메서드를 살펴보려면 연결된 오실레이터 제어하기를 참고하세요).

다음으로, `GainNode` (en-US) `gainNode1`은 연결되지 않은 오실레이터의 볼륨을 다루기 위해 생성됩니다 (`oscNode1`). 이 gain은 0.5로 설정합니다. 또한 `gainNode2`와 `gainNode3`를 생성하고 이들의 값을 `gainNode1`의 값과 동일하게 설정합니다. 그 후, 볼륨 슬라이더의 값을 같은 값으로 설정해 이 값이 슬라이더가 제어하는 gain 레벨과 동기화되도록 합니다.

모든 gain 노드가 생성되고 나면, `ConstantSourceNode` (en-US), `constantNode`를 생성합니다. 이 노드의 출력을 `gainNode2`와 `gainNode3` 둘 다의 `gain` `AudioParam`에 연결하고, `start()` (en-US) 메서드를 호출해 constant 노드 실행을 시작합니다. 이제 이 노드는 두 개의 gain 노드의 값에 값 0.5를 전달하고, `constantNode.offset` (en-US)에서의 모든 변화는 자동적으로 `gainNode2`와 `gainNode3` 둘 다의 gain을 설정할 것입니다 (예상한 대로 그들의 오디오 입력에 영향을 미칩니다).

마지막으로, gain 노드에 전달되는 모든 소리가, 출력이 스피커이든, 헤드폰이든, 녹음 스트림이든, 또는 어떠한 다른 destination 유형이든 간에 출력에 도달하도록 모든 gain 노드를 `AudioContext`의 `destination` (en-US)에 연결합니다.

window의 `load` 이벤트 처리기를 `setup()` 함수에 설정하면, 무대는 준비 완료되었습니다. 어떻게 작동되는지 살펴봅시다.

#### 오실레이터 켜고 끄기

`OscillatorNode` (en-US)는 정지 상태를 지원하지 않기 때문에, 오실레이터를 다시 켜기 위해서 재생 버튼을 클릭했을 때 오실레이터를 제거하고 다시 시작시킴으로써 정지 상태를 흉내내야 합니다. 코드를 살펴봅시다.

```js
function togglePlay(event) {
  if (playing) {
    playButton.textContent = "▶";
    stopOscillators();
  } else {
    playButton.textContent = "⏸";
    startOscillators();
  }
}
```

만약 `playing` 변수가 이미 오실레이터가 재생되고 있음을 나타낸다면, `playButton`의 내용을 유니코드 문자 "오른쪽을 가리키는 삼각형" (▶)으로 변경하고 오실레이터를 끄기 위해 `stopOscillators()`를 호출합니다. 이 코드에 대해 아래의 오실레이터 멈추기를 참고하세요.

만약 `playing`이 현재 멈춰 있음을 나타내는 false라면, 재생 버튼의 내용을 유니코드 문자 "정지 부호" (⏸)로 변경하고 오실레이터가 음을 재생하도록 `startOscillators()`를 호출합니다. 이 코드는 아래의 오실레이터 재생하기에서 다뤄집니다.

#### 연결된 오실레이터 제어하기

연결된 오실레이터 쌍의 gain 슬라이더에 대한 이벤트 처리기인 `changeVolume()` 함수는 다음과 같이 생겼습니다.

```js
function changeVolume(event) {
  constantNode.offset.value = volumeControl.value;
}
```

이 간단한 함수는 두 노드의 gain을 제어합니다. 우리가 해야 하는 것은 `ConstantSourceNode` (en-US)의 `offset` (en-US) 파라미터의 값을 설정하는 것 뿐입니다. 이 값은 노드의 상수 출력 값이 되는데, 이는 노드의 모든 출력에 전달되고, 이 출력이라 함은, 위에서 설정되었다시피 `gainNode2`와 `gainNode3`입니다.

이것이 아주 간단한 예제이긴 하지만, 다수의 연결된 노드에서 재생되는 다수의 연결된 파라미터를 가진 32개의 오실레이터 신시사이저를 상상해 보십시오. 그것들 모두를 조정하기 위해 연산의 수를 줄일 수 있음은 코드의 크기와 성능 양 쪽 모두에 대해 매우 유용함을 증명할 것입니다.

#### 오실레이터 재생하기

오실레이터가 재생 중이 아닌 도중에 사용자가 재생/정지 토글 버튼을 클릭했을 때, `startOscillators()` 함수가 호출됩니다.

```js
function startOscillators() {
  oscNode1 = context.createOscillator();
  oscNode1.type = "sine";
  oscNode1.frequency.value = 261.625565300598634; // 중앙 C
  oscNode1.connect(gainNode1);

  oscNode2 = context.createOscillator();
  oscNode2.type = "sine";
  oscNode2.frequency.value = 329.627556912869929; // E
  oscNode2.connect(gainNode2);

  oscNode3 = context.createOscillator();
  oscNode3.type = "sine";
  oscNode3.frequency.value = 391.995435981749294; // G
  oscNode3.connect(gainNode3);

  oscNode1.start();
  oscNode2.start();
  oscNode3.start();

  playing = true;
}
```

세 개의 각 오실레이터는 같은 방식으로 설정됩니다.

1. `BaseAudioContext.createOscillator` (en-US)를 호출함으로써 `OscillatorNode` (en-US)를 생성합니다.
2. 오디오 파형으로써 사인파를 사용하기 위해 오실레이터의 type을 `"sine"`으로 설정합니다.
3. 오실레이터의 주파수를 원하는 값으로 설정합니다. 이 경우, `oscNode1`는 중앙 C로, `oscNode2`와 `oscNode3`는 E와 G음을 재생함으로써 화음을 완성합니다.
4. 새로운 오실레이터를 해당하는 gain 노드에 연결합니다.

세 개의 모든 오실레이터가 완성되고 나면, 이것들은 각각의 `ConstantSourceNode.start()` (en-US) 메서드를 차례로 호출함으로써 시작되고, `playing`은 음이 재생되는 것을 추적하기 위해 `true`로 설정됩니다.

#### 오실레이터 멈추기

음을 정지하기 위해 사용자가 재생 상태를 바꿨을 때 오실레이터를 멈추는 것은 각각의 노드를 정지하는 것과 마찬가지로 쉽습니다.

```js
function stopOscillators() {
  oscNode1.stop();
  oscNode2.stop();
  oscNode3.stop();
  playing = false;
}
```

각 노드는 `ConstantSourceNode.stop()` (en-US) 메서드를 호출함으로써 정지되고, `playing`는 `false`로 설정됩니다.

### 결과

같이 보기
-----

* Web Audio API
* Web Audio API 사용하기
* 간단한 신시사이저 키보드 (예제)
* `OscillatorNode` (en-US)
* `ConstantSourceNode` (en-US)


Web Audio API의 기본 개념 - Web API

Web Audio API의 기본 개념
====================

오디오가 어떻게 여러분의 앱을 통해서 전송(route)되는지를 설계하는 동안 여러분이 적절한 결정을 내리는 것을 돕기 위해, 이 문서는 Web Audio API의 기능이 어떻게 동작하는가를 뒷받침하는 얼마간의 오디오 이론을 설명합니다. 이 문서를 읽는다고 해서 여러분이 숙련된 사운드 엔지니어가 될 수는 없지만, 왜 Web Audio API가 이렇게 동작하는지를 이해하기에 충분한 배경지식을 줄 것입니다.

오디오 그래프
-------

Web Audio API는 오디오 컨텍스트(audio context) 내의 오디오 연산을 다루는 것을 포함하고, **모듈러 라우팅**(modular routing)을 허용하도록 설계되었습니다. 기본적인 오디오 연산은 **오디오 노드**(audio node)와 함께 수행되는데, 이는 **오디오 라우팅 그래프**를 형성하기 위해 함께 연결되어 있습니다. 다른 유형의 채널 레이아웃을 가진 몇몇의 소스(source)들은 심지어 하나의 컨텍스트 내에서 지원됩니다. 이 모듈식의(modular) 디자인은 역동적인 효과를 가진 복잡한 오디오 기능을 만드는 데 있어 유연함을 제공합니다.

하나 또는 더 많은 소스에서 시작하고, 하나 또는 더 많은 노드를 통과하고, 그리고서 도착지(destination)에서 끝나는 체인(chain)을 형성하며, 오디오 노드는 입력과 출력을 통해 연결되어 있습니다. 그러나, 예를 들어 여러분이 단지 오디오 데이터를 시각화하기를 원한다면 도착지를 반드시 제공할 필요는 없습니다. 웹 오디오의 단순하고, 일반적인 작업 흐름은 다음과 같습니다:

1. 오디오 컨텍스트를 생성합니다.
2. 컨텍스트 내에서, 다음과 같이 소스를 생성합니다 — `<audio>` (en-US), oscillator, 또는 stream.
3. 효과 노드를 생성하는데, 예를 들자면 reverb, biquad filter, panner, 또는 compressor가 있습니다.
4. 사용자의 컴퓨터 스피커와 같이, 오디오의 최종 도착지를 선택합니다.
5. 오디오 소스로부터 0 또는 더 많은 효과를 거쳐 연결(connection)을 확립하는데, 마지막으로는 앞서 선택된 도착지에서 끝납니다.

**참고:** 한 신호에서 사용 가능한 오디오 채널의 숫자는 종종 숫자 형식으로 표현되는데, 예를 들자면 2.0 또는 5.1과 같습니다. 이것은 채널 표기법이라고 불립니다. 첫번째 숫자는 신호가 포함하는 전체 주파수 범위 오디오 채널의 숫자입니다. 마침표 뒤의 숫자는 저주파 효과(LFE) 출력에 대해 비축된 채널의 수를 나타냅니다; 이 숫자는 종종 **서브 우퍼**(subwoofer)로 불립니다.

![오디오 컨텍스트라고 써진 외부 상자와 소스, 효과, 목적지라고 써진 세 개의 내부 상자를 가진 하나의 간단한 도표. 세 개의 내부 상자는 좌에서 우를 향하는 화살표를 사이에 가지고 있는데, 이는 오디오 정보의 흐름을 나타냅니다.](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/webaudioapi_en.svg)

각각의 입력 또는 출력은 몇몇의 **채널**으로 구성되어 있는데, 이는 특정한 오디오 레이아웃을 나타냅니다. *모노*, *스테레오*, *quad*, *5.1* 등등을 포함하는, 어떠한 별개의 채널 구조든 지원됩니다.

![입력과 출력 그리고 이 입력/출력 내부의 채널을 통해 연결하는 AudioNode의 능력을 보여줍니다.](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/mdn.png)

오디오 소스는 다양한 방법으로 얻어질 수 있습니다:

* 소리는 JavaScript에서 (oscillator처럼) 오디오 노드에 의해 직접적으로 생성될 수 있습니다.
* 가공되지 않은(raw) PCM 데이터로부터 생성될 수 있습니다 (오디오 컨텍스트는 지원되는 오디오 포맷을 디코드하는 메서드를 가지고 있습니다).
* (`<video>` 또는 `<audio>` (en-US)처럼) HTML 미디어 요소로부터 취해질 수 있습니다.
* (웹캠이나 마이크처럼) WebRTC `MediaStream` (en-US)로부터 직접적으로 취해질 수 있습니다.
오디오 데이터: 무엇이 샘플 속에 들어있는가
------------------------

오디오 신호가 처리될 때, **샘플링**이란 연속 신호(continuous signal)의 불연속 신호(discrete signal)로의 전환을 의미합니다; 또는 달리 말하면, 라이브로 연주하고 있는 밴드와 같이, 연속적인 음파를 컴퓨터가 오디오를 구별되는 단위로 다룰 수 있게 허용하는 일련의 샘플들로 전환하는 것을 의미합니다.

더 많은 정보는 위키피디아 문서 샘플링 (신호 처리)에서 찾을 수 있습니다.

오디오 버퍼: 프레임, 샘플, 그리고 채널
-----------------------

`AudioBuffer`는 매개변수로서 채널의 수 (1은 모노, 2는 스테레오 등), 버퍼 내부의 샘플 프레임의 수를 의미하는 길이, 그리고 초당 재생되는 샘플 프레임의 수인 샘플 레이트를 취합니다.

샘플은 특정한 채널(스테레오의 경우, 왼쪽 또는 오른쪽)에서, 각각의 특정한 시점에의 오디오 스트림의 값을 표현하는 단일의 float32 값입니다. 프레임 또는 샘플 프레임은, 특정한 시점에 재생될 모든 채널의 모든 값들의 집합입니다: 즉 같은 시간에 재생되는 모든 채널의 모든 샘플 (스테레오 사운드의 경우 2개, 5.1의 경우 6개 등)입니다.

샘플 레이트는 Hz로 측정되는, 1초에 재생될 이 샘플들 (또는 프레임들, 왜냐하면 한 프레임의 모든 샘플들이 같은 시간에 재생되므로) 의 수입니다. 샘플 레이트가 높을수록 음질이 더 좋습니다.

모노와 스테레오 오디오 버퍼를 살펴봅시다, 각각 1초 길이고, 44100Hz로 재생됩니다:

* 모노 버퍼는 44100 샘플과, 44100 프레임을 가질 것입니다. `length` 프로퍼티는 44100이 될 것입니다.
* 스테레오 버퍼는 88200 샘플을 가질 것이나, 여전히 44100 프레임입니다. `length` 프로퍼티는 프레임의 수와 동일하므로 여전히 44100일 것입니다.

![긴 줄에서 오디오 버퍼의 몇몇 프레임을 보여주는 도표인데, 각각은 두 개의 샘플을 포함하고 있습니다. 버퍼가 두 개의 채널을 가지고 있으므로, 이것은 스테레오입니다.](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/sampleframe-english.png)

버퍼가 재생될 때, 여러분은 제일 왼쪽의 샘플 프레임을 들을 것이고, 그리고서 다음에 있는 제일 오른쪽의 샘플 프레임 등등을 들을 것입니다. 스테레오의 경우에, 여러분은 양 채널을 동시에 들을 것입니다. 샘플 프레임은 대단히 유용한데, 왜냐하면 샘플 프레임은 채널의 수에 독립적이고, 정밀한 오디오 조작을 함에 있어 유용한 방법으로 시간을 나타내기 때문입니다.

**참고:** 프레임 카운트로부터 초로 시간을 얻기 위해서는, 프레임의 수를 샘플 레이트로 나누십시오. 샘플의 수로부터 프레임의 수를 얻기 위해서는, 채널 카운트로 나누십시오.

두 개의 간단한 예제입니다:

```js
var context = new AudioContext();
var buffer = context.createBuffer(2, 22050, 44100);
```

**참고:** 디지털 오디오에서, **44,100 Hz** (또한 **44.1 kHz**로 표현되어짐) 은 일반적인 샘플링 주파수입니다. 왜 44.1kHz일까요?

첫째로, 왜냐하면 인간의 가청 범위(hearing range)는 대략적으로 20 Hz에서 20,000 Hz이기 때문입니다. 표본화 정리(Nyquist–Shannon sampling theorem)에 의하여, 샘플링 주파수는 반드시 재생하기를 원하는 최대 주파수의 2배보다 커야 합니다. 그러므로, 샘플링 레이트는 40 kHz보다 커야만 합니다.

둘째로, 신호는 반드시 샘플링 전에 저주파 통과 필터(low-pass filter)를 거쳐야만 합니다, 그렇지 않으면 에일리어싱(aliasing)이 발생합니다. 이상적인 저주파 통과 필터는 완벽히 20 kHz 아래의 주파수들을 (약화시키는 일 없이) 통과시키고 완벽히 20 kHz 위의 주파수들을 잘라낼 것이지만, 실제로는 천이 대역(transition band)이 필수적인데, 여기서 주파수들은 부분적으로 약화됩니다. 천이 대역이 넓을수록, 주파수 중복방지 필터(anti-aliasing filter)를 만들기 쉽고 경제적입니다. 44.1 kHz 샘플링 주파수는 2.05 kHz 천이 대역을 감안합니다.

만약 위의 이 호출을 사용한다면, 여러분은 44100Hz (아주 일반적입니다, 대부분의 보통 사운드 카드는 이 레이트에서 실행됩니다) 에서 실행되는 AudioContext에서 재생될 때 0.5초동안 지속될 두 개의 채널을 가진 스테레오 버퍼를 얻을 것입니다. (22050 프레임 / 44100Hz = 0.5초)

```js
var context = new AudioContext();
var buffer = context.createBuffer(1, 22050, 22050);
```

만약 이 호출을 사용한다면, 여러분은 44100Hz에서 실행되는 AudioContext에서 재생될 때 자동적으로 44100Hz로 *리샘플*(resample)되고 1.0초동안 지속될 단지 하나의 채널을 가진 모노 버퍼를 얻을 것입니다. (44100 프레임 / 44100Hz = 1초)

**참고:** 오디오 리샘플링은 이미지 리사이징과 몹시 유사합니다. 예를 들어 여러분이 16 x 16 이미지를 가지고 있지만 32 x 32 영역을 채우고 싶다고 가정해 봅시다. 당신은 리사이즈 (또는 리샘플) 합니다. 결과는 더 낮은 품질을 가지지만 (리사이징 알고리즘에 따라서, 흐릿하거나 각질 수 있습니다), 리사이즈된 이미지가 더 적은 공간을 차지한 채로 작동은 합니다. 리샘플된 오디오는 정확히 동일합니다: 여러분은 공간을 저장하지만, 실제로는 높은 주파수의 콘텐츠 또는 고음의 소리를 적절히 재생할 수 없을 것입니다.

### 평면(planar) 대 인터리브(interleaved) 버퍼

Web Audio API는 평면 버퍼 포맷을 사용합니다. 왼쪽과 오른쪽 채널은 다음과 같이 저장됩니다:
```

LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (16 프레임의 버퍼에 대해)
```

이것은 오디오 프로세싱에서 아주 일반적입니다: 이것은 각 채널을 독립적으로 처리하기 쉽게 만들어줍니다.

대안은 인터리브 버퍼 포맷을 사용하는 것입니다:
```

LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (16 프레임의 버퍼에 대해)
```

이 포맷은 많은 프로세싱 없이 오디오를 저장하고 재생하는 데 아주 일반적인데, 예를 들자면 디코드된 MP3 스트림이 있습니다.

Web Audio API는 **오직** 평면 버퍼만을 드러내는데, 왜냐하면 프로세싱을 위해 만들어졌기 때문입니다. 이것은 평면으로 동작하나, 오디오가 재생을 위해 사운드 카드에 전달되었을 때 인터리브로 전환합니다. 역으로, MP3가 디코드되었을 때, 이것은 인터리브 포맷으로 시작하나, 프로세싱을 위해 평면으로 전환됩니다.

오디오 채널
------

다른 오디오 버퍼는 다른 수의 채널을 포함합니다: 간단한 모노(오직 한 개의 채널)와 스테레오(왼쪽과 오른쪽 채널)에서부터, 각 채널에 포함된 다른 사운드 샘플을 가지고 있어 더욱 풍부한 소리 경험을 가능케 하는 quad와 5.1과 같은 더욱 복잡한 것들까지 있습니다. 채널들은 보통 아래의 테이블에 상세히 설명된 표준 약어에 의해 표현됩니다:

| *Mono* | `0: M: mono` |
| --- | --- |
| *Stereo* | `0: L: left 1: R: right` |
| *Quad* | `0: L: left 1: R: right 2: SL: surround left 3: SR: surround right` |
| *5.1* | `0: L: left 1: R: right 2: C: center 3: LFE: subwoofer 4: SL: surround left 5: SR: surround right` |

### 업믹싱(up-mixing)과 다운믹싱(down-mixing)

채널의 수가 입력과 출력 사이에서 맞지 않을 때, 업 또는 다운 믹싱이 다음의 규칙에 따라 발생합니다. 이는 `AudioNode.channelInterpretation` 프로퍼티를 `speakers` 또는 `discrete`로 설정함으로써 어느 정도 제어될 수 있습니다.

| 해석 | 입력 채널 | 출력 채널 | 믹싱 규칙 |
| --- | --- | --- | --- |
| `스피커` | `1` *(Mono)* | `2` *(Stereo)* | *모노에서 스테레오로 업믹스*`M` 입력 채널이 양
 출력 채널 (`L`와 `R`)에 대해 사용됩니다.`output.L = input.Moutput.R = input.M` |
| `1` *(Mono)* | `4` *(Quad)* | *모노에서 quad로 업믹스*`M` 입력 채널이 비
 서라운드(non-surround) 출력 채널에 대해 사용됩니다 (`L` 과
 `R`). 서라운드 출력 채널 (`SL` 과
 `SR`)은 작동하지 않습니다(silent).`output.L = input.Moutput.R = input.Moutput.SL = 0output.SR
= 0` |
| `1` *(Mono)* | `6` *(5.1)* | *모노에서 5.1로 업믹스*`M` 입력 채널이 센터 출력
 채널 (`C`)에 대해 사용됩니다. 모든 다른
 채널들(`L`, `R`, `LFE`,
 `SL`, 그리고 `SR`)은 작동하지 않습니다.`output.L = 0output.R = 0``output.C = input.Moutput.LFE = 0output.SL = 0output.SR
= 0` |
| `2` *(Stereo)* | `1` *(Mono)* | *스테레오에서 모노로 다운믹스*양 출력 채널 (`L`
 과 `R`)은 고유한 출력 채널 (`M`)을 생산하기 위해
 동등하게 결합됩니다.`output.M = 0.5 * (input.L + input.R)` |
| `2` *(Stereo)* | `4` *(Quad)* | *스테레오에서 quad로 업믹스*`L` 과
 `R` 입력 채널이 각자의 비 서라운드 출력 채널 (`L`
 과 `R`)에 대해 사용됩니다. 서라운드 출력 채널 (`SL`
 과 `SR`) 은 작동하지 않습니다.`output.L = input.Loutput.R = input.Routput.SL = 0output.SR
= 0` |
| `2` *(Stereo)* | `6` *(5.1)* | *스테레오에서 5.1로 업믹스*`L` 과
 `R` 입력 채널이 각자의 비 서라운드 출력 채널 (`L`
 과 `R`) 에 대해 사용됩니다. 서라운드 출력 채널 (`SL`
 과 `SR`), 그리고 센터 (`C`) 와 서브우퍼
 (`LFE`) 채널은 작동하지 않습니다.`output.L = input.Loutput.R = input.Routput.C = 0output.LFE
= 0output.SL = 0output.SR = 0` |
| `4` *(Quad)* | `1` *(Mono)* | *quad에서 모노로 다운믹스*모든 네 개의 입력 채널
 (`L`, `R`, `SL`, and `SR`)
 이 고유한 출력 채널 (`M`)을 생산하기 위해 동등하게
 결합됩니다.`output.M = 0.25 * (input.L + input.R +` `input.SL + input.SR``)` |
| `4` *(Quad)* | `2` *(Stereo)* | *quad에서 스테레오로 다운믹스*왼쪽 입력 채널 (`L`
 과 `SL`) 둘 다 고유한 왼쪽 출력 채널 (`L`)을
 생산하기 위해 동등하게 결합됩니다. 그리고 유사하게, 오른쪽 입력 채널
 (`R` 과 `SR`) 둘 다 고유한 오른쪽 출력 채널을
 생산하기 위해 동등하게 결합됩니다.`output.L = 0.5 * (input.L + input.SL``)``output.R = 0.5 * (input.R + input.SR``)` |
| `4` *(Quad)* | `6` *(5.1)* | *quad에서 5.1로 업믹스*`L`, `R`,
 `SL`, 그리고 `SR` 입력 채널이 각각의 출력 채널
 (`L` 과 `R`)에 대해 사용됩니다. 센터
 (`C`)와 서브우퍼 (`LFE`) 채널은 작동하지 않은 채로
 남아있습니다.`output.L = input.Loutput.R = input.Routput.C = 0output.LFE
= 0output.SL = input.SLoutput.SR = input.SR` |
| `6` *(5.1)* | `1` *(Mono)* | *5.1에서 모노로 다운믹스*왼쪽 (`L` 과
 `SL`), 오른쪽 (`R` 과 `SR`) 그리고 중앙
 채널이 모두 함께 믹스됩니다. 서라운드 채널은 약간 약화되고 regular
 lateral 채널은 하나의 채널로 카운트되도록 `√2/2`를 곱함으로써
 파워가 보정(power-compensated)됩니다. 서브우퍼 (`LFE`) 채널은
 손실됩니다.`output.M = 0.7071 * (input.L + input.R) + input.C + 0.5 * (input.SL +
input.SR)` |
| `6` *(5.1)* | `2` *(Stereo)* | *5.1에서 스테레오로 다운믹스*중앙 채널 (`C`)이
 각각의 측면 서라운드 채널(`SL` 또는 `SR`)과
 합계되고 각각의 측면 채널로 믹스됩니다. 두 개의 채널로
 다운믹스되었으므로, 더 낮은 파워로 믹스되었습니다: 각각의 경우에
 `√2/2`가 곱해집니다. 서브우퍼 (`LFE`) 채널은
 손실됩니다.`output.L = input.L + 0.7071 * (input.C + input.SL)output.R =
input.R` `+ 0.7071 * (input.C + input.SR)` |
| `6` *(5.1)* | `4` *(Quad)* | *5.1에서 quad로 다운믹스*중앙 (`C`) 채널이
 측면의 비 서라운드 채널 (`L` 과 `R`)과 믹스됩니다.
 두 채널로 다운믹스되었으므로, 더 낮은 파워로 믹스되었습니다: 각각의
 경우에 `√2/2`가 곱해집니다. 서라운드 채널은 변경되지 않은
 채로 전달됩니다. 서브우퍼 (`LFE`) 채널은 손실됩니다.`output.L = input.L + 0.7071 * input.Coutput.R = input.R +
0.7071 * input.Coutput.SL = input.SLoutput.SR =
input.SR` |
| 기타 비표준 레이아웃 |
 비표준 채널 레이아웃은 `channelInterpretation`이
 `discrete`로 설정된 것처럼 다뤄집니다.사양(specification)은
 분명히 새로운 스피커 레이아웃의 미래의 정의를 허용합니다. 특정한 수의
 채널에 대한 브라우저의 행동이 미래에 달라질지도 모르므로 이 대비책은
 그러므로 미래에도 사용할 수 있는 (future proof) 것이 아닙니다.
  |
| `discrete` | any (`x`) | `x<y`인 any (`y`) | *discrete 채널의 업믹스*대응하는 입력 채널을 가지고 있는
 각각의 출력 채널을 채웁니다, 즉 같은 인덱스를 가진 입력 채널입니다.
 해당하는 입력 채널이 없는 채널은 작동하지 않은 채로 남아있습니다.
  |
| any (`x`) | `x>y`인 any (`y`) | *discrete 채널의 다운믹스*대응하는 입력 채널을 가지고 있는
 각각의 출력 채널을 채웁니다, 즉 같은 인덱스를 가진 입력 채널입니다.
 해당하는 출력 채널을 가지고 있지 않은 입력 채널은 탈락됩니다.
  |

시각화
---

일반적으로, 오디오 시각화는 보통 진폭 이득(gain) 또는 주파수 데이터인, 시간에 대한 오디오 데이터의 출력에 접근함으로써, 그리고서 그것을 그래프와 같이 시각적 결과로 바꾸기 위해 그래픽 기술을 사용함으로써 성취됩니다. Web Audio API는 통과하는 오디오 신호를 변경하지 않는 `AnalyserNode`를 가지고 있습니다. 대신 이것은 `<canvas>`와 같은 시각화 기술로 전달될 수 있는 오디오 데이터를 출력합니다.

![오디오 스트림을 수정하는 일 없이, FFT를 사용하여, 노드가 주파수와 그것에 관련된 시간 영역(time-domain) 데이터를 얻을 수 있게 허용합니다.](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/fttaudiodata_en.svg)

여러분은 다음의 메서드들을 사용해 데이터를 얻을 수 있습니다:

`AnalyserNode.getFloatFrequencyData()`

현재 주파수 데이터를 이것 안으로 전달된 `Float32Array` 배열 안으로 복사합니다.

`AnalyserNode.getByteFrequencyData()`

현재 주파수 데이터를 이것 안으로 전달된 `Uint8Array` (unsigned byte array) 안으로 복사합니다.

`AnalyserNode.getFloatTimeDomainData()`

현재 파형, 또는 시간 영역(time-domain), 데이터를 이것 안으로 전달된 `Float32Array` 안으로 복사합니다.

`AnalyserNode.getByteTimeDomainData()`

현재 파형, 또는 시간 영역, 데이터를 이것 안으로 전달된 `Uint8Array` (unsigned byte array) 안으로 복사합니다.

**참고:** 더 많은 정보를 보시려면, Web Audio API로 시각화 문서를 참조하세요.

공간화
---

(Web Audio API의 `PannerNode` (en-US) 와 `AudioListener` (en-US) 노드에 의해 다뤄지는) 오디오 공간화는 공간의 어떤 점에서의 오디오 신호의 위치와 행동을 나타내고(model), 청자(listener)가 그 오디오를 들을 수 있게 허용합니다.

panner의 위치는 right-hand 데카르트 좌표 (Cartesian coordinate)로 기술됩니다; 이것의 움직임은 도플러 효과를 생성하는데 필수적인 속도 벡터를 사용하고, 이것의 방향성(directionality)은 방향성 원뿔을 사용합니다. 이 원뿔은 아주 클 수 있는데, 예를 들자면 전방향의 소스(omnidirectional source)에 대한 것일 수 있습니다.

![PannerNode는 공간 위치와 속도와 주어진 신호에 대한 방향성을 제공합니다.](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/pannernode_en.svg)

청자의 위치는 right-hand 데카르트 좌표를 사용해 기술됩니다; 이것의 움직임은 속도 벡터를 사용하고 청자의 머리가 향하고 있는 방향은 위와 앞의 두 개의 방향 벡터를 사용합니다. 이것들은 각각 청자의 머리의 위의 방향과, 청자의 코가 가리키고 있는 방향을 정의하며, 서로 직각에 있습니다.

![AudioListener의 위와 앞의 벡터 위치를 보고 있는데, 위와 앞 벡터는 서로 90°에 있습니다.](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/webaudiolistenerreduced.png)

**참고:** 더 많은 정보를 보시려면, Web audio 공간화 기본 문서를 참조하세요.

팬 인(fan-in)과 팬 아웃(fan-out)
--------------------------

오디오 용어에서, **팬 인**은 `ChannelMergerNode` (en-US)가 일련의 모노 입력 소스를 취하고 단일의 다수 채널 신호를 출력하는 과정을 설명합니다:

![](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/fanin.svg)

**팬 아웃**은 반대 과정을 설명하는데, `ChannelSplitterNode` (en-US)가 다수 채널 입력 소스를 취하고 다수의 모노 출력 신호를 출력합니다.

![](/ko/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API/fanout.svg)