한눈에 보기
- 원문 핵심: 음악 이론을 악보 암기보다 주파수와 배열의 변환 규칙으로 설명
- 출발점: 브라우저가 만드는 440Hz 파형에 음량 변화를 붙이면 한 음의 기본 형태 완성
- 12음의 이유: 옥타브를 12개의 같은 비율로 나눈 평균율은 순정한 음정을 조금씩 포기한 실용적 절충안
- 코드 표현: 음계는 간격 배열, 화음은 반음 거리 배열, 조옮김은 기준값에 같은 수를 더하는 연산
- 주의할 점: 협화음의 선호를 물리 법칙 하나로 설명할 수 없으며 문화적 학습과 익숙함도 큰 영향
- 실무 의미: 음악 교육, 게임 사운드, 창작 도구를 만들 때 소리 생성과 음악 규칙을 별도 층으로 설계할 필요
서론: 악보보다 배열이 먼저였던 음악 이론 수업
2026년 8월 17일 RunJS의 창업자 루크 하스는 Music theory for programmers라는 장문 튜토리얼을 공개했다. 악기를 연주하지 못하는 프로그래머가 440Hz 사인파 하나에서 출발해 배음, 옥타브, 12평균율, MIDI 번호, 음계, 화음, 코드 진행, 악보까지 자바스크립트로 다시 만드는 내용이다.
원문의 장점은 음악 용어를 프로그래밍 개념에 억지로 빗대는 데 있지 않다. 실제로 주파수를 계산하고 배열을 바꾸며 소리를 들어본 다음, 그 결과에 음악 용어를 붙인다. 장음계는 [2, 2, 1, 2, 2, 2, 1], 장3화음은 [0, 4, 7], 단3화음은 [0, 3, 7]이라는 식이다. 이름을 외우기 전에 입력과 변환 규칙을 확인하니 무엇이 바뀌었는지가 눈에 들어온다.
다만 모든 설명을 물리와 산술만으로 닫으면 곤란하다. 원문은 서양 조성 음악을 이해하는 훌륭한 입구지만, “왜 좋은 소리로 들리는가”에는 배음의 겹침뿐 아니라 문화적으로 익힌 기대도 작용한다. 따라서 이 글은 원문의 계산을 직접 다시 확인하고, 브라우저 구현에서 빠지기 쉬운 조건과 음악 지각 연구의 반대 근거까지 함께 다룬다.
1. 원문은 소리에서 악보까지 어떤 순서로 올라가나
원문은 악보의 오선이나 음이름으로 시작하지 않는다. 먼저 시간에 따라 달라지는 숫자 목록이 스피커 진동으로 바뀐다는 사실을 보여준다. 그 위에 주파수, 음량 변화, 배음, 음정, 음계, 화음, 코드 진행을 차례로 올린 뒤 마지막에 악보를 설명한다.
흐름을 압축하면 다음과 같다.
- 소리 만들기:
OscillatorNode로 일정한 주파수의 파형 생성 - 음의 모양 만들기:
GainNode로 시작과 끝의 음량을 부드럽게 변경 - 높낮이 정리하기: 주파수를 두 배로 만들면 같은 음이름의 한 옥타브 위 소리
- 12개 음 만들기: 한 옥타브를 같은 비율의 12단계로 나누고 MIDI 정수에 연결
- 음악 문법 만들기: 음계와 화음, 코드 진행을 정수 배열로 표현
- 기록 형식 읽기: 악보를 음높이·길이·조표를 담은 오래된 직렬화 형식으로 해석
이 순서가 프로그래머에게 익숙한 이유는 추상화 층을 한 번에 하나씩 추가하기 때문이다. 가장 아래에는 공기와 파형이 있고, 가장 위에는 ‘C장조’나 ‘I–V–vi–IV’ 같은 사람이 읽는 기호가 있다. 위층의 표현은 아래층의 계산을 감추지만, 필요하면 다시 주파수까지 내려갈 수 있다.
2. 먼저 쉽게 이해하면: 소리는 시간에 따라 바뀌는 숫자다
한 문장으로 말하면: 디지털 소리는 매 순간 스피커가 얼마나 움직여야 하는지를 적은 숫자의 연속이다.
한 장씩 빠르게 넘기는 애니메이션을 떠올리면 큰 그림을 잡기 쉽다. 그림 한 장은 멈춰 있지만 여러 장을 일정한 속도로 보여주면 움직임이 된다. 오디오 샘플도 각각은 한 시점의 크기일 뿐이지만, 초당 수만 개를 차례로 재생하면 파형과 소리로 들린다.
실제로는 다음 세 단계로 작동한다.
- 입력: 주파수, 파형 종류, 시작 시각, 재생 길이
- 처리: 브라우저가 시간별 샘플을 만들고 음량 변화를 적용
- 출력: 오디오 장치를 거쳐 스피커가 공기를 밀고 당기는 소리
비유의 한계: 실제 브라우저 오디오는 표본화 주파수, 위상, 지연 시간, 에일리어싱, 운영체제의 오디오 장치 같은 조건을 함께 받는다. 숫자 배열이라는 설명만으로 음질과 재생 시점을 모두 설명할 수는 없다.
Web Audio API 명세에서 OscillatorNode는 기본 440Hz 사인파를 만들며, frequency는 헤르츠 단위의 AudioParam이다. 하지만 주파수만 갑자기 끊으면 파형 중간에서 값이 튀어 짧은 클릭 소리가 날 수 있다. 원문이 GainNode로 0에서 시작해 빠르게 커지고 다시 거의 0으로 줄어드는 곡선을 붙이는 이유다. 음악에서는 이런 시간별 음량 모양을 엔벌로프라고 부른다.

