원문: Generate Sounds Programmatically With Javascript

최근에 있었던 해커톤에서 나는 Web Audio API를 사용해 프로그래밍 방식으로 생성된 소리를 이용한 멀티플레이어 8비트 시퀀서를 만들기로 결정했다. HTML 5의 audio 태그만 사용하는 것은 원치 않았는데 너무 제한적이란 걸 알았기 때문이다… 하지만 처음 발견했던 것은 제대로 된 소리를 내는 것이 전혀 간단하지 않았다는 것이다. 특히 당신이 나처럼 아주 기초적인 음악적 소양 정도만 가지고 있다면 말이다. 그럼 정확히 어떻게 해야 깨끗하고 낭랑하며 멋진 소리가 나는 음을 만들 수 있을까?

이 글에서는 몇몇 조언과 사용 가능한 코드를 제공한다. 당신이 사용 중인 브라우저에서 지원한다면 실제로 실행할 수 있는 예제도 있다.1

간단한 삐 소리 만들기

첫 번째로 사인파를 이용해 아주 기본적인 삐 소리를 만들어 보자. 우리는 소리를 생성하는 데 가장 중요한 객체인 audio context를 초기화한 후에, 사인파를 생성하는 oscillator를 생성할 것이다. 마지막으로 oscillator를 context에 연결하고 start 한다.

var context = new AudioContext()
var o = context.createOscillator()
o.type = "sine"
o.connect(context.destination)
o.start()

재생 정지

여기서 생성한 소리가 썩 좋지 않다는 것을 알아차렸을 것이다. 전화기를 들었을 때 같은 소리가 나고, 멈출 때 “뚝”(click) 소리를 듣게 되는데 이는 전혀 즐겁지 않다. 이유는 사람의 청각은 이 훌륭한 글에서 설명한 것과 같이 반응하기 때문이다. 기본적으로, 사인파가 0이 되는 지점 외에서 소리를 멈췄을 때 당신은 이 뚝 소리를 듣게 된다.

사인파의 뚝 하는 잡음

뚝 소리 없애기

뚝 소리를 없애기 위한 최고의 해답은 지수함수를 이용해 사인파를 감소시키는 것이다. 이 문서에 나오는 AudioParam.exponentialRampToValueAtTime()를 사용한다.

이번엔 우리는 oscillator에 gain node를 추가할 필요가 있다. Gain node를 사용하면 문서에 있는 이 도표에 나온 것처럼 신호의 볼륨을 바꿀 수 있다.

Gain node

소리를 시작하는 코드는 이제 이렇게 바뀌었다:

var context = new AudioContext()
var o = context.createOscillator()
var  g = context.createGain()
o.connect(g)
g.connect(context.destination)
o.start(0)

재생

소리를 멈추기 위해 우리는 gain 값을 변경해 효과적으로 볼륨을 낮출 수 있다. 이 함수에는 값이 항상 양수여야 하는 제약이 있기 때문에 0으로 감소시키지 않는다는 것을 참고하자.

g.gain.exponentialRampToValueAtTime(
  0.00001, context.currentTime + 0.04
)

정지

직접 들었듯이 뚝 소리가 사라졌다! 하지만 지수 감소를 이용해 할 수 있는 흥미로운 일은 이뿐만이 아니다.

울리는 효과 설정하기

위 예제에서는 소리를 0.04초 동안 아주 빠르게 멈췄다. 하지만 이 X 값을 바꾸면 어떻게 될까?

g.gain.exponentialRampToValueAtTime(0.00001, context.currentTime + X)

재생 정지 (X=0.1) 정지 (X=1) 정지 (X=5)

타격음

소리가 작아질 때의 시간을 길게 주면 전혀 다른 느낌을 준다. 신호가 시작하자마자 정지해 보면 더 확연히 드러난다.

시작하고 빠르게 정지 시작하고 느리게 정지

첫 번째는 톡 하는 잡음 같고 다른 소리는 실제 악기에서 연주되는 음 같다.

다양한 Oscilator

지금까지 우리는 주로 사인파 신호를 사용하였다. 하지만 다른 선택지도 있다:

Oscilator의 종류

o.type = type를 설정하여 oscilator의 종류를 바꿔가며 재생해 보면 더욱 흥미롭다.

Sine Square Triangle Sawtooth

실제 음계 재생하기

위 코드로 멋진 음을 내는 것이 매우 간단해졌다. 하지만 대체 무슨 음을 재생하고 있던 것일까? 이제 진동수를 고려해봐야 할 때다. 이를테면 어느 사람은 A4가 440Hz라는 것을 알지만, 더 많은 음이 존재한다.

음의 Hz 진동수

다음의 표를 이용하면 어떤 음이라도 진동수를 이용하여 재생 가능한 매핑을 쉽게 생성할 수 있다. 나는 해커톤에서 간단한 해시 매핑을 사용하였고 이 gist에서 볼 수 있다.

CC#DEbEFF#GG#ABbB
016.3517.3218.3519.4520.6021.8323.1224.5025.9627.5029.1430.87
132.7034.6536.7138.8941.2043.6546.2549.0051.9155.0058.2761.74
265.4169.3073.4277.7882.4187.3192.5098.00103.8110.0116.5123.5
3130.8138.6146.8155.6164.8174.6185.0196.0207.7220.0233.1246.9
4261.6277.2293.7311.1329.6349.2370.0392.0415.3440.0466.2493.9
5523.3554.4587.3622.3659.3698.5740.0784.0830.6880.0932.3987.8
6104711091175124513191397148015681661176018651976
7209322172349248926372794296031363322352037293951
8418644354699497852745588592062726645704074597902

이를 구현하려면 oscilator에 진동수를 추가하기만 하면 된다.

var frequency = 440.0
o.frequency.value = frequency

진동수 값을 변경한다면 어떤 음도 재생할 수 있다. 예를 들면:

261.6Hz (C4) 440Hz (A4) 830.6Hz (G#5)

볼륨이 감소하는 시점과 서로 다른 신호를 조합하면 더 흥미로운 소리를 만들 수 있다.

174.6Hz (F3) - Square 1109Hz (C#6) - Sawtooth 87.31 Hz (F2) - Triangle


  1. (역주) 해당 페이지에도 나와 있지만 Safari의 경우 webkit prefix가 필요, 즉 webkitAudioContext과 같이 사용해야 합니다. 따라서 본 글의 예제도 제대로 작동하지 않습니다.