편집용 개념도: Web Audio의 세부 처리 전체가 아니라 숫자 입력에서 소리가 나기까지의 큰 흐름만 단순화했다
여기서 주파수와 음색은 구분해야 한다. 220Hz 사인파와 220Hz 톱니파는 같은 높이로 들리지만 느낌은 다르다. 사인파는 한 주파수에 가깝고, 톱니파나 실제 악기는 기본 주파수의 정수배에 해당하는 배음을 함께 포함하기 때문이다. 음높이는 중심 주파수와 관련되고, 음색은 어떤 배음이 얼마나 섞였는지와 시간에 따른 변화까지 포함한다.
3. 왜 한 옥타브에 12개 음이 들어가나
주파수 공간은 덧셈보다 곱셈으로 보는 편이 맞다. 220Hz에서 한 옥타브 위는 440Hz이고, 440Hz에서 한 옥타브 위는 880Hz다. 증가량은 각각 220Hz와 440Hz로 다르지만 귀에는 같은 간격으로 느껴진다. 옥타브가 주파수 비율 2:1로 정의되기 때문이다.
3:2의 완전5도처럼 단순한 비율은 배음이 자주 겹친다. 220Hz와 330Hz를 함께 내면 두 음의 배음 중 660Hz와 1,320Hz 등이 맞아떨어진다. 그러나 완전5도만 계속 쌓아 음 체계를 만들면 열두 번 뒤에 처음 위치로 정확히 돌아오지 않는다.
원문의 계산을 자바스크립트로 다시 확인하면 (3/2)^12 / 2^7은 약 1.013643이다. 음정 차이 단위인 센트로 바꾸면 약 23.46센트다. 반올림 오차가 아니라 2의 거듭제곱과 3의 거듭제곱이 정확히 같아질 수 없어서 생기는 구조적 차이다. 이를 피타고라스 콤마라고 부른다.
오늘날 널리 쓰는 12평균율은 순정한 비율을 모두 지키는 대신 한 옥타브를 같은 비율의 12단계로 나눈다. 한 단계의 비율은 2^(1/12)이고, 일곱 단계 위의 평균율 완전5도는 순정 3:2보다 약 1.96센트 낮다. 작은 오차를 모든 조에 나눠 가진 덕분에 같은 간격 구조를 어느 음에서든 옮겨 쓸 수 있다.
| 옥타브 분할 수 | 3:2에 가장 가까운 단계 | 오차 |
|---|---|---|
| 7 | 4단계 | 약 16.24센트 |
| 12 | 7단계 | 약 1.96센트 |
| 19 | 11단계 | 약 7.22센트 |
| 29 | 17단계 | 약 1.49센트 |
| 41 | 24단계 | 약 0.48센트 |
12가 수학적으로 유일한 답은 아니다. 29나 41분할은 완전5도를 더 가깝게 만들 수 있지만 건반과 작곡 체계가 훨씬 복잡해진다. 12는 비교적 적은 음으로 여러 조에서 쓸 만한 음정을 얻는 절충안이다. 원문이 이를 ‘가장 싼 수의 음으로 설득력 있는 완전5도를 산다’고 설명하는 이유가 여기에 있다.
평균율을 MIDI 번호와 연결하면 음높이가 정수 좌표가 된다. 공식 MIDI 자료에서 가운데 C는 60, 440Hz의 A는 69로 다뤄진다. 변환식 440 * 2 ** ((n - 69) / 12)을 적용하면 MIDI 60은 약 261.63Hz, 69는 440Hz, 72는 약 523.25Hz가 된다. 이 수치를 직접 계산해 원문 예제와 일치함을 확인했다.
4. 음계와 화음은 배열로 어떻게 표현되나
12개의 음을 모두 같은 간격으로 차례로 연주하면 위치를 잡기 어렵다. 서양 조성 음악은 그중 일부를 고르고 간격을 불균등하게 배치해 기준점과 긴장을 만든다. 음계는 음이름 목록보다 다음 음까지 몇 반음 이동하는지 적은 배열로 표현할 수 있다.
- 장음계:
[2, 2, 1, 2, 2, 2, 1] - 자연단음계:
[2, 1, 2, 2, 1, 2, 2] - 장음계의 C 기준 MIDI 값:
[60, 62, 64, 65, 67, 69, 71, 72]
장음계의 일곱 모드는 같은 간격 배열의 시작점을 옮긴 회전으로 만들 수 있다. 이 설명은 이오니안, 도리안, 프리지안 같은 온음계 모드를 이해하는 데 유용하다. 다만 세상의 모든 음계가 하나의 배열 회전으로 환원되는 것은 아니다. 다른 조율법과 비서양 음악 전통에는 이 모델 밖의 음정과 규칙이 있다.
화음은 동시에 낼 음을 기준음에서 얼마나 떨어뜨릴지 적은 배열로 볼 수 있다. 장3화음은 [0, 4, 7], 단3화음은 [0, 3, 7]이다. 가운데 값 하나가 4에서 3으로 바뀌면 장화음에서 단화음으로 달라진다. 음계 안에서 한 음씩 건너뛰어 1·3·5번째 음을 고르는 방식은 [0, 2, 4]라는 음계 인덱스로 표현할 수 있다.

편집용 개념도: 서양 12평균율에서 자주 쓰는 표현을 층별로 정리했으며 다른 조율법과 음악 전통 전체를 뜻하지 않는다
로마 숫자로 쓰는 코드 진행은 상대 주소와 비슷하다. I–V–vi–IV는 C, G, Am, F라는 고정 이름이 아니라 현재 조의 1·5·6·4번째 화음을 뜻한다. 기준음만 바꾸면 간격 관계를 유지한 채 곡 전체를 옮길 수 있다. 조옮김은 복잡한 재작성보다 모든 MIDI 값에 같은 정수를 더하는 연산에 가까워진다.
5. 프로그래머가 여기서 배울 수 있는 설계 원칙
음악 이론을 코드로 옮기는 과정은 오디오 프로그래밍을 넘어 자료 모델을 설계하는 연습이 된다. 같은 대상을 어느 층에서 표현하느냐에 따라 수정 비용과 오류 가능성이 크게 달라진다.
첫째, 단위를 자료형처럼 다뤄야 한다. 440이라는 값은 주파수이고, 69는 MIDI 번호이며, A4는 사람이 읽는 음이름이다. 모두 같은 음을 가리킬 수 있지만 서로 바로 더하거나 비교하면 안 된다. 실제 서비스에서는 FrequencyHz, MidiNote, PitchName, ScaleDegree를 구분하고 변환 함수를 경계에 두는 편이 안전하다.
둘째, 제약은 표현력을 줄이는 대신 틀릴 공간을 줄인다. 아무 주파수나 고르는 것보다 음계 배열 안에서 멜로디를 고르면 서로 어울릴 가능성이 높다. 생성형 음악 기능도 같은 원리를 쓸 수 있다. 모델이 모든 값을 직접 만들게 하기보다 조, 박자, 음역, 악기별 동시 발음 수를 먼저 제한하면 검사하기 쉬운 결과를 얻는다.
셋째, 상대 표현은 재사용을 만든다. 코드 진행을 실제 음이름이 아니라 음계 안의 위치로 저장하면 조옮김과 비교가 쉬워진다. UI에서도 사용자는 ‘C4를 261.63Hz로 바꾸기’보다 ‘한 옥타브 올리기’나 ‘장조를 단조로 바꾸기’를 원한다. 내부 값과 사용자의 작업 언어를 분리해야 한다.
넷째, 기호 처리와 소리 재생은 다른 책임이다. 음계와 화음을 계산하는 엔진이 곧 신시사이저는 아니다. 다음 도구들은 서로 다른 층을 맡는다.
| 도구 | 주된 역할 | 적합한 작업 | 남는 책임 |
|---|---|---|---|
| Web Audio API | 파형·음량·필터·출력 연결 | 브라우저 소리 생성 | 재생 시점·클릭·기기 차이 |
| Tone.js | 악기·동시 발음·시간 예약 | 음악 앱 시제품 | 음악 규칙과 화면 설계 |
| Tonal | 음정·음계·화음의 기호 계산 | 이론 엔진·분석 | 실제 소리 출력 |
| RunJS | 즉시 실행되는 자바스크립트 실험 | 학습·짧은 검증 | 제품용 구조와 배포 |
[Tonal 공식 저장소](https://github.com/tonaljs/tonal)는 자신을 실제 소리가 아니라 음표, 음정, 화음, 음계를 다루는 추상화 라이브러리로 설명한다. 반면 [Tone.js의 PolySynth 문서](https://tonejs.github.io/docs/15.1.22/classes/PolySynth.html)는 여러 단일 음색의 발음을 배분해 화음을 재생하는 역할에 초점을 둔다. 두 층을 나누면 음악 규칙을 바꿀 때 오디오 엔진까지 다시 작성하지 않아도 된다.
6. 원문의 설명을 어디까지 믿어야 하나
원문의 “단순한 분수일수록 좋게 들린다”와 “협화음은 귀가 반복 패턴을 빨리 찾는 것”이라는 문장은 입문 설명으로는 힘이 있다. 배음의 겹침과 가까운 주파수 사이의 맥놀이가 음정의 거칠기에 영향을 주는 것도 사실이다. 그러나 이것만으로 아름다움이나 선호를 보편 법칙처럼 설명해서는 안 된다.
2016년 7월 13일 네이처에 실린 볼리비아 치마네 원주민과 미국·볼리비아 도시 거주자의 비교 연구는 서양 음악 노출이 적은 치마네 참가자들이 협화 화음과 불협화 화음을 비슷하게 평가했다고 보고했다. 거친 소리에 대한 반응은 나타났지만, 협화음을 더 좋아하는 정도는 서양 음악 노출이 많은 집단에서 강했다. 물리적 거칠기와 문화적으로 익힌 화성 선호를 구분해야 한다는 뜻이다.
또한 원문이 다루는 범위는 서양 12평균율의 음높이 체계다. 리듬, 프레이징, 음색의 시간 변화, 화음 사이에서 각 성부가 짧은 거리로 움직이는 보이스 리딩은 거의 다루지 않는다. 같은 [0, 4, 7]도 어떤 악기로 어느 음역에서 얼마나 세게 연주하느냐에 따라 전혀 다르게 들린다.
브라우저 구현에도 조건이 있다. MDN의 자동 재생 가이드는 AudioContext의 재생 가능 상태가 사용자 상호작용에 따라 달라질 수 있다고 설명한다. 따라서 실제 웹 앱은 버튼 클릭 뒤 ctx.resume()을 호출하고, 오실레이터 시작·종료 시각을 currentTime 기준으로 예약해야 한다. 긴 곡이나 정교한 리듬에서는 자바스크립트 타이머만 믿지 말고 오디오 시계와 미리보기 범위를 함께 설계해야 한다.
7. 30분 안에 따라 할 최소 실습
처음부터 곡 전체를 만들 필요는 없다. 다음 네 단계만 실행하면 원문의 핵심 구조를 확인할 수 있다.
- 440Hz 사인파를 0.3초 재생하고 시작과 끝에 음량 변화를 적용
- MIDI 번호를 주파수로 바꾸는 함수를 만든 뒤 60·69·72의 결과 확인
- 장음계 간격 배열을 누적해 여덟 개의 MIDI 번호 생성
[0, 4, 7]과[0, 3, 7]을 동시에 재생해 가운데 음 하나의 차이 비교
아래 코드는 원문과 Web Audio 문서를 바탕으로 짧게 다시 구성한 예시다. 실제 실행 결과를 기록한 코드는 아니며 브라우저의 사용자 클릭 이벤트 안에서 호출해야 한다.
const midiToFreq = (midi) => 440 * 2 ** ((midi - 69) / 12);
const buildScale = (root, gaps) =>
gaps.reduce(
(notes, gap) => [...notes, notes.at(-1) + gap],
[root],
);
const major = buildScale(60, [2, 2, 1, 2, 2, 2, 1]);
const majorChord = [0, 4, 7].map((gap) => 60 + gap);
const minorChord = [0, 3, 7].map((gap) => 60 + gap);
console.log(major.map(midiToFreq));
소리가 나지 않으면 음악 계산부터 의심하기보다 AudioContext 상태, 출력 노드 연결, 음량 값, 시작·종료 시각을 먼저 본다. RunJS는 Web Audio 가이드에서 OscillatorNode와 GainNode를 연결하는 최소 예제를 제공한다. 일반 브라우저 콘솔에서는 페이지의 자동 재생 정책과 사용자 입력 조건을 별도로 처리해야 한다.
8. 서비스로 보면 어디에 쓸 수 있나
RunJS의 글은 음악 이론 강의인 동시에 자바스크립트 실행 환경이 어떤 학습 경험을 만들 수 있는지 보여주는 제품 예제다. 설명을 읽고 같은 자리에서 코드를 바꾸고 즉시 소리를 듣는 흐름은 설치와 프로젝트 설정에 드는 시간을 줄인다. 정답을 읽는 대신 한 숫자를 바꿔 결과를 비교할 수 있다는 점이 핵심 가치다.
- 코딩 교육: 배열,
reduce, 지수 함수, 시간 예약을 들을 수 있는 결과로 학습 - 음악 교육: 악보 이전에 주파수와 간격을 직접 바꿔 음정 차이를 확인
- 게임·웹 제작: 짧은 효과음, UI 알림음, 절차적 음악의 시제품 제작
- 창작 도구: 음계와 코드 규칙을 제한 조건으로 제공하는 작곡 보조 기능
운영 비용도 분명하다. 모바일 브라우저별 오디오 정책, 블루투스 장치의 지연, 많은 동시 발음이 만드는 CPU 부하, 샘플 음원의 라이선스, 청각 과민 사용자를 위한 음량 제한과 접근성 설정을 챙겨야 한다. 교육 서비스라면 소리가 들리지 않는 환경에서도 파형과 숫자로 결과를 확인할 대체 화면이 필요하다.
한국 시장에서는 비전공자 대상 개발 교육과 음악 테크 수업의 접점이 먼저 열릴 수 있다. ‘반복문으로 음계를 만든다’는 실습은 수학과 코딩을 별개 과목으로 나누지 않고 하나의 결과물로 묶는다. 다만 서양 12평균율만 음악의 보편 규칙처럼 가르치지 말고 국악의 음 조직, 다른 조율 체계, 리듬 중심 전통이 별도로 존재한다는 경계를 함께 알려야 한다.
결론: 음악 이론이 쉬워진 것이 아니라 바꿔 볼 수 있게 됐다
RunJS의 튜토리얼이 주는 가장 큰 도움은 음악을 몇 개의 공식으로 끝냈다는 데 있지 않다. 음높이, 음계, 화음, 조옮김을 각각 수정 가능한 자료와 함수로 바꿔 놓았다는 데 있다. 프로그래머는 값을 바꾸고 결과를 들으며 규칙이 맡은 역할과 한계를 스스로 확인할 수 있다.
이 접근은 악보 읽기나 연주 경험을 대신하지 않는다. 리듬과 표현, 문화적 문법, 실제 악기의 물성은 배열 밖에 많이 남아 있다. 그래도 소리를 처음 배우는 개발자에게 주파수에서 시작해 MIDI와 음계, 화음으로 올라가는 순서는 좋은 출발점이다. 음악 이론을 암기 목록이 아니라 검증 가능한 모델로 바꿔 주기 때문이다.
참고 자료
- Music theory for programmers - RunJS, 2026년 8월 17일
- Web Audio - RunJS 공식 문서, 2026년 8월 확인
- Web Audio API - MDN Web Docs
- Web Audio API 1.1 - W3C Audio Working Group
- Autoplay guide for media and Web Audio APIs - MDN Web Docs
- Microtuning and Alternative Intonation Systems - The MIDI Association
- Indifference to dissonance in native Amazonians reveals cultural variation in music perception - Nature, 2016년 7월 13일
- Tonal - Tonal 공식 GitHub 저장소
- PolySynth - Tone.js 공식 문서
'최신IT 정보 > IT 개발정보' 카테고리의 다른 글
| AI 시대 프로덕트 엔지니어란: 코드를 넘어 문제·실험·성과를 책임지는 법 (0) | 2026.08.24 |
|---|---|
| Netflix가 LLM API 요금을 버렸다: 자체 서빙 스택 vLLM + Triton로 전환한 진짜 이유 (1) | 2026.07.27 |
| [Tech Deep Dive] open-design 코드 레벨 분석: 로컬 CLI 에이전트 기반 UI 생성 프레임워크 (0) | 2026.05.05 